Úpravy souborů NEON
Konfigurační soubor píšou lidé: komentáře, prázdné řádky, tabulátory nebo mezery, hexadecimální číslo,
yes místo true. Nástroj, který změní jednu volbu a soubor zapíše zpět, musí tohle všechno
zachovat. Na této stránce se dozvíte, jak:
- načíst soubor do syntaktického stromu, který si pamatuje každý jeho znak
- měnit, přidávat, odebírat a přejmenovávat položky tak, aby diff obsahoval jen vaši změnu
- najít ve stromu položky a komentáře
Knihovny pro PHP, JavaScript a
Python upravují soubory stejně, jak to popisuje specifikace. Nad kteroukoliv ukázkou si zvolte svůj jazyk a
přepnou se všechny ukázky na stránce. Python píše jména ve snake_case, takže setValue() je tam
set_value().
Načtení souboru do stromu
Ukázky upravují tento soubor config.neon:
# database connection
database:
host: localhost # the server
port: 0x0CEA
# for development only
debug: yes
services:
- App\Model\ArticleFacade
mail:
smtp: true
Nejprve ho naparsujte do syntaktického stromu. Na rozdíl od dekódování do hodnot si strom pamatuje každý znak souboru, takže jeho vypsáním dostanete soubor zpět bajt po bajtu:
use Nette\Neon\Neon;
$text = file_get_contents('config.neon');
$document = Neon::parse($text);
$document->print() === $text; // true
import { readFileSync } from 'node:fs';
import { parse } from '@nette/neon';
const text = readFileSync('config.neon', 'utf8');
const doc = parse(text);
doc.print() === text; // true
from pathlib import Path
from neon import parse
text = Path("config.neon").read_text(encoding="utf-8")
doc = parse(text)
doc.print() == text # True
Úpravy níže mění strom a výsledek uložíte tak, že výstup print() zapíšete zpět do souboru.
Změna hodnot
Položku najdete podle její cesty, tedy seznamu klíčů od kořene, stejných klíčů, jaké dává dekódování:
$document->setValue(['database', 'host'], 'db.example.com');
$document->setValue(['database', 'port'], 3307);
$document->setValue(['database', 'debug'], false);
$document->setValue(['mail', 'smtp'], true);
doc.setValue(['database', 'host'], 'db.example.com');
doc.setValue(['database', 'port'], 3307);
doc.setValue(['database', 'debug'], false);
doc.setValue(['mail', 'smtp'], true);
doc.set_value(["database", "host"], "db.example.com")
doc.set_value(["database", "port"], 3307)
doc.set_value(["database", "debug"], False)
doc.set_value(["mail", "smtp"], True)
Soubor se pak liší ve třech řádcích:
- host: localhost # the server
- port: 0x0CEA
- debug: yes
+ host: db.example.com # the server
+ port: 0x0CEB
+ debug: no
Změní se jen hodnota, mezery a komentář za ní zůstanou. Nová hodnota se zapíše ve stylu té staré: port
zůstane hexadecimální a z yes se stane no, ne false.
Poslední volání zapíše true tam, kde už true je, a soubor se vůbec nezmění: zápis
stejné hodnoty nic nemění. Formulář, který ukládá všechna svá pole, proto nepřepíše volby, na které nikdo
nesáhl.
Přidávání položek
Když poslední klíč cesty neexistuje, setValue() položku přidá. Metoda addItem() přidá
položku bez klíče, tedy položku seznamu:
$document->setValue(['database', 'user'], 'admin');
$document->addItem(['services'], 'App\Model\OrderFacade');
doc.setValue(['database', 'user'], 'admin');
doc.addItem(['services'], 'App\\Model\\OrderFacade');
doc.set_value(["database", "user"], "admin")
doc.add_item(["services"], r"App\Model\OrderFacade")
debug: yes
+ user: admin
- App\Model\ArticleFacade
+ - App\Model\OrderFacade
Nová položka přijde na konec svého rodiče a odsadí se jako její sousedé.
Neexistující klíč uprostřed cesty je chyba, aby překlep nevytvořil novou sekci. S volbou create nastavenou
na false je chybou i chybějící poslední klíč:
$document->setValue(['databse', 'user'], 'admin');
// Nette\Neon\Exception: No item 'databse' in the path databse.user
$document->setValue(['database', 'pasword'], 'secret', create: false);
// Nette\Neon\Exception: No item 'pasword' in the path database.pasword, and create is false
doc.setValue(['databse', 'user'], 'admin');
// NeonError: No item 'databse' in the path databse.user
doc.setValue(['database', 'pasword'], 'secret', { create: false });
// NeonError: No item 'pasword' in the path database.pasword, and create is false
doc.set_value(["databse", "user"], "admin")
# NeonError: No item 'databse' in the path databse.user
doc.set_value(["database", "pasword"], "secret", create=False)
# NeonError: No item 'pasword' in the path database.pasword, and create is false
Odmítnutá úprava ve stromu nezmění nic, ani svou polovinu.
Odebírání položek
$document->removeItem(['database', 'debug']);
$document->removeItem(['mail', 'smtp']);
doc.removeItem(['database', 'debug']);
doc.removeItem(['mail', 'smtp']);
doc.remove_item(["database", "debug"])
doc.remove_item(["mail", "smtp"])
port: 0x0CEA
- # for development only
- debug: yes
- mail:
- smtp: true
+ mail: {}
Položka si s sebou vezme komentář na svém řádku a komentáře přímo nad sebou, protože ji popisují. Komentář oddělený od položky prázdným řádkem je nadpis toho, co následuje, a zůstane.
Odebrání poslední položky ponechá kolekci: z mail se stane {}, protože v konfiguraci
prázdná sekce a žádná sekce často znamenají něco jiného. Kde znamenají totéž, předejte volbu nullable a
klíč zůstane bez hodnoty:
$document->removeItem(['mail', 'smtp'], nullable: true);
// mail:
doc.removeItem(['mail', 'smtp'], { nullable: true });
// mail:
doc.remove_item(["mail", "smtp"], nullable=True)
# mail:
Komentář # database connection stojí přímo nad database, takže odebrání database
odebere i jeho. Pokud ho chcete zachovat, nechte ho přesunout k tomu, co následuje:
use Nette\Neon\CommentPolicy;
$document->removeItem(['database'], CommentPolicy::MoveToNextToken);
import { CommentPolicy } from '@nette/neon';
doc.removeItem(['database'], { comments: CommentPolicy.MoveToNextToken });
from neon import CommentPolicy
doc.remove_item(["database"], comments=CommentPolicy.MOVE_TO_NEXT_TOKEN)
Přejmenování klíčů
Metoda setKey() přejmenuje klíč a jeho hodnotu nechá, jak je:
$document->setKey(['mail'], 'mailer');
doc.setKey(['mail'], 'mailer');
doc.set_key(["mail"], "mailer")
Strom
Strom se skládá z uzlů a uzly z tokenů, každý s přesným textem a se svými trivia: mezerami, konci řádků a
komentáři okolo. Metoda findItem() najde položku podle cesty:
$item = $document->findItem(['database', 'host']);
$item->getValue(); // 'localhost'
$item->getTrailingComments()[0]->text; // '# the server'
$item->value->getFirstToken()->text; // 'localhost', text přesně tak, jak je zapsaný
$item->getPosition(); // řádek 3, sloupec 2
const item = doc.findItem(['database', 'host']);
item.getValue(); // 'localhost'
item.getTrailingComments()[0].text; // '# the server'
item.value.getFirstToken().text; // 'localhost', text přesně tak, jak je zapsaný
item.getPosition(); // řádek 3, sloupec 2
item = doc.find_item(["database", "host"])
item.get_value() # 'localhost'
item.get_trailing_comments()[0].text # '# the server'
item.value.get_first_token().text # 'localhost', text přesně tak, jak je zapsaný
item.get_position() # řádek 3, sloupec 2
Uzly mají osm tříd: DocumentNode, BlockArrayNode, InlineArrayNode,
ArrayItemNode, EntityNode, EntityChainNode, StringNode a
LiteralNode. Uzel najde své potomky dané třídy metodou find(), svého předka metodou
findAncestor() a třída Traverser projde strom a cestou může uzly nahrazovat nebo odebírat. Pozice
se vždy počítá z aktuálního stromu, takže po úpravách stále platí.
Každá knihovna má spustitelné příklady, které jdou dál, třeba formulář nad konfiguračním souborem: PHP, JavaScript, Python.