BookGen – A kezdetek
A könyv történetéhez hozzátartozik az eszköz is, amivel létrehoztuk. Korábban nem beszéltem róla, de a könyv és az oldal tartalmának szerkesztéséhez Ãrtam egy saját eszközt, ami a BookGen nevet kapta. Ebben a cikkben errÅ‘l szeretnék mesélni.
A probléma
Adott két ember, akik könyvet szeretnének Ãrni. Azt szeretnék, hogy a munkájuk verziókezelt legyen, vagyis minden módosÃtás egyszerűen visszakövethetÅ‘ legyen. LehetÅ‘sége legyen nyelvtani és szakmai lektoroknak akár egyesével a változások átnézésére és kommentelni tudják ezeket.
A megÃrt szöveget nyomtatásban és elektronikus módon is közzé szeretnék tenni. Az Ãrók továbbá olyan eszközzel szeretnének dolgozni, ami a formázás helyett a szöveg Ãrására helyezi a hangsúlyt.
A megoldás
A problémaleÃrás elsÅ‘ bekezdésébÅ‘l adódik, hogy valami verziókezelÅ‘ rendszer kell, ami ezeket tudja. Szerencsére ezt a részt gyorsan kipipálhattuk, mivel a GitLab és a GitHub kiválóan alkalmas erre. Merge- vagy Pull request formájában lehetÅ‘séget biztosÃt a review-ra és a tooling támogatása igen kifinomult, illetve de-facto ipari szabvánnyá nÅ‘tte ki magát.
A probléma második bekezdése már kevésbé triviális, mivel olyan formátumot kell alkalmazni, ami verziókezelhetÅ‘ GIT által. Ez kizárja a DOCX és ODT formátumokat. Persze némi trükközéssel mondhatnánk, hogy ez is verziókezelhetÅ‘, mivel ezek lényegében ZIP fájlok XML tartalommal, az XML meg szöveg. Vagyis lehetne Ãrni egy olyan programot, ami a DOCX fájlt kibontja, a tartalmát GIT-ben tárolja, majd a tárolt fájlokból újra összerakja a dokumentumot.
Ezt a lehetÅ‘séget az elején gyorsan elvetettük, mivel a „What you See is what you get” tÃpusú szövegszerkesztÅ‘kben sok a „vizuális zaj”, ami a fókuszt az Ãrásról a formázásra tereli. Másik ellenérv az XML verziókezelése ellen az volt, hogy ez egy gépileg generált dokumentum, nulla vagy minimális formázással. Ezt merge-elni normálisan nem lehet, hacsak nem formázzuk emberi formára elÅ‘tte. Ez a probléma ugyanúgy fennáll a HTML esetén is, Ãgy ezt is elvetettük.
Ekkor jött az ötlet, hogy használjunk valami minimális leÃró nyelvet, amibÅ‘l tudunk generálni szépen formázott szöveget.
Az egyik opció ami ilyenkor fel szokott merülni, az a különböző TeX formátumok használata, mint a LaTeX. Ennek a használatát azért vetettük el, mert bőven több energiát igényelt volna tőlünk az elején a megtanulása, mint amivel rendelkeztünk.
Hosszú ötletelés után végül a Markdown mellett döntöttünk. A döntést megkönnyÃtette, hogy ezt mind a ketten ismertük, illetve a GitHub és a GitLab is beépÃtetten tartalmaz hozzá támogatást, valamint szinte minden komolyabb kódolásra alkalmas szerkesztÅ‘ támogatja valamilyen szinten.
A Markdown lényegében egy text fájl, minimális formázó készlettel. Az alap formázási dolgok egyszerűek:
# Ez egy cÃmsor lesz
## Ez egy 2. szintű cÃmsor
**Ez egy félkövér mondat.**
*Ez egy dőlt betűs mondat.*
* Számozatlan felsorolás
* Ismét egy lista elem
1. Ez viszont már számozott
2. És ez is.
Ezen felül természetesen sokkal több mindent tud a Markdown. A további lehetÅ‘ségeirÅ‘l egy jó Ãrás a https://github.github.com/gfm/ cÃmen található.
A végleges dokumentum generálása
A Markdown széles körű támogatása miatt kézenfekvÅ‘, hogy a végleges dokumentáció készÃtésére valami statikus weboldal készÃtÅ‘ tool-t használjunk. EbbÅ‘l választék bÅ‘ven van. A https://jamstack.org/generators/ listája alapján a cikk Ãrásának pillanatában 329 darab lehetséges választás van, mind eltérÅ‘ tudással és „hülyeségekkel”.
Persze ez nem azt jelenti, hogy mind a 329 darabot ismerni kell. Egész komoly karriert tud az ember épÃteni, ha a Hugo, Gatsby és Jeckyl rendszereket ismeri.
Anno, amikor mi programot választottunk, a GitBook-ra esett a választás, mivel ez kimondottan könyv Ãráshoz lett fejlesztve. ElsÅ‘re egy remek eszköznek tűnt, amit egyszerű használni, de mint általában lenni szokott, használat közben jönnek elÅ‘ a hülyeségek.
Szeretlek, nem szeretlek
A GitBook papÃron egy remek eszköz. Konfigurálni gyerekjáték, egészen addig, amÃg nem akar az ember semmit se módosÃtani a gyári viselkedésen. Ha mégis, akkor gyorsan rájön, hogy ez egy szeret-nem szeret hullámvasút lesz frusztrációval, haraggal és kevés sikerélménnyel.
Ez részben az NPM katasztrofális csomagkezelésnek köszönhetÅ‘, illetve a dokumentáció hiányának és akkor még nem beszéltünk a JavaScript szépségeirÅ‘l, ami 10+ év C# után olyan, mint ha egy luxus terepjáróból át kellene ülnöd egy törött féltengelyes Trabantba: nem a legjobb élmény 🙂 Persze tudom, hogy van TypeScript, meg lehetne sokkal rosszabb is, ha mondjuk WhiteSpace-ben kellene megÃrni az egészet, de rövid idÅ‘ alatt rájöttem, hogy a NodeJS és a JavaScript nem az én világom.
Egy ilyen frusztrált délutánon, mikor generálni akartam egy elÅ‘nézetet és az istenért nem ment valami NPM hülyeség miatt, akkor elhatároztam, hogy Ãrni egy ilyen toolchain-t nem lehet olyan nehéz.
Az N+1 megoldás: BookGen
Tehát adott egy frusztrált programozó, aki pusztán mérgességbÅ‘l nekiáll egy saját eszközt Ãrni. Itt felmerülhet a kérdés, hogy de miért Ãrtam sajátot, ha éppen egy picivel korábban emlÃtettem, hogy 329 darab tool van hasonló célra? A politikailag korrekt válasz erre az, hogy egyik sem volt megfelelÅ‘ a leÃrása alapján arra, amire nekem kellett volna.
Persze a BookGen se úgy indult, hogy pár nap alatt kiforrott megoldás legyen, de meglepÅ‘en könnyen ment az alapjainak az összerakása. Azt tudtam, hogy C#-ban fogom megÃrni, mivel ezzel vagyok a leginkább tisztában és a tool fejlesztése közben tudtam úgy is, hogy lesz még mit tanulnom közben, ezért nem akartam egy új nyelv megtanulásával bajlódni.
Első nehézség: Markdown HTML-be konvertálása
Szerencsére ebbÅ‘l is van több opció C# esetén, különbözÅ‘ minÅ‘ségben megÃrt könyvtárak állnak rendelkezésünkre. Viszont ha olyat szeretnénk, ami viszonylag karban is tartott, működése tesztekkel alátámasztott és nem utolsó sorban könnyen bÅ‘vÃthetÅ‘, akkor igazából csak a Markdig könyvtár marad, mint lehetséges megoldás.
A Markdig egyébként a PowerShell Core-ban is megtalálható, ami egy erÅ‘s húzó indok volt: ha a Microsoft-nak elég jó, akkor valószÃnűleg nekem is az lesz.
Második nehézség: Frontend
Igazából ez mondhatni nem volt nehézség, mivel a Bootstrap elég jól összerakott és elég jól dokumentált, valamint külön szekciója is van a W3Schools-on. Persze web fejlesztésben jártas kollégák jogosan mondják, hogy a Bootstrap bloated. Éppen ezért úgy Ãrtam meg a keretrendszert, hogy ne legyen a frontend bedrótozva. Bármilyen keretrendszer alkalmazható legyen és csak az alapértelmezett téma legyen Bootstrap alapú.
Harmadik nehézség: Webszerver
Ismét egy kakukktojás a listán, mivel ezt sem volt nehéz megoldanom. A .NET keretrendszer a 2.0 óta beépÃtve tartalmazza a HTTPListener osztályt, ami a Windows beépÃtett HTTP stack segÃtségével alkalmas webszerver létrehozásra. Persze limitációkkal. Egyik ilyen, hogy a szerver nem futhat a 80-as porton, csak akkor, ha rendszergazdaként futtatjuk vagy külön tűzfal szabályban ezt megengedjük. Ezzel együtt lehet élni.
A másik komoly limitációja szintén biztonsági okokból az, hogy külön tűzfal szabály vagy rendszergazdai jogok hiányában csak localhost-ról érhető el. Ezzel is együtt tudtam sokáig élni, mondván úgyis csak előnézeti és fejlesztési célokra kell az eszközben.
Ezt a megoldást késÅ‘bb egy teljesen saját TcpListener-re épülÅ‘ webszerverre cseréltem, amit eredetileg egy másik projekthez Ãrtam. Ha valakit érdekel, akkor ez a https://github.com/webmaster442/HttpServerFramework cÃmen fellelhetÅ‘ a GitHub-on.
Negyedik nehézség: Template rendszer
Minden fejlesztő tisztában van vele, hogy ha lehetőség van rá, akkor nem keverjük a szezont a fazonnal. Vagyis weben elkerüljük, hogy a template keveredjen a kóddal. Éppen ezért tudtam, hogy szükségem lesz egy template rendszerre. A template rendszerek legalább annyira szerteágazók, mint a static website generátorok. Van bőven választék, különböző előnyökkel és hátrányokkal együtt.
Azt tudtam, hogy nem szeretnék valami bonyolultat, de azt is tudtam, hogy egy olyan rendszert keresek, ami egyszerre kompatibilis a HTML-el és a Markdown-al. Hosszas keresgélés után egy saját rendszert implementáltam, amit a WordPress Shortcode rendszere inspirált.
Inspirált, mivel nem 1:1 megfeleltetésrÅ‘l van szó. Az én template kódjaim Ãgy néznek ki:
<!--{SriDependency file="Assets/bootstrap.min.css"}-->
Ezek a HTML szempontjából kommentek, amit mind a böngésző, mind a Markdown feldolgozó ignorál. A fenti kód éppen egy Subresource Integrity ellenőrzéssel ellátott css fájlt hivatkozik be.
A kódok megvalósÃtása a háttérben egy ITemplateShortCode interfészre illeszkednek. Ezek automatikus betöltését a Managed Extensibility Framework-el oldottam meg. A tényleges generált tartalom és a Shortcode cserét pedig reguláris kifejezések segÃtségével.
Architektúra
Az elsÅ‘ és talán legfontosabb tanulsága a történet eddigi részének az, hogy „idegállapotban” NE kezdjünk el tool-t tervezni még akkor sem, ha „pár nap és kész” felkiáltással állunk neki, mert a pár nap általában pár hét, a pár hét pedig legjobb esetben is pár hónap lesz. És nem azért, mert alábecsültük a feladatot, hanem a vérszem miatt, mert ha már ezt tudja a program, akkor még milyen jó lenne, ha mást is tudna még.

Sajnos ezt a hibát elkövettem az elején, Ãgy párszor a kód alapja átdolgozásra került. Ez nem egy kellemes feladatnak tűnhet, de jó zene (zárójelben megjegyzem, hogy valószÃnűleg az olvasók 90%-nak más a fogalma a „jó” és „zene” szavakról) mellett egész kikapcsolódó érzés refaktorálni, legalábbis nekem stressz kezelésre legalább annyira bevált, mint az Ãrás. Ilyenkor Zen van és chill.
Végeredményben a fő tervezési minta a Chain of Responsibility-t követi. Ez lehetővé tette, hogy több feldolgozó pipeline-t hozzak létre, különböző kimeneti formátumokhoz úgy, hogy a lényegi generálás nagy része újrafelhasználható legyen.
Ennek köszönhetÅ‘en a statikus HTML export funkció után a WordPress kompatibilis XML export lefejlesztése csak 3 napot vett igénybe (ebbÅ‘l a legtöbb idÅ‘ az XML fájl alapján az osztályok legyártása és tisztÃtása volt). Az EPUB export is hasonlóan egyszerű volt, bár itt nagyobb nehézséget a tesztelés és a rendes EPUB formátum leÃrás megtalálása okozta.
Nyomtatni vagy nem nyomtatni?
Természetesen szükség volt egy nyomtatásra alkalmas formátumra is. Korábbi könyv Ãrós tapasztalatom alapján tudom, hogy HTML-bÅ‘l PDF-et lehet generálni, de csak az esetek kis százalékában lesz automatikusan olyan a végeredmény, amit szeretnénk. Valahol mindig elcsúszik a tördelés. Éppen ezért a nyomtatásra szánt változat előállÃtása esetén lényegében egy CSS nélküli, minimálisan formázott HTML kerül előállÃtásra, amit bármelyik szövegszerkesztÅ‘ tud importálni, amiben utána a töréspontok és egyebek könnyen állÃthatóak.
Természetesen Ãgy munka van vele, de ez nem probléma, mivel a kéziratot a kiadó a nyomdába kerülés elÅ‘tt valamilyen kiadványszerkesztÅ‘ben úgyis újra tördeli.
Végszó
Mivel kezd olyan lenni ez a cikk, mint egy rétes tészta, ezért ezt a részt itt be is fejezném egy záró gondolattal:
Tisztában vagyok vele, hogy egy olyan eszközt alkottam, ami rajtam kÃvül valószÃnűleg kevés embernek lesz hasznos, de azért bÃzom benne, hogy más is majd annak találja, legalább egyes részeit. Éppen ezért a tool megtalálható a GitHub-on a https://github.com/webmaster442/BookGen cÃmen. A kódot MIT licenc alapján publikáltam.
Folytatása következik?…