Skip to content
This repository has been archived by the owner on Dec 3, 2023. It is now read-only.

[Statie] Add Twig #892

Merged
merged 13 commits into from
Jun 18, 2018
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"symfony/event-dispatcher": "^3.4|^4.0",
"symfony/finder": "^3.4|^4.0",
"symfony/http-kernel": "^3.4|^4.0",
"symfony/yaml": "^3.4|^4.0"
"symfony/yaml": "^3.4|^4.0",
"twig/twig": "^2.4"
},
"require-dev": {
"nette/application": "^2.4",
Expand Down Expand Up @@ -57,6 +58,7 @@
"Symplify\\Statie\\": "packages/Statie/src",
"Symplify\\Statie\\FlatWhite\\": "packages/Statie/packages/FlatWhite/src",
"Symplify\\Statie\\Generator\\": "packages/Statie/packages/Generator/src",
"Symplify\\Statie\\Twig\\": "packages/Statie/packages/Twig/src",
"Symplify\\TokenRunner\\": "packages/TokenRunner/src"
}
},
Expand All @@ -75,6 +77,7 @@
"Symplify\\Statie\\FlatWhite\\Tests\\": "packages/Statie/packages/FlatWhite/tests",
"Symplify\\Statie\\Generator\\Tests\\": "packages/Statie/packages/Generator/tests",
"Symplify\\Statie\\Tests\\": "packages/Statie/tests",
"Symplify\\Statie\\Twig\\Tests\\": "packages/Statie/packages/Twig/tests",
"Symplify\\Tests\\": "tests",
"Symplify\\TokenRunner\\Tests\\": "packages/TokenRunner/tests"
}
Expand Down
11 changes: 10 additions & 1 deletion packages/Statie/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Downloads](https://img.shields.io/packagist/dt/symplify/statie.svg?style=flat-square)](htptps://packagist.org/packages/symplify/statie)
[![Subscribe](https://img.shields.io/badge/subscribe-to--releases-green.svg?style=flat-square)](https://libraries.io/packagist/symplify%2Fstatie)

Statie takes HTML, Markdown and Latte files and generates static HTML page.
Statie takes HTML, Markdown and Twig or Latte files and generates static HTML page.

## Install

Expand Down Expand Up @@ -96,6 +96,15 @@ services:

See documentation at [www.statie.org](https://www.statie.org).

### Pick a Templating You Like

```yaml
parameters:
templating: 'latte'
# or
templating: 'twig'
```

## Who Runs on Statie?

See what Statie can do and how community uses it:
Expand Down
7 changes: 5 additions & 2 deletions packages/Statie/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"nette/finder": "^2.4",
"nette/utils": "^2.5",
"tracy/tracy": "^2.4",
"twig/twig": "^2.4",
"symfony/console": "^3.4|^4.0",
"symfony/finder": "^3.4|^4.0",
"symfony/yaml": "^3.4|^4.0",
Expand All @@ -33,14 +34,16 @@
"psr-4": {
"Symplify\\Statie\\": "src",
"Symplify\\Statie\\FlatWhite\\": "packages/FlatWhite/src",
"Symplify\\Statie\\Generator\\": "packages/Generator/src"
"Symplify\\Statie\\Generator\\": "packages/Generator/src",
"Symplify\\Statie\\Twig\\": "packages/Twig/src"
}
},
"autoload-dev": {
"psr-4": {
"Symplify\\Statie\\Tests\\": "tests",
"Symplify\\Statie\\FlatWhite\\Tests\\": "packages/FlatWhite/tests",
"Symplify\\Statie\\Generator\\Tests\\": "packages/Generator/tests"
"Symplify\\Statie\\Generator\\Tests\\": "packages/Generator/tests",
"Symplify\\Statie\\Twig\\Tests\\": "packages/Twig/tests"
}
},
"extra": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace Symplify\Statie\Twig\Exception;

use Exception;

final class InvalidTwigSyntaxException extends Exception
{
}
110 changes: 110 additions & 0 deletions packages/Statie/packages/Twig/src/Renderable/TwigFileDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php declare(strict_types=1);

namespace Symplify\Statie\Twig\Renderable;

use Nette\Utils\Strings;
use Symplify\Statie\Configuration\Configuration;
use Symplify\Statie\Contract\Renderable\FileDecoratorInterface;
use Symplify\Statie\Generator\Configuration\GeneratorElement;
use Symplify\Statie\Renderable\File\AbstractFile;
use Symplify\Statie\Twig\TwigRenderer;

final class TwigFileDecorator implements FileDecoratorInterface
{
/**
* @var Configuration
*/
private $configuration;

/**
* @var TwigRenderer
*/
private $twigRenderer;

public function __construct(Configuration $configuration, TwigRenderer $twigRenderer)
{
$this->configuration = $configuration;
$this->twigRenderer = $twigRenderer;
}

/**
* @param AbstractFile[] $files
* @return AbstractFile[]
*/
public function decorateFiles(array $files): array
{
foreach ($files as $file) {
if (! in_array($file->getExtension(), ['twig', 'md'], true)) {
continue;
}

$this->decorateFile($file);
}

return $files;
}

/**
* @param AbstractFile[] $files
* @return AbstractFile[]
*/
public function decorateFilesWithGeneratorElement(array $files, GeneratorElement $generatorElement): array
{
foreach ($files as $file) {
$this->decorateFileWithGeneratorElements($file, $generatorElement);
}

return $files;
}

private function decorateFile(AbstractFile $file): void
{
$parameters = $file->getConfiguration() + $this->configuration->getOptions() + [
'file' => $file,
];

$htmlContent = $this->twigRenderer->renderExcludingHighlightBlocks($file, $parameters);

$file->changeContent($htmlContent);
}

/**
* Higher priorities are executed first.
*/
public function getPriority(): int
{
return 700;
}

private function decorateFileWithGeneratorElements(AbstractFile $file, GeneratorElement $generatorElement): void
{
// prepare parameters
$parameters = $file->getConfiguration() + $this->configuration->getOptions() + [
$generatorElement->getVariable() => $file,
'layout' => $generatorElement->getLayout(),
];

// add layout
$this->prependLayoutToFileContent($file, $generatorElement->getLayout());

$htmlContent = $this->twigRenderer->renderExcludingHighlightBlocks($file, $parameters);

// trim "{% extends %s %}" left over
$htmlContent = Strings::replace($htmlContent, '#{% extends "[a-z]+" %}#');

$file->changeContent($htmlContent);
}

/**
* @inspiration https://github.com/sculpin/sculpin/blob/3264c087e31da2d49c9ec825fec38cae4d583d50/src/Sculpin/Bundle/TwigBundle/TwigFormatter.php#L113
*/
private function prependLayoutToFileContent(AbstractFile $file, string $layout): void
{
// wrap to block
$content = '{% block content %}' . $file->getContent() . '{% endblock %}';

$layout = sprintf('{%% extends "%s" %%}', $layout);

$file->changeContent($layout . PHP_EOL . $content);
}
}
110 changes: 110 additions & 0 deletions packages/Statie/packages/Twig/src/TwigRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php declare(strict_types=1);

namespace Symplify\Statie\Twig;

use Nette\Utils\Strings;
use Symplify\Statie\Renderable\File\AbstractFile;
use Symplify\Statie\Twig\Exception\InvalidTwigSyntaxException;
use Throwable;
use Twig\Environment;
use Twig\Loader\ArrayLoader;

final class TwigRenderer
{
/**
* @var Environment
*/
private $twigEnvironment;

/**
* @var ArrayLoader
*/
private $twigArrayLoader;

/**
* @var string
*/
private const CODE_BLOCKS_HTML_PATTERN = '#(?<code><code(?: class=\"[a-z-]+\")?>*(?:(?!<\/code>).)+<\/code>)#ms';

/**
* @var string
*/
private const PLACEHOLDER_PATTERN = '#(?<placeholder>' . self::PLACEHOLDER_PREFIX . '[0-9]+)#m';

/**
* @todo decopule this placeholder logic to own service
* @var string
*/
private const PLACEHOLDER_PREFIX = '___replace_block___';

/**
* @var int
*/
private $codePlaceholderId = 0;

/**
* @var string[]
*/
private $highlightedCodeBlocks = [];

public function __construct(Environment $twigEnvironment, ArrayLoader $twigArrayLoader)
{
$this->twigEnvironment = $twigEnvironment;
$this->twigArrayLoader = $twigArrayLoader;
}

/**
* @param mixed[] $parameters
*/
public function renderExcludingHighlightBlocks(AbstractFile $file, array $parameters): string
{
$this->reset();

// replace code with placeholder
$contentWithPlaceholders = Strings::replace(
$file->getContent(),
self::CODE_BLOCKS_HTML_PATTERN,
function (array $match): string {
$placeholder = self::PLACEHOLDER_PREFIX . ++$this->codePlaceholderId;
$this->highlightedCodeBlocks[$placeholder] = $match['code'];

return $placeholder;
}
);

// co-dependency of Latte\Engine
$this->twigArrayLoader->setTemplate($file->getFilePath(), $contentWithPlaceholders);
$renderedContentWithPlaceholders = $this->render($file, $parameters);

// replace placeholder back with code
return Strings::replace(
$renderedContentWithPlaceholders,
self::PLACEHOLDER_PATTERN,
function (array $match): string {
return $this->highlightedCodeBlocks[$match['placeholder']];
}
);
}

/**
* @param string[] $parameters
*/
private function render(AbstractFile $file, array $parameters = []): string
{
try {
return $this->twigEnvironment->render($file->getFilePath(), $parameters);
} catch (Throwable $throwable) {
throw new InvalidTwigSyntaxException(sprintf(
'Invalid Twig syntax found or missing value in "%s" file: %s',
$file->getFilePath(),
$throwable->getMessage()
));
}
}

private function reset(): void
{
$this->codePlaceholderId = 0;
$this->highlightedCodeBlocks = [];
}
}
13 changes: 13 additions & 0 deletions packages/Statie/packages/Twig/src/config/services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
_defaults:
autowire: true

Symplify\Statie\Twig\:
resource: '..'

# twig setup
Twig\Loader\ArrayLoader:

Twig\Environment:
$options:
'cache': '%kernel.cache_dir%/twig'
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{ hi }}
{{ hi|upper }}
{{ hi|escape }}

<pre><code>
{{ keepMe }}
</code></pre>


<pre><code class="language-php">
{{ ouje }}
</code></pre>


Proměnná <code>{{ NAME }}</code> má stejnou hodnotu jako File name.


<pre><code class="language-php">
namespace ${Namespace};
</code></pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Welcome
WELCOME
Welcome

<pre><code>
{{ keepMe }}
</code></pre>


<pre><code class="language-php">
{{ ouje }}
</code></pre>


Proměnná <code>{{ NAME }}</code> má stejnou hodnotu jako File name.


<pre><code class="language-php">
namespace ${Namespace};
</code></pre>