BookGen – A felhasználó szemszögéből
Az előző cikkben a BookGen keletkezésének körülményeit és az architektúráját ismertettem nagy vonalakban. A mai cikkben viszont a felhasználói oldalról közelíteném meg.
A program alapvetően egy parancssoros alkalmazás. A parancsok szintaxisa, amivel használható, az a GIT-ből merített inspirációt. Ebből adódóan a forrás Markdown fájlokat tartalmazó mappából statikus weblapot készíteni az alábbi módon lehet:
BookGen Build --action BuildWeb
Felmerülhet jogosan a kérdés, hogy ha valaki nem ismeri a programot, akkor hogyan tanulhatja meg a használatát? Erre az egyszerű válasz az, hogy úgy, mint jó 20 évvel ezelőtt ahogy tették az emberek a DOS időszakában: dokumentáció olvasásával.
Na de nem 1984-et írunk, maximum csak metaforikusan. Éppen ezért több tonna dokumentáció olvasása és fejlesztőként a megírása sem egy kellemes élmény. Emiatt a programba beépítettem a parancsok dokumentációját, így lényegében interaktívan a
BookGen Help
parancs kiadásával tájékozódni lehet a program alapvető használatáról, illetve a Help-nek célzottan megadható a kívánt parancs neve, ami az ahhoz tartozó súgót jeleníti meg. Pl:
BookGen Help Build
Ez már egy fokkal felhasználóbarátabb, de még mindig nem az igazi. A „modern” parancssori shellek támogatják az automatikus parancskiegészítést is valamilyen módon. Windows alatt a PowerShell képes ilyesmire, ha a program, amit használni szeretnénk, rendelkezik ilyen funkcióval. Természetesen ezt a funkciót is beleépítettem a programba. Viszont ez out-of-the-box nem működik, fel kell okosítani a shell profilt. Ez a következő parancs kiadásával lehetséges, ha PowerShell-ből indítottuk a programot:
BookGen InstallPsAutocomplete $profile
GUI kell vagy nem kell?
A terminál alapú programok reneszánszukat élik, legalábbis fejlesztői téren. Viszont attól, hogy valami konzol alapú, az még nem zárja ki azt, hogy rendelkezzen karakteres GUI-val. Régi motorosoknak talán ismerős lehet a Turbo Vision, ami a maga idejében egy remek eszköz volt.
Ennek egy modern, C# megfelelője a Miguel de Icaza által készített gui.cs. Aki ismerős Icaza korábbi munkáival, annak nem lesz meglepetés, hogy egyszerre zseniális és nagyon rossz minőségű ez is. Az ötlet remek, de az implementáció CRAP indexe az egekben van. Szerencsére a nézegetését megúszhatjuk, mivel NuGet csomagból is elérhető.
A gui.cs legnagyobb problémája, hogy gyárilag nem biztosít megoldást arra, hogy a UI kódtól leválasszuk az üzleti logikát. Itt persze nézőpont kérdése, hogy ez probléma-e egyáltalán, mivel egy UI könyvtárnak nem feltétlen kell, hogy célja legyen valami magasabb szintű absztrakció.
Jelen esetben az absztrakciót egy saját MVVM megoldással váltottam ki, amit a XAML inspirált. Utólag végiggondolva azonban ez egy picit ágyúval a verébre kategória lett és lehet hogy jobban jártam volna egy MVC implementációval. Nagy valószínűséggel ez a jövőben majd változni fog, de térjünk vissza a GUI használhatóságára.

A GUI leginkább egy proxyként működik a Build parancsra és az új könyv létrehozásához szolgáló parancsokhoz. Egy viszonylag új funkciója, hogy a súgó is beépítésre került.

Konfiguráció és új könyv
Az új könyv létrehozását megkezdhetjük manuálisan is, de ez nem nagy élmény, éppen ezért beépítetten tartalmaz erre is eszközt a program, amit a
BookGen Init
parancs kiadásával tudunk aktiválni. Ez konzol grafikusan lehetővé teszi, hogy létrehozzunk minden olyan fájlt, ami a könyvíráshoz kell.

A BookGen tervezésekor az elején a fő szempont az egyszerűség volt. Éppen ezért csupán 2db fő fájlból áll a konfiguráció.
Az egyik ilyen fő fájl, aminek a nevét nem lehet módosítani, az a bookgen.json fájl. A tool build közben ezt a fájl olvassa fel. Ez konfigurál minden olyan beállítást, ami a kimeneti fájlok előállításához szükséges.
A JSON-re azért esett a választásom, mivel mondhatni ipari szabvány. Ezen felül könnyen szerkeszthető és olvasható. Tudom, hogy manapság konfigurációra inkább a YAML népszerűbb meg talán felhasználóbarátabb is, de megmondom őszintén, hogy a hideg kiráz az olyan formátumoktól, amelyek különbséget tesznek a space és tab közötti tagolásban.
Egyetlen hátránya a JSON konfigurációnak, hogy a JSON szabvány szerint nem tartalmazhat kommenteket. Ez néha napján jól jönne a konfiguráció megértéséhez és szerkesztéséhez. Azonban igyekeztem minden beállításnak beszédes, jól érthető nevet adni. Ha pedig valahol tényleg elakadna a felhasználó, akkor a
BookGen confighelp
parancs kiadásával részletes információt tud kapni a beállítási lehetőségekről.
A másik fő fájl a tartalomjegyzék fájlja. Ez egy Markdown fájl, ami linkeket tartalmaz a könyvben szereplő fájlokra. Itt Markdown-ra azért esett a választásom, mivel a tartalomjegyzék könyvenként eltérő stílusú és mélységű.
Az Init parancs még létrehoz egy Visual Studio Code számára érthető tasks.json fájlt is, hacsak nem módosítjuk a beállítást. Ez leginkább egy kényelmi funkció, ugyanis az egész könyv szövege Visual Studio Code segítségével készült el.
Képfeldolgozás
A BookGen egyik olyan szolgáltatása, amire a legbüszkébb vagyok a képfeldolgozáshoz kapcsolódik és nem igen találkoztam még hasonló megoldással statikus weblap készítő eszközöknél.
A build konfiguráció során be lehet állítani kimeneti formátumoknál az alábbiakat:
- Jpeg, png, svg fájlok Webp formátumba konvertálása
- Képek átméretezése, ha a megadott maximális méretet (szélesség x magasság) átlépnék
- Képek base64 kódolt beágyazása a kimeneti HTML-be
A funkciót az ihlette, hogy a különböző kimeneti formátumok számára eltérő felbontású és minőségű képekre van szükség. Például web esetén nem szerencsés, hogy ha 40 MiB méretű PNG képeket publikálunk, viszont nyomtatás esetén meg az nem szerencsés, ha 40 KiB méretű rommá tömörített Webp képeket küldünk a nyomdába.
Az SVG átkonvertálása funkció szintén kimeneti formátum támogatás miatt került be. Ugyan az EPUB lényegében HTML fájlok összessége egy ZIP-ben, mégsem támogat rendesen SVG-t, mivel az EPUB3 még mindig XHTML alapú, ami valahol logikus is: ritkán fordul elő, hogy a HTML5 összes jósága kellene egy elektronikus könyv megjelenítéséhez.
Bővíthetőség
A legtöbb statikus weblap készítő eszköz kötött ahhoz a nyelvhez, amiben írták. Ez azt jelenti, hogy ha az eszköz JavaScript-ben készült, akkor a bővítményeket és az egyedi kiegészítőket is JavaScript-ben kell megírnunk. Ez nem a legkényelmesebb felhasználói szempontból, mivel ha nem vagyunk profik egy adott nyelven, akkor először meg kell tanulnunk a nyelvet annyira, hogy tudjunk benne alkotni, vagy hagyjuk az eszközt a fenébe és keresünk egy olyat, amit olyan nyelven írtak, amihez értünk.
Ha az utóbbit választjuk, akkor azonban sok mindent dobhatunk a kukába és lehet, hogy csak későn jövünk rá, hogy a választott eszköz mégsem lesz jó arra, amire szeretnénk.
A BookGen esetén ezt el szerettem volna kerülni. Az alap rendszer C#-ban van megírva, de lehetővé szerettem volna tenni, hogy bármilyen nyelven bővíthető legyen a template rendszere a korábban már ismertetett Shortcode-szerű rendszerrel.
Ez első hallásra lehetetlen, vagy legalábbis elég nehéz feladatnak tűnhet. Természetesen túl lehetett volna ennek a szekerét is tolni, de igyekeztem a realitás talaján maradni. Jelenleg a rendszer JavaScript, PHP és Python bővíthetőséget tartalmaz.
Ennek a kivitelezését és működését a legjobban egy példán keresztül lehet elmagyarázni. Tételezzük fel, hogy a template fájl az alábbi Shortcode-ot tartalmazza:
<!--{NodeJs file="script.js"}-->
Ebben az esetben az fog történni, hogy a program elindítja a beállításokban konfigurált NodeJs.exe fájlt, majd lefuttatja a script.js fájlt. Ami pedig amúgy a konzolra kerülne kimenetként, az a Shortcode helyére lesz behelyettesítve. A nagyobb flexibilitás miatt a könyv generálásához tartozó összes beállítást is megkapja a szkript egy natív JS objektumban.
PHP és Python esetén is hasonló a helyzet. Kérdés az lehet, hogy hogy az Istenben lesz egy C# objektumból PHP vagy Python objektum? A válasz erre igen egyszerű. Mint említettem a JSON ipari szabvány, ezért minden nyelv támogatja. Ez alól a Python és a PHP sem kivétel. Vagyis lényegében JSON-be szerializálódik az összes beállítás, ami aztán nyelvfüggő módon dekódolva lesz egy változóba. Ennek a változónak a létrehozása pedig a futtatandó script fájlnak az első sorába másolódik a tényleges futtatás előtt.
Puruttya egy megoldás, viszont működőképes és sokkal kevesebb overhead implementálással jár, mint mondjuk egy JSON-RPC megvalósítása. Természetesen a szabványosság és kompatibilitási okok miatt ha lesz időm, akkor írok egy JSON-RPC hidat is.
C# scripting
Szégyen lenne, ha egy C#-ban írt statikus weboldal generátort nem lehetne C#-ban bővíteni, szkriptezni.
Alapvetően a C# nem egy szkript nyelv, de mióta a kód Roslyn-al fordul IL kódra, azóta sok új érdekes lehetősége van az embernek. Egyik ilyen érdekes lehetőség, hogy C# kódot akár egy C# alkalmazásból tudunk fordítani. (Ilyenre példa a RoslynPad) Ezt felhasználva megalkottam a saját szkript rendszeremet, ami lényegében ugyanazt tudja, mint a NodeJs, PHP vagy Python szkript felület: bővíteni a generátor lehetőségeit.
A szkripting és a bővíthetőség tipikusan nem az a funkció volt, amire a tervezés első lépéseként gondoltam (részletekért lásd előző cikkek) és ennek meg is volt az ára: az első szkript rendszer implementálásakor a program nagy részét át kellett írnom és strukturálnom, ami bőven több időt vett igénybe, mint gondoltam. Ennek ellenére nem volt haszontalan, mivel beleástam magam a Roslyn fordító működésébe, ami a C# könyv későbbi változataihoz hasznos lesz.
Az indító
Részben a kényelem és a lustaság szülte, hogy a programhoz készítettem egy indító alkalmazást is, amivel grafikusan ki tudunk választani egy mappát ahol futtatni szeretnénk a programot. A futtatás alatt itt az értendő, hogy indít egy PowerShell munkamenetet az adott mappában úgy, hogy a korábban említett parancs kiegészítés is működik. Ha a gépre telepítve van a Windows Terminal, akkor a PowerShell munkamenet természetesen abban indul el 🙂

Jövőbeli tervek
Az egyik közel jövőbeli terv, hogy a jelenleg WPF-ben írt indító alkalmazást átírom Avalonia-ra. Elvileg az Avalonia többé-kevésbé WPF kompatibilis. Nagy előnye, hogy platformfüggetlen, illetve nagyobb közösség fejleszti, mint a lassan 10 éve csak életben tartott WPF-et.
Egy hosszabb távú tervem nem szorosan kapcsolódik a cikk eddigi tartalmához, de egy másik számomra fájó pontját kívánja javítani az írási folyamatnak. Ez nem más, mint a helyesírás ellenőrzés. Számtalan Markdown-képes szerkesztőt kipróbáltam, de valahogy mindig a Visual Studio Code mellett kötöttem ki. Egyetlen hátránya, hogy „normális”, 2GiB (sajnos nem vicc) alatt fogyasztó helyesírás ellenőrző plugin nincs hozzá.
Ez leginkább annak köszönhető, hogy a megoldások többsége ugyanazt a memória faló Hunspell kompatibilis JavaScript könyvtárat használja. A nevetséges memóriaigény mellett sokkal nagyobb probléma, hogy a használatától belassul a szerkesztő. Ez a lassulás olyan szintig tud fajulni, hogy egy gomb lenyomására kb. 5 másodperc, mire reagál a szerkesztő nagyobb fájlok esetén.
Szerencsére van megoldás a problémára, de tudtommal még senki sem csinálta meg. A Visual Studio Code tervezésekor gondoltak arra, hogy rendesen bővíthető legyen. Egyik ilyen opció az úgynevezett Language Server Protocol lehetőség.
A Language Server Protocol mögött az volt az alapötlet, hogy egy adott programozási nyelv támogatását (auto complete, syntax check, stb…) az adott nyelven lehet a leghatékonyabban megoldani. Az ötletem az, hogy írok egy language szervert Markdown fájlokhoz, ami a háttérben egy gyors Hunspell implementáció segítségével jól használható helyesírás ellenőrzést végez majd. De ez igazán a jövő zenéje. Esetlegesen ha ez a része felkeltette az érdeklődésedet, akkor tekintsd egyfajta felkérésnek a táncra 🙂