Řešení zavináčů – nechť je z Latte základ Nette šablon

- jakubkulhan
- Člen | 55
Nikde jsem nenašel, jestli se hnulo s řešením
zavináčů,
takže tohle je
můj návrh řešení.
Základní předpoklad: pokud někdo používá Nette šablony, používá LatteFilter.
Bohužel z pozice filtru (resp. makroprocesoru) toho Latte moc
nezmůže.
Jednou možností by bylo z Latte udělat 2-pass
makroprocesor – tzn. že v prvním běhu by si zapamatoval potřebné
informace o šabloně
(kde je jaký snippet, jakými podmínkami je obalen) a podle toho by si
v druhém běhu
před dané podmínky zavináče „domyslel“.
Ale lepší by podle mě bylo, aby Latte povýšil z pozice filtru na
základní stavební
kámen Nette šablon – už ně makroprocesor, nýbrž
plnohodnotný jazyk.
Místo toho, aby přechroupával textovou verzi šablony, vytvořil by si
její in-memory
reprezentaci, tu zpracoval a z ní až vygeneroval PHP kód. Compile-time
šablon by se
asi prodloužil, run-time by byl stejně rychlý v případě zavedení
optimalizací by mohl
být ještě rychlejší.
Vytvořil jsem prototyp Latte
parseru v phpegu, napsat
plnohodnotný parser, ať v phpegu, nebo ručně, by nemělo být složité.
V prototypu
parseru je s HTML tagy pracováno jako s HTML tagy, takže
<li {if $first}class="first"{/fi}> skončí chybou.
V plnohodnotném parseru by šlo
i toto řešit. Nicméně bych se přikláněl k něčemu na způsob mého
dřívějšího
smart_attrs
patche.
Aby z Latte byl opravdu jazyk, krom syntaxe by měl mít také jasně
definovanou
sémantiku. U většiny příkazů (podmínky, cykly, odkazy…) je to asi
jasné. Zaměřil
bych se na dědičnost.
Příkaz {extends …} musí být v šabloně na prvním
místě, na jakémkoli jiném místě
znamená sémantickou chybu. Nechť vše vně bloků
(ať deklarovaných pomocí {block …}, nebo atributu
n:block="…") je záporný
prostor a uvnitř bloků kladný prostor (terminologie vypůjčená z
Evoque). Extenduje-li šablona A
šablonu B,
záporný prostor A je nahrazen záporným prostorem B. Resp.
místo TOMu (Template Object Modelu) Ačka použijeme TOM Bčka,
a v něm nahradíme bloky shodnými bloky z Ačka.
Podobně by se měly všechny {include …} uzly nahradit uzly
odpovídajícími
vkládanému obsahu.
Například TOM šalony:
{* A.latte *}
some negative space
{block foo}bar{/block}
{block baz}bar{/block}
negative space at the end of file
by byl:
[
@TextNode {
text = "some negative space\n"
},
@BlockNode {
name = "foo",
children = [
@TextNode {
text = "bar"
}
]
},
@BlockNode {
name = "baz",
children = [
@TextNode {
text = "bar"
}
]
}
@TextNode {
text = "negative space at the end of file\n"
},
]
a šablony:
{* B.latte *}
{extends A.latte}
{block foo}wow{/block}
by byl:
[
@TextNode {
text = "some negative space\n"
},
@BlockNode {
name = "foo",
children = [
@TextNode {
text = "wow"
}
]
},
@BlockNode {
name = "baz",
children = [
@TextNode {
text = "bar"
}
]
}
@TextNode {
text = "negative space at the end of file\n"
},
]
Až tedy proběhne fáze starající se v šablonách o dědičnost,
dostaneme in-memory
reprezentaci šablony (TOM), která bude obsahovat jak správný záporný
prostor, tak
správný kladný prostor a nebude obsahovat žádné includy. S tímhle TOMem
se už dají
provádět další transformace a kontroly.
Při generování se může používat technika jako doteď
(if (SnippetHelper::$outputAllowed) {):
class LatteNode
{
abstract public function isAnySnippetInside();
abstract public function generatePhp();
}
class SnippetNode
{
public function isAnySnippetInside()
{
return TRUE;
}
public function generatePhp() { … }
}
class IfNode
{
/** @var string */
private $condition;
/** @var array of LatteNode */
private $ifClause;
/** @var array if LatteNode */
private $elseClause;
public function isAnySnippetInside()
{
foreach (array_merge($this->ifClause, $this->elseClause) as $node) {
if ($node->isAnySnippetInside()) {
return TRUE;
}
}
return FALSE;
}
public function generatePhp()
{
$php = "";
if ($this->isAnySnippetInside()) {
$php .= "}\n";
}
$php .= "if ({$this->condition}):\n"
if ($this->isAnySnippetInside()) {
$php .= "if (SnippetHelper::\$outputAllowed) {\n";
}
foreach ($this->ifClause as $node) {
$php .= $node->generatePhp();
}
if (!empty($this->elseClause)) {
if ($this->isAnySnippetInside()) {
$php .= "}\n";
}
$php .= "else:\n";
if ($this->isAnySnippetInside()) {
$php .= "if (SnippetHelper::\$outputAllowed) {\n";
}
foreach ($this->elseClause as $node) {
$php .= $node->generatePhp();
}
}
if ($this->isAnySnippetInside()) {
$php .= "}\n";
}
$php .= "endif;\n";
if ($this->isAnySnippetInside()) {
$php .= "if (SnippetHelper::\$outputAllowed) {\n";
}
}
}

- Honza Kuchař
- Člen | 1662
No což pak o to, Template bez Latte jsem nikdy nepoužil. (na to jsem příliš líný) Ale kdo to naimplementuje?

- jakubkulhan
- Člen | 55
Honza Kuchař napsal(a):
No což pak o to, Template bez Latte jsem nikdy nepoužil. (na to jsem příliš líný) Ale kdo to naimplementuje?
Já se na to klidně vrhnu. Z prototypu se k plnohodnotnému parseru dostanu za chvíli (pozor, selfpromo: díky skvělému phpegu :-)). Potřeboval bych vyjádření toho nepovolanějšího, jestli se to vůbec má šanci dostat do frameworku, nebo jestli zavináče začal už řešit jinak (protože na roadmapě 1.0 to je a zavázal se, že beta do 19. září vyjde…).
Mám další nápad k šablonám a jejich spolupráci se zbytkem frameworku.
Komponenty vykreslované v šablonách (formuláře např.) by mohly místo
$component->render() vrátit Latte strom s
LatteNodes, Latte by ho mohlo rovnou zkompilovat do šablony a
v dalších voláních by komponenta už jenom vracela data pro strom, který
dříve vrátila Latte (resp. zkompilovaný kód). Např. u formulářů je
stejně v plánu přepsat ConventionalRenderer do syntaxe
LatteFilteru, tak by formuláře místo toho mohly vracet Latte strom.

- Honza Kuchař
- Člen | 1662
Zní to zajímavě, myslím, že minimálně myšlenka Davida zaujme a pokud jsi ochoten to implementovat, jistě nebude proti. Problém je čas, jako vždy. :)

- Dr.Diesel
- Člen | 53
jakubkulhan napsal(a):
Mám další nápad k šablonám a jejich spolupráci se zbytkem frameworku. Komponenty vykreslované v šablonách (formuláře např.) by mohly místo
$component->render()vrátit Latte strom sLatteNodes, Latte by ho mohlo rovnou zkompilovat do šablony a v dalších voláních by komponenta už jenom vracela data pro strom, který dříve vrátila Latte (resp. zkompilovaný kód). Např. u formulářů je stejně v plánu přepsatConventionalRendererdo syntaxe LatteFilteru, tak by formuláře místo toho mohly vracet Latte strom.
U tohohle bych jen podotknul, ze si nejsem jisty, zda komponenta bude za vsech okolnosti pouzivat stejnou sablonu / podavat stejny vystup Latte stromu.
Editoval Dr.Diesel (23. 8. 2010 11:28)

- jakubkulhan
- Člen | 55
Honza Kuchař napsal(a):
Zní to zajímavě, myslím, že minimálně myšlenka Davida zaujme a pokud jsi ochoten to implementovat, jistě nebude proti. Problém je čas, jako vždy. :)
Čas neřešme, ten teď mám a věnuji ho tomu. Jen nechci, aby to dopadlo
jako u patche
pro atributy nebo FileJournalu.
Dr.Diesel napsal(a):
U tohohle bych jen podotknul, ze si nejsem jisty, zda komponenta bude za vsech okolnosti pouzivat stejnou sablonu / podavat stejny vystup Latte stromu.
To si už komponenta musí rozhodnout sama, jestli se na stránce bude
vykreslovat podle podle doby dne, či podle počasí. Latte by měl zachovávat
kompatibilitu s LatteFilterem, takže by se ve výchozím stavu volalo
$component->render(). Avšak pokud by komponenta implementovala
interface např. ILatteComponent, jenž by obsahoval dvě
metody – getNode() a getData() --, v compile-time
by Latte nahradil widget uzel stromem získaným voláním
getNode() a v run-time by akorát stromu dodal data z metody
getData().

- David Grudl
- Nette Core | 8285
Tento týden jsem strávil pročítáním všech vláken na fóru a konečně se dostal k písmenu „R“ :-)
Jakube, především díky za RFC s návrhem rešení konkrétního problému, moc si toho vážím a doufám, že tím odstartuješ vlnu. Slibuju, že se těmto věcem budu primárně věnovat a zřídil jsem na to vlastní kategorii na fóru.
Než se dostaneme k technické stránce jeden dotaz: přijdeš zítra na Poslední sobotu? Pokud ano, bylo by asi lepší vše probrat osobně, než tu vypisovat elaboráty.

- jakubkulhan
- Člen | 55
David Grudl napsal(a):
Než se dostaneme k technické stránce jeden dotaz: přijdeš zítra na Poslední sobotu? Pokud ano, bylo by asi lepší vše probrat osobně, než tu vypisovat elaboráty.
Přijdu.

- jakubkulhan
- Člen | 55
Lopata napsal(a):
Jak dopadla diskuse na Poslední sobotě?
Jestli jsem to pochopil správně, zavináčová magie je potřeba pouze
v případě, že se používají staré snippety
{snippet nazevSnippetu}, nové snippety (s dvojtečkou)
{snippet:nazevSnippetu} jsou implementovány jinak (staré se
odchytávají přímo z vykreslované stránky /zavináče jsou tam k tomu,
aby se zbytečně nevykonával kód, který není potřeba vykonávat/ a nové
jsou samostatné funkce /šablona se tedy vůbec nevykonává, vykonávají se
pouze funkce reprezentující jednotlivé snippety; problém je s tím, že
tyhle snippety jsou bez scope, takže všechny proměnné v nich použité
musí pocházet přímo z inicializace šablony/).
Navíc kompilovaný parser, který vypadne třebas z phpegu, je velký. Pro syntaxi typu Latte bude přesahovat 50KiB. A aby tvořil parser šablon osminu velikosti frameworku (předpokládám, že velikost ostatního kódu kolem šablon by zůstala stejná /leč pravděpodobnější je, že kvůli práci se stromem, by velikost ostatku šablon vzrostla/)?
Takže v 1.0 bych počítal s rozloučením se se zavináči a starými snippety spíše než s přepisem LatteFilteru.

- David Grudl
- Nette Core | 8285
Abych to doplnil, snippety implementovány jsou, ale chování není dosud dotaženo. Jestli by bylo i tak užitečné přepsat LatteFilter do Jakubova phpegu je sporné, mimojiné proto, že je to odladěná věc a jsou tu jiné věci, které by bylo fajn udělat. Pokud se do toho ale bude chtít pustit, má samozřejmě ruce volné.