Implemented pagination \o/

This commit is contained in:
Grégoire Pineau 2013-08-08 00:02:57 +02:00
parent c1ca02669b
commit bc307e0a26
36 changed files with 554 additions and 70 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
/Carew/Tests/Functional/fixtures/*/web/
/Carew/Tests/Command/fixtures/*/web/
/composer.lock
/vendor/

View File

@ -99,7 +99,11 @@ class CoreExtension implements ExtensionInterface
});
$container['twig'] = $container->share(function($container) {
$twig = new Twig_Environment($container['twig.loader'], array('strict_variables' => true, 'debug' => true));
$twig = new Twig_Environment($container['twig.loader'], array(
'strict_variables' => true,
'debug' => true,
'base_template_class' => 'Carew\Twig\Template',
));
// We will not be able to add new global in Twig 2.0, so we should declare everything now;
$twig->addGlobal('carew', new Globals($container['config']));

View File

@ -179,4 +179,9 @@ class Document
return $this;
}
public function __toString()
{
return $this->title;
}
}

View File

@ -18,21 +18,25 @@ class Markdown implements EventSubscriberInterface
public function onDocument(CarewEvent $event)
{
$subject = $event->getSubject();
$documents = $event->getSubject();
$extension = $subject->getFile()->getExtension();
if ('md' !== $extension) {
if ('twig' !== $extension) {
return;
}
$extension = pathinfo(str_replace('.twig', '', $subject->getFilePath()), PATHINFO_EXTENSION);
foreach ($documents as $document) {
$extension = $document->getFile()->getExtension();
if ('md' !== $extension) {
return;
if ('twig' !== $extension) {
continue;
}
$extension = pathinfo(str_replace('.twig', '', $document->getFilePath()), PATHINFO_EXTENSION);
if ('md' !== $extension) {
continue;
}
}
$document->setBody($this->markdownParser->transform($document->getBody()));
}
$subject->setBody($this->markdownParser->transform($subject->getBody()));
$event->setSubject($documents);
}
public static function getSubscribedEvents()

View File

@ -17,32 +17,70 @@ class Twig implements EventSubscriberInterface
public function preRender(CarewEvent $event)
{
$document = $event->getSubject();
$documents = $event->getSubject();
$documentsTmp = array();
foreach ($documents as $k => $document) {
$documentsTmp[] = $document;
if (false === $document->getLayout()) {
continue;
}
if (false === $document->getLayout()) {
return;
$this->setTwigGlobals($event, $document);
// Force autoloading of Twig_Extension_StringLoader
$stringLoader = $this->twig->getExtension('string_loader');
$template = twig_template_from_string($this->twig, $document->getBody());
$nbItems = $template->getNbItems(array());
if ($template->getMaxPerPage() >= $nbItems) {
$document->setBody($template->render(array()));
continue;
}
unset($documentsTmp[$k]);
$nbPages = ceil($nbItems / $template->getMaxPerPage());
for ($i = 1; $i <= $nbPages; $i++) {
$documentTmp = clone $document;
if (1 < $i) {
$pathInfo = pathinfo($documentTmp->getPath());
$pathInfo['filename'] = sprintf('%s-page-%d', $pathInfo['filename'], $i);
$documentTmp->setPath(sprintf('%s/%s.%s', $pathInfo['dirname'], $pathInfo['filename'], $pathInfo['extension']));
}
$documentTmp->setBody($template->render(array(
'__offset__' => ($i - 1) * $template->getMaxPerPage(),
)));
$documentsTmp[] = $documentTmp;
}
}
$this->setTwigGlobals($event);
$document->setBody($this->twig->render('pre_render_template.html.twig', array('body' => $document->getBody())));
$event->setSubject($documentsTmp);
}
public function postRender(CarewEvent $event)
{
$document = $event->getSubject();
$documents = $event->getSubject();
if (false === $document->getLayout()) {
return;
foreach ($documents as $document) {
if (false === $document->getLayout()) {
continue;
}
$this->setTwigGlobals($event, $document);
$layout = $document->getLayout();
if (false === strpos($layout, '.twig')) {
$layout .= '.html.twig';
}
$document->setBody($this->twig->render($layout));
}
$this->setTwigGlobals($event);
$layout = $document->getLayout();
if (false === strpos($layout, '.twig')) {
$layout .= '.html.twig';
}
$document->setBody($this->twig->render($layout));
$event->setSubject($documents);
}
public static function getSubscribedEvents()
@ -55,11 +93,10 @@ class Twig implements EventSubscriberInterface
);
}
private function setTwigGlobals(CarewEvent $event)
private function setTwigGlobals(CarewEvent $event, $document)
{
$globals = $event->hasArgument('globalVars') ? $event->getArgument('globalVars') : array();
$document = $event->getSubject();
$globals['relativeRoot'] = $document->getRootPath();
$globals['currentPath'] = $document->getPath();
$globals['document'] = $document;

View File

@ -64,14 +64,14 @@ class Processor
public function processDocument($document, array $globalVars = array())
{
$event = new CarewEvent($document, array('globalVars' => $globalVars));
$event = new CarewEvent(array($document), array('globalVars' => $globalVars));
try {
$documents = $this->eventDispatcher->dispatch(Events::DOCUMENT_BODY, $event)->getSubject();
} catch (\Exception $e) {
throw new \LogicException(sprintf('Could not process: "%s".', (string) $document->getFile()), 0 , $e);
}
return $document;
return $documents;
}
public function write(Document $document)

View File

@ -12,7 +12,7 @@ class BuildTest extends AbstractTest
public function testExecuteWithSite1()
{
$this->deleteDir($webDir = __DIR__.'/fixtures/site1/web');
list(, $statusCode) = $this->runApplication(dirname($webDir));
list($application, $statusCode) = $this->runApplication(dirname($webDir));
$this->assertSame(0, $statusCode);
@ -54,6 +54,72 @@ class BuildTest extends AbstractTest
$this->deleteDir($webDir);
}
public function testExecuteWithSiteAndPagination()
{
$this->deleteDir($webDir = __DIR__.'/fixtures/site2/web');
list($application, $statusCode) = $this->runApplication(dirname($webDir));
$this->assertSame(0, $statusCode);
$lis = array();
$this->assertTrue(file_exists($webDir.'/index.html'));
$crawler = new Crawler(file_get_contents($webDir.'/index.html'));
$this->assertCount(1, $crawler->filter('ul'));
$this->assertCount(4, $crawler->filter('ul')->eq(0)->filter('li'));
foreach ($crawler->filter('ul')->eq(0)->filter('li') as $li) {
$lis[] = trim($li->textContent);
}
$this->assertTrue(file_exists($webDir.'/index-page-2.html'));
$crawler = new Crawler(file_get_contents($webDir.'/index-page-2.html'));
$this->assertCount(1, $crawler->filter('ul'));
$this->assertCount(4, $crawler->filter('ul')->eq(0)->filter('li'));
foreach ($crawler->filter('ul')->eq(0)->filter('li') as $li) {
$lis[] = trim($li->textContent);
}
$this->assertTrue(file_exists($webDir.'/index-page-3.html'));
$crawler = new Crawler(file_get_contents($webDir.'/index-page-3.html'));
$this->assertCount(1, $crawler->filter('ul'));
$this->assertCount(4, $crawler->filter('ul')->eq(0)->filter('li'));
foreach ($crawler->filter('ul')->eq(0)->filter('li') as $li) {
$lis[] = trim($li->textContent);
}
$this->assertTrue(file_exists($webDir.'/index-page-4.html'));
$crawler = new Crawler(file_get_contents($webDir.'/index-page-4.html'));
$this->assertCount(1, $crawler->filter('ul'));
$this->assertCount(3, $crawler->filter('ul')->eq(0)->filter('li'));
foreach ($crawler->filter('ul')->eq(0)->filter('li') as $li) {
$lis[] = trim($li->textContent);
}
$this->assertFalse(file_exists($webDir.'/index-page-5.html'));
sort($lis);
$expected = array (
'Page1',
'Page10',
'Page11',
'Page12',
'Page13',
'Page14',
'Page2',
'Page3',
'Page4',
'Page5',
'Page6',
'Page7',
'Page8',
'Page9',
'index',
);
$this->assertSame($expected, $lis);
}
public function testExecuteWithConfigFolder()
{
$this->deleteDir($webDir = __DIR__.'/fixtures/config-folder/web');

View File

@ -0,0 +1,8 @@
---
title: index
permalink: index.html
---
Hello
{{ render_documents(paginate(carew.documents, 4)) }}

View File

@ -0,0 +1,5 @@
---
title: Page1
---
Page1

View File

@ -0,0 +1,5 @@
---
title: Page10
---
Page10

View File

@ -0,0 +1,5 @@
---
title: Page11
---
Page11

View File

@ -0,0 +1,5 @@
---
title: Page12
---
Page12

View File

@ -0,0 +1,5 @@
---
title: Page13
---
Page13

View File

@ -0,0 +1,5 @@
---
title: Page14
---
Page14

View File

@ -0,0 +1,5 @@
---
title: Page2
---
Page2

View File

@ -0,0 +1,5 @@
---
title: Page3
---
Page3

View File

@ -0,0 +1,5 @@
---
title: Page4
---
Page4

View File

@ -0,0 +1,5 @@
---
title: Page5
---
Page5

View File

@ -0,0 +1,5 @@
---
title: Page6
---
Page6

View File

@ -0,0 +1,5 @@
---
title: Page7
---
Page7

View File

@ -0,0 +1,5 @@
---
title: Page8
---
Page8

View File

@ -0,0 +1,5 @@
---
title: Page9
---
Page9

View File

@ -1 +0,0 @@
local:local-layout

View File

@ -1 +0,0 @@
vendor:vendor-layout

View File

@ -1,25 +0,0 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>default-extends</title>
<meta name="description" content="">
<meta name="author" content="">
<body>
<h1>title:default-extends</h1>
<div class="body">
<p>Should be wrapped into a div</p>
</div>
<footer>
<small>
Made with help from <a href="http://carew.github.com/" target="_blank">Carew</a>
</small>
</footer>
</body>
</html>

View File

@ -25,7 +25,7 @@ class MarkdownTest extends \PHPUnit_Framework_TestCase
public function testOnDocument($expected, $file)
{
$document = $this->createDocument($file);
$event = new CarewEvent($document);
$event = new CarewEvent(array($document));
$markdownParser = $this->getMock('Michelf\Markdown');
$markdownParser
@ -42,7 +42,7 @@ class MarkdownTest extends \PHPUnit_Framework_TestCase
{
$document = $this->createDocument('simple.md.twig');
$document->setBody('[homepage](<{{ carew.relativeRoot }}>)');
$event = new CarewEvent($document);
$event = new CarewEvent(array($document));
$extraction = new Markdown();
$extraction->onDocument($event);

View File

@ -15,7 +15,9 @@ class TwigTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$this->twigLoader = new \Twig_Loader_Array(array('pre_render_template.html.twig' => '{{ include(template_from_string(body)) }}'));
$twig = new \Twig_Environment($this->twigLoader);
$twig = new \Twig_Environment($this->twigLoader, array(
'base_template_class' => 'Carew\Twig\Template',
));
$twig->addExtension(new \Twig_Extension_StringLoader());
$twig->addGlobal('carew', new Globals());
@ -46,7 +48,9 @@ class TwigTest extends \PHPUnit_Framework_TestCase
$document->setPath('index.html');
$document->setBody($body);
$this->twigListenner->preRender(new CarewEvent($document));
$event = new CarewEvent(array($document));
$this->twigListenner->preRender($event);
$this->assertSame($expected, $document->getBody());
}
@ -57,7 +61,8 @@ class TwigTest extends \PHPUnit_Framework_TestCase
$document->setLayout(false);
$document->setBody('{{ foo }}');
$this->twigListenner->preRender(new CarewEvent($document));
$event = new CarewEvent(array($document));
$this->twigListenner->preRender($event);
$this->assertSame('{{ foo }}', $document->getBody());
}
@ -68,7 +73,7 @@ class TwigTest extends \PHPUnit_Framework_TestCase
$document->setLayout('default');
$document->setBody('{{ carew.extra.foo }}');
$event = new CarewEvent($document);
$event = new CarewEvent(array($document));
$event['globalVars'] = array('foo' => 'bar');
$this->twigListenner->preRender($event);
@ -92,7 +97,8 @@ class TwigTest extends \PHPUnit_Framework_TestCase
$document = new Document();
$document->setBody($body);
$this->twigListenner->postRender(new CarewEvent($document));
$event = new CarewEvent(array($document));
$this->twigListenner->postRender($event);
$this->assertSame($expected, $document->getBody());
}
@ -124,7 +130,7 @@ class TwigTest extends \PHPUnit_Framework_TestCase
EOL;
$this->twigLoader->setTemplate('default.html.twig', $template);
$event = new CarewEvent($document);
$event = new CarewEvent(array($document));
$event['globalVars'] = array('foo' => 'bar', 'relativeRoot' => 'should not appear');
$this->twigListenner->postRender($event);

View File

@ -0,0 +1,53 @@
<?php
namespace Carew\Tests\Twig\Node;
use Carew\Twig\Node\Pagination;
class PaginationTest extends \Twig_Test_NodeTestCase
{
public function getTests()
{
$tests = array();
$env = new \Twig_Environment(new \Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$moduleNode = $env->parse($env->tokenize('{% do range(0, 100)|sort|reverse %}'));
$node = $moduleNode->getNode('body')->getNode(0)->getNode('expr');
$node = new Pagination($node, 20);
$tests[] = array($node, <<<'EOF'
public function getNbItems(array $context)
{
$context = $this->env->mergeGlobals($context);
return count(twig_reverse_filter($this->env, twig_sort_filter(range(0, 100))));
}
public function getMaxPerPage()
{
return 20;
}
EOF
);
$env = new \Twig_Environment(new \Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$moduleNode = $env->parse($env->tokenize('{% do collection|reverse %}'));
$node = $moduleNode->getNode('body')->getNode(0)->getNode('expr');
$node = new Pagination($node, 20);
$tests[] = array($node, <<<'EOF'
public function getNbItems(array $context)
{
$context = $this->env->mergeGlobals($context);
return count(twig_reverse_filter($this->env, (isset($context["collection"]) ? $context["collection"] : null)));
}
public function getMaxPerPage()
{
return 20;
}
EOF
);
return $tests;
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace Carew\Tests\Twig\NodeVisitor;
use Carew\Twig\NodeVisitor\Paginator;
class PaginatorTest extends \PHPUnit_Framework_TestCase
{
public function getNodeVisitorAlterNothingIfNotNeededTests()
{
return array(
array('{% extends "foo" %}{% block content %}{{ parent() }}{% endblock %}'),
array('{{ collection|slice(1, 10) }}'),
array('{{ collection[2:10] }}'),
);
}
/**
* @dataProvider getNodeVisitorAlterNothingIfNotNeededTests
*/
public function testNodeVisitorAlterNothingIfNotNeeded($template)
{
$env = new \Twig_Environment(new \Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$stream = $env->parse($env->tokenize($template));
$env = new \Twig_Environment(new \Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$env->addNodeVisitor(new Paginator());
$streamWithNodeVisitorRegistred = $env->parse($env->tokenize($template));
$this->assertSame((string) $stream, (string) $streamWithNodeVisitorRegistred);
}
/**
* @expectedException Twig_Error_Syntax
* @expectedExceptionMessage Missing first argument of "paginate" function.
*/
public function testNodeVisitorThrowExceptionIfCollectionIsNotDefined()
{
$env = $this->createEnv();
$stream = $env->parse($env->tokenize('{{ paginate() }}'));
}
/**
* @expectedException Twig_Error_Syntax
* @expectedExceptionMessage Second argument of "paginate" function should be an integer.
*/
public function testNodeVisitorThrowExceptionIfMaxPerPageValueIsNotAConstant()
{
$env = $this->createEnv();
$stream = $env->parse($env->tokenize('{{ paginate(collection, maxPerPage) }}'));
}
public function testNodeVisitorAddSliceFilter()
{
$env = $this->createEnv();
$stream = $env->parse($env->tokenize('{{ paginate(collection, 10) }}'));
$nodeFilter = $stream->getNode('body')->getNode(0)->getNode('expr');
$this->assertInstanceOf('Twig_Node_Expression_Filter', $nodeFilter);
$this->assertInstanceOf('Twig_Node_Expression_Constant', $nodeFilter->getNode('filter'));
$this->assertSame('slice', $nodeFilter->getNode('filter')->getAttribute('value'));
$arguments = $nodeFilter->getNode('arguments');
$this->assertInstanceOf('Twig_Node_Expression_Name', $arguments->getNode(0));
$this->assertSame('__offset__', $arguments->getNode(0)->getAttribute('name'));
$this->assertInstanceOf('Twig_Node_Expression_Constant', $arguments->getNode(1));
$this->assertSame(10, $arguments->getNode(1)->getAttribute('value'));
$extraNode = $stream->getNode('extra');
$this->assertInstanceOf('Twig_Node_Extra', $extraNode);
$this->assertInstanceOf('Carew\Twig\Node\Pagination', $extraNode->getNode(0));
$this->assertInstanceOf('Twig_Node_Expression_Name', $extraNode->getNode(0)->getNode('node'));
$this->assertSame('collection', $extraNode->getNode(0)->getNode('node')->getAttribute('name'));
$this->assertSame(10, $extraNode->getNode(0)->getAttribute('maxPerPage'));
}
private function createEnv()
{
$env = new \Twig_Environment(new \Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
$env->addNodeVisitor(new Paginator());
$env->addFunction(new \Twig_SimpleFunction('paginate', function() { }));
$env->addGlobal('collection', range(1, 100));
return $env;
}
}

View File

@ -4,8 +4,17 @@ namespace Carew\Twig;
use Carew\Document;
use Carew\Twig\NodeVisitor\Paginator;
class CarewExtension extends \Twig_Extension
{
public function getNodeVisitors()
{
return array(
new Paginator(),
);
}
public function getFunctions()
{
return array(
@ -15,6 +24,7 @@ class CarewExtension extends \Twig_Extension
new \Twig_SimpleFunction('render_document', array($this, 'renderDocument'), array('is_safe' => array('html'), 'needs_environment' => true)),
new \Twig_SimpleFunction('render_documents', array($this, 'renderDocuments'), array('is_safe' => array('html'), 'needs_environment' => true)),
new \Twig_SimpleFunction('render_*', array($this, 'renderBlock'), array('is_safe' => array('html'), 'needs_environment' => true)),
new \Twig_SimpleFunction('paginate', function() { } ),
);
}

View File

@ -0,0 +1,53 @@
<?php
namespace Carew\Twig\Node;
class Pagination extends \Twig_Node
{
public function __construct(\Twig_Node $node, $maxPerPage)
{
parent::__construct(array('node' => $node), array('maxPerPage' => (integer) $maxPerPage));
}
public function compile(\Twig_Compiler $compiler)
{
$this->compileGetNbItems($compiler);
$this->compileMaxPerPage($compiler);
}
private function compileGetNbItems(\Twig_Compiler $compiler)
{
$compiler
->write("public function getNbItems(array \$context)\n", "{\n")
->indent()
;
$compiler->addIndentation();
$compiler->raw("\$context = \$this->env->mergeGlobals(\$context);\n\n", false);
$compiler->addIndentation();
$compiler->raw('return count(', false);
$compiler->subcompile($this->getNode('node'));
$compiler->raw(");\n");
$compiler
->outdent()
->write("}\n\n")
;
}
private function compileMaxPerPage(\Twig_Compiler $compiler)
{
$compiler
->write("public function getMaxPerPage()\n", "{\n")
->indent()
;
$compiler->write(sprintf("return %s;\n", $this->getAttribute('maxPerPage')));
$compiler
->outdent()
->write("}\n\n")
;
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Carew\Twig\NodeVisitor;
use Carew\Twig\Node\Pagination as PaginationNode;
class Paginator implements \Twig_NodeVisitorInterface
{
private $currentModule;
private $maxPerPage;
public function __construct($maxPerPage = 10)
{
$this->maxPerPage = $maxPerPage;
}
public function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
if ($node instanceof \Twig_Node_Module) {
$this->currentModule = $node;
} elseif ($node instanceof \Twig_Node_Expression_Function) {
return $this->enterPaginationFilterNode($node, $env);
}
return $node;
}
public function enterPaginationFilterNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
$name = $node->getAttribute('name');
if ('paginate' != $name) {
return $node;
}
$args = $node->getNode('arguments');
if (!$args->hasNode(0)) {
throw new \Twig_Error_Syntax('Missing first argument of "paginate" function.');
}
// extract $maxPerPage;
if ($args->hasNode(1)) {
$arg = $args->getNode(1);
if (!$arg instanceof \Twig_Node_Expression_Constant) {
throw new \Twig_Error_Syntax('Second argument of "paginate" function should be an integer.');
}
$maxPerPage = (integer) $arg->getAttribute('value');
} else {
$maxPerPage = $this->maxPerPage;
}
$nodeToPaginate = $args->getNode(0);
// Set-up the PaginationNode
$extra = $this->currentModule->getNode('extra');
$extra->setNode(0, new PaginationNode($nodeToPaginate, $maxPerPage));
// Filter the node with "|slice(offset, maxPerPage)"
$slicedNode = new \Twig_Node_Expression_Filter(
$nodeToPaginate,
new \Twig_Node_Expression_Constant('slice', 1),
new \Twig_Node(array(
new \Twig_Node_Expression_Name('__offset__', 1), //
new \Twig_Node_Expression_Constant($maxPerPage, 1),
)),
1
);
return $slicedNode;
}
public function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
if ($node instanceof \Twig_Node_Module) {
$this->currentModule = null;
}
return $node;
}
public function getPriority()
{
0;
}
}

View File

View File

@ -1 +0,0 @@
{{ include(template_from_string(body)) }}

16
Carew/Twig/Template.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace Carew\Twig;
abstract class Template extends \Twig_Template
{
public function getNbItems(array $context)
{
return null;
}
public function getMaxPerPage()
{
return null;
}
}

View File

@ -14,6 +14,12 @@
"email" : "igor@wiedler.ch"
}
],
"repositories": [
{
"type": "git",
"url": "git@github.com:lyrixx/Twig.git"
}
],
"require": {
"cocur/slugify": "0.2.*",
"michelf/php-markdown": "1.3.*@dev",
@ -24,7 +30,7 @@
"symfony/filesystem": "~2.1",
"symfony/finder": "~2.1",
"symfony/yaml": "~2.1",
"twig/twig": "~1.12"
"twig/twig": "dev-extra"
},
"require-dev": {
"symfony/dom-crawler": "~2.2",