Skip to content

Commit 15ddd0d

Browse files
committed
feat: have the twig generator use a template
1 parent 12cb350 commit 15ddd0d

8 files changed

Lines changed: 96 additions & 29 deletions

File tree

bin/ext-html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ $app->command('generate [generator] [source] [dest]', function (InputInterface $
3939
$sourceDefault = realpath(__DIR__ . '/../examples/template-generator/source');
4040
$destDefault = realpath(__DIR__ . '/../examples/template-generator/dest');
4141
$app
42-
->command('watch [generator] [source] [dest] [--overwrite-existing=]', function ($generator, $source, $dest, $overwriteExisting = false, InputInterface $input, OutputInterface $output) {
42+
->command('watch [generator] [source] [dest] [--overwrite-existing=]', function ($generator, $source, $dest, InputInterface $input, OutputInterface $output, $overwriteExisting = false) {
4343
(new WatchCommand(new ComponentBuilder()))($generator, $source, $dest, $overwriteExisting, $input, $output);
4444
})
4545
->descriptions('Observes a directory or file for changes, and generates templates for HTML Elements', ['generator' => 'name of the generator', 'source' => 'source path', 'dest' => 'destination path'])

docs/phpmd.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ layout: home
1010
| -------- | ------ | ------------ | ----- | ------- |
1111
| <span class="prio3">3</span> | src/Command/CreateClassCommand.php | 291 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$type`. |
1212
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 35 | [UnusedPrivateField](https://phpmd.org/rules/unusedcode.html#unusedprivatefield) | Avoid unused private fields such as `$data`. |
13-
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 215 | [UnusedPrivateMethod](https://phpmd.org/rules/unusedcode.html#unusedprivatemethod) | Avoid unused private methods such as `parseFile`. |
14-
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 215 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$generator`. |
15-
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 215 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$sourceFile`. |
16-
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 215 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$dest`. |
17-
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 215 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$io`. |
18-
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 220 | [UnusedPrivateMethod](https://phpmd.org/rules/unusedcode.html#unusedprivatemethod) | Avoid unused private methods such as `formatHtml`. |
13+
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 222 | [UnusedPrivateMethod](https://phpmd.org/rules/unusedcode.html#unusedprivatemethod) | Avoid unused private methods such as `parseFile`. |
14+
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 222 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$generator`. |
15+
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 222 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$sourceFile`. |
16+
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 222 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$dest`. |
17+
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 222 | [UnusedFormalParameter](https://phpmd.org/rules/unusedcode.html#unusedformalparameter) | Avoid unused parameters such as `$io`. |
18+
| <span class="prio3">3</span> | src/Command/WatchCommand.php | 227 | [UnusedPrivateMethod](https://phpmd.org/rules/unusedcode.html#unusedprivatemethod) | Avoid unused private methods such as `formatHtml`. |
1919
| <span class="prio3">3</span> | src/Service/ComponentBuilder.php | 50 | [UnusedLocalVariable](https://phpmd.org/rules/unusedcode.html#unusedlocalvariable) | Avoid unused local variables such as `$childElement`. |
2020

2121
Issues detected: 9
@@ -55,9 +55,9 @@ Issues detected: 15
5555
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 113 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Revolt\EventLoop` in method `__invoke`. |
5656
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 117 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Revolt\EventLoop` in method `__invoke`. |
5757
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 122 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Revolt\EventLoop` in method `__invoke`. |
58-
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 164 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Delegator\HTMLDocumentDelegator` in method `processSingleFile`. |
59-
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 220 | [ErrorControlOperator](http://phpmd.org/rules/cleancode.html#errorcontroloperator) | Remove error control operator `@` on line 226. |
60-
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 234 | [ElseExpression](https://phpmd.org/rules/cleancode.html#elseexpression) | The method createDirectory uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. |
58+
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 169 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Delegator\HTMLDocumentDelegator` in method `processSingleFile`. |
59+
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 227 | [ErrorControlOperator](http://phpmd.org/rules/cleancode.html#errorcontroloperator) | Remove error control operator `@` on line 233. |
60+
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 241 | [ElseExpression](https://phpmd.org/rules/cleancode.html#elseexpression) | The method createDirectory uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. |
6161
| <span class="prio1">1</span> | src/Delegator/HTMLDocumentDelegator.php | 87 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\DOM\HTMLDocument` in method `createEmpty`. |
6262
| <span class="prio1">1</span> | src/Delegator/HTMLDocumentDelegator.php | 92 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\DOM\HTMLDocument` in method `createFromString`. |
6363
| <span class="prio1">1</span> | src/Delegator/HTMLDocumentDelegator.php | 97 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\DOM\HTMLDocument` in method `createFromFile`. |
@@ -105,6 +105,8 @@ Issues detected: 15
105105
| <span class="prio1">1</span> | src/Element/Void/Track.php | 114 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Enum\KindEnum` in method `setKind`. |
106106
| <span class="prio1">1</span> | src/Service/ComponentBuilder.php | 54 | [ElseExpression](https://phpmd.org/rules/cleancode.html#elseexpression) | The method buildDOM uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. |
107107
| <span class="prio1">1</span> | src/Service/ComponentBuilder.php | 63 | [ElseExpression](https://phpmd.org/rules/cleancode.html#elseexpression) | The method buildDOM uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. |
108+
| <span class="prio1">1</span> | src/TemplateGenerator/TwigGenerator.php | 64 | [MissingImport](http://phpmd.org/rules/cleancode.html#MissingImport) | Missing class import via use statement (line `64`, column `30`). |
109+
| <span class="prio1">1</span> | src/TemplateGenerator/TwigGenerator.php | 85 | [MissingImport](http://phpmd.org/rules/cleancode.html#MissingImport) | Missing class import via use statement (line `85`, column `30`). |
108110
| <span class="prio1">1</span> | src/Trait/GlobalAttributesTrait.php | 189 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Enum\AutoCapitalizeEnum` in method `setAutoCapitalize`. |
109111
| <span class="prio1">1</span> | src/Trait/GlobalAttributesTrait.php | 209 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Enum\ContentEditableEnum` in method `setContentEditable`. |
110112
| <span class="prio1">1</span> | src/Trait/GlobalAttributesTrait.php | 216 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Enum\ContentEditableEnum` in method `setContentEditable`. |
@@ -120,7 +122,7 @@ Issues detected: 15
120122
| <span class="prio1">1</span> | src/Trait/GlobalAttributesTrait.php | 456 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Enum\SpellCheckEnum` in method `setSpellCheck`. |
121123
| <span class="prio1">1</span> | src/Trait/GlobalAttributesTrait.php | 459 | [StaticAccess](https://phpmd.org/rules/cleancode.html#staticaccess) | Avoid using static access to class `\Html\Enum\SpellCheckEnum` in method `setSpellCheck`. |
122124

123-
Issues detected: 75
125+
Issues detected: 77
124126
## Design
125127

126128
| Priority | File | Line | Rule | Message |
@@ -132,9 +134,9 @@ Issues detected: 75
132134
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 69 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method __invoke() contains an exit expression. |
133135
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 119 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method __invoke() contains an exit expression. |
134136
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 161 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method processSingleFile() contains an exit expression. |
135-
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 186 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method processSingleFile() contains an exit expression. |
136-
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 236 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method createDirectory() contains an exit expression. |
137+
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 193 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method processSingleFile() contains an exit expression. |
138+
| <span class="prio1">1</span> | src/Command/WatchCommand.php | 243 | [ExitExpression](https://phpmd.org/rules/design.html#exitexpression) | The method createDirectory() contains an exit expression. |
137139

138140
Issues detected: 9
139141

140-
Sat Aug 23 20:37:33 CEST 2025
142+
Sun Aug 24 11:16:16 CEST 2025

examples/template-generator/dest/teaser-example-two.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
<h3>{{ entry.title }}</h3>
33
<p>{{ entry.description }}</p>
44
<img alt="{{ entry.image.alt }}" src="{{ entry.image.url }}">
5-
<a href="{{ entry.url }}" role="button">{{ 'Read more'|t }}</a>
5+
<a class="cta" href="{{ entry.url }}" role="button">{{ 'Read more'|t }}</a>
66
</div>
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1-
<div class="teaser"><h3>{{ entry.title }}</h3><p>{{ entry.description }}</p><img src="{{ entry.image.url }}" alt="{{ entry.image.alt }}"><a role="button" href="{{ entry.url }}">{{ 'Read more'|t }}</a></div>
1+
{# teaser-example-two #}
2+
{% block teaser-example-two %}
3+
{% apply spaceless }
4+
<div class="teaser">
5+
<hgroup>
6+
<h3>{{ entry.title }}</h3>
7+
<h4>{{ entry.subtitle }}</h4>
8+
</hgroup>
9+
<p>{{ entry.description }}</p>
10+
<img alt="{{ entry.image.alt }}" src="{{ entry.image.url }}">
11+
<a href="{{ entry.url }}" role="button">{{ 'Read more'|t }}</a>
12+
</div>
13+
{% endapply %}
14+
{% endblock %}

examples/template-generator/source/teaser-twig.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ teaser-example-two:
55
div:
66
class: teaser
77
children:
8-
- h3:
9-
text: "{{ entry.title }}"
8+
- hgroup:
9+
children:
10+
- h3:
11+
text: "{{ entry.title }}"
12+
- h4:
13+
text: "{{ entry.subtitle }}"
1014
- p:
1115
text: "{{ entry.description }}"
1216
- img:

src/Command/WatchCommand.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,16 @@ private function processSingleFile(
161161
exit;
162162
}
163163

164-
$dom = HTMLDocumentDelegator::createEmpty();
165-
$dom->formatOutput = true;
166-
$dom->setRenderer($templateGenerator);
167-
$this->componentBuilder->buildComponent($dom, $data);
168-
$detsinationPath = sprintf(
164+
// Set the component handle if using TwigGenerator
165+
if (method_exists($templateGenerator, 'setComponentHandle')) {
166+
$templateGenerator->setComponentHandle($componentHandle);
167+
}
168+
169+
$document = HTMLDocumentDelegator::createEmpty();
170+
$document->formatOutput = true;
171+
$document->setRenderer($templateGenerator);
172+
$this->componentBuilder->buildComponent($document, $data);
173+
$destinationPath = sprintf(
169174
'%s/%s',
170175
$dest,
171176
str_replace(['{component}', '{extension}'], [
@@ -174,12 +179,14 @@ private function processSingleFile(
174179
], $templateGenerator->getNamePattern())
175180
);
176181

177-
if ($dom->formatOutput) {
182+
$output = (string) $document;
183+
// Only pretty-print if not templated (HTML output)
184+
if ($document->formatOutput && ! $templateGenerator->isTemplated()) {
178185
$formatter = new PrettyPrintHtml();
179-
$dom = $formatter->serializeHtml($dom->delegated, rawAttributes: false);
186+
$output = $formatter->serializeHtml($document->delegated, rawAttributes: false);
180187
}
181188

182-
file_put_contents($detsinationPath, $dom);
189+
file_put_contents($destinationPath, $output);
183190
}
184191
} catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
185192
$io->error('Failed to parse component description file. ' . $e->getMessage());
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{# <?= $componentHandle ?> #}
22
{% block <?= $componentHandle ?> %}
3-
<?= $html ?>
3+
{% apply spaceless }
4+
<?= $html ?>
5+
{% endapply %}
46
{% endblock %}

src/TemplateGenerator/TwigGenerator.php

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class TwigGenerator implements TemplateGeneratorInterface
1212
{
1313
public const TEMPLATE = 'src/Resources/templates/twig.tpl.php';
1414

15+
private string $componentHandle = 'component';
16+
1517
public function getExtension(): string
1618
{
1719
return 'twig';
@@ -49,13 +51,50 @@ public function render($elementOrDocument): ?string
4951
return null;
5052
}
5153

54+
public function setComponentHandle(string $handle): void
55+
{
56+
$this->componentHandle = $handle;
57+
}
58+
5259
public function renderElement(HTMLElementDelegatorInterface $element): string
5360
{
54-
return (string) $element->delegated->ownerDocument->saveHTML($element->delegated);
61+
$html = (string) $element->delegated->ownerDocument->saveHTML($element->delegated);
62+
// Pretty print the HTML before injecting into the template
63+
if (class_exists('Edent\\PrettyPrintHtml\\PrettyPrintHtml')) {
64+
$formatter = new \Edent\PrettyPrintHtml\PrettyPrintHtml();
65+
$html = $formatter->serializeHtml($element->delegated, rawAttributes: false);
66+
// Convert tabs to 4 spaces for indentation
67+
$html = str_replace("\t", ' ', $html);
68+
// Add 4 spaces to the beginning of every line
69+
$html = preg_replace('/^/m', ' ', $html);
70+
}
71+
$template = file_get_contents(self::TEMPLATE);
72+
$output = str_replace(
73+
['<?= $componentHandle ?>', '<?= $html ?>'],
74+
[$this->componentHandle, $html],
75+
$template
76+
);
77+
return $output;
5578
}
5679

5780
public function renderDocument(HTMLDocumentDelegatorInterface $document): string
5881
{
59-
return (string) $document->delegated->saveHTML($document->delegated);
82+
$html = (string) $document->delegated->saveHTML($document->delegated);
83+
// Pretty print the HTML before injecting into the template
84+
if (class_exists('Edent\\PrettyPrintHtml\\PrettyPrintHtml')) {
85+
$formatter = new \Edent\PrettyPrintHtml\PrettyPrintHtml();
86+
$html = $formatter->serializeHtml($document->delegated, rawAttributes: false);
87+
// Convert tabs to 4 spaces for indentation
88+
$html = str_replace("\t", ' ', $html);
89+
// Add 4 spaces to the beginning of every line
90+
$html = preg_replace('/^/m', ' ', $html);
91+
}
92+
$template = file_get_contents(self::TEMPLATE);
93+
$output = str_replace(
94+
['<?= $componentHandle ?>', '<?= $html ?>'],
95+
[$this->componentHandle, $html],
96+
$template
97+
);
98+
return $output;
6099
}
61100
}

0 commit comments

Comments
 (0)