From 047fcf713220a6b13b7ec2b01a5a61add509fc37 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Lukas=20H=C3=A4gele?= Date: Fri, 4 Oct 2024 11:29:49 +0200 Subject: [PATCH] work in progress to create a new markdown to html converter --- .template.md.tmp => content/.template.md.tmp | 0 bechamelsosse.md => content/bechamelsosse.md | 0 bohnenmus.md => content/bohnenmus.md | 0 .../briegelschmiere.md | 0 burger.md => content/burger.md | 0 .../chilaquiles-rancheros.md | 0 .../chili-con-carne_karin.md | 0 donauwelle.md => content/donauwelle.md | 0 .../feta-oliven-dip.md | 0 .../fettuccine-mit-trueffeloel.md | 0 .../gebratene-schweinemedaillons.md | 0 .../gebratene-spaetzle-mit-bacon.md | 0 .../gnocchi-bacon-pfanne.md | 0 .../gnocchi-hackfleisch-auflauf.md | 0 .../gnocchi-mit-speck-zucchini.md | 0 griesssuppe.md => content/griesssuppe.md | 0 gulaschsuppe.md => content/gulaschsuppe.md | 0 .../hackbaellchen-gefuellt-mit-mozzarella.md | 0 .../hackbaellchen-in-senfsosse.md | 0 .../hackbaellchenpfanne-mit-paprikagemuese.md | 0 .../hackfleischsosse.md | 0 .../hackfleischsosse_lasagne.md | 0 ...ehnchenbrust-in-salbei-thymian-marinade.md | 0 .../haehnchengeschnetzeltes-mit-spaetzle.md | 0 haferbrei.md => content/haferbrei.md | 0 .../harissa-haenchen-mit-zucchini.md | 0 .../hazans-basilikum-pesto.md | 0 hefeteig.md => content/hefeteig.md | 0 .../joghurtdressing.md | 0 .../knoblauchbaguette.md | 0 .../lachssahnesosse.md | 0 lasagne.md => content/lasagne.md | 0 .../lauchfrischkaese.md | 0 lauchgemuese.md => content/lauchgemuese.md | 0 .../linsenbolognese.md | 0 linseneintopf.md => content/linseneintopf.md | 0 marmorkuchen.md => content/marmorkuchen.md | 0 .../mediterranes-gemuese.md | 0 nusszopf.md => content/nusszopf.md | 0 ...orzo-nudel-risotto-mit-rauchigen-pilzen.md | 0 .../pfannkuchen-auflauf.md | 0 .../pfannkuchenteig.md | 0 power-muesli.md => content/power-muesli.md | 0 .../putengeschnetzeltes.md | 0 quarkoelteig.md => content/quarkoelteig.md | 0 .../radieschenfrischkaese.md | 0 .../rauchiges-schweinefilet.md | 0 .../reiseintopf-mit-hackfleisch.md | 0 .../reiseintopf-mit-haenchenbrust.md | 0 .../salatdressing-einfach.md | 0 salsa-dip.md => content/salsa-dip.md | 0 .../schinkennudeln.md | 0 ...edische-frikadellen-mit-kartoffelstampf.md | 0 .../schweinelachssteaks-mit-schupfnudeln.md | 0 .../selbstgebackenes-knoblauchbrot.md | 0 spaetzle.md => content/spaetzle.md | 0 .../spinatcannelloni.md | 0 spinatquiche.md => content/spinatquiche.md | 0 .../thailaendisches-kokos-limetten-curry.md | 0 .../thunfischsalat.md | 0 .../thunfischsosse.md | 0 tomatensosse.md => content/tomatensosse.md | 0 tortilla.md => content/tortilla.md | 0 .../toskanischer-filettopf.md | 0 .../vietnamesisches-rindfleisch.md | 0 ...hnachtliche-entenbrust-an-wintergemuese.md | 0 wurstsalat.md => content/wurstsalat.md | 0 .../zitronen-haenchenkeule-mit-tabbouleh.md | 0 .../zitronenkuchen.md | 0 .../zucchini-lasagne.md | 0 .../zucchinicremesuppe.md | 0 zuckerglasur.md => content/zuckerglasur.md | 0 src/html.c | 325 ++++++++++++++++++ src/main.c | 280 +++++++++++++++ src/util.h | 286 +++++++++++++++ 75 files changed, 891 insertions(+) rename .template.md.tmp => content/.template.md.tmp (100%) rename bechamelsosse.md => content/bechamelsosse.md (100%) rename bohnenmus.md => content/bohnenmus.md (100%) rename briegelschmiere.md => content/briegelschmiere.md (100%) rename burger.md => content/burger.md (100%) rename chilaquiles-rancheros.md => content/chilaquiles-rancheros.md (100%) rename chili-con-carne_karin.md => content/chili-con-carne_karin.md (100%) rename donauwelle.md => content/donauwelle.md (100%) rename feta-oliven-dip.md => content/feta-oliven-dip.md (100%) rename fettuccine-mit-trueffeloel.md => content/fettuccine-mit-trueffeloel.md (100%) rename gebratene-schweinemedaillons.md => content/gebratene-schweinemedaillons.md (100%) rename gebratene-spaetzle-mit-bacon.md => content/gebratene-spaetzle-mit-bacon.md (100%) rename gnocchi-bacon-pfanne.md => content/gnocchi-bacon-pfanne.md (100%) rename gnocchi-hackfleisch-auflauf.md => content/gnocchi-hackfleisch-auflauf.md (100%) rename gnocchi-mit-speck-zucchini.md => content/gnocchi-mit-speck-zucchini.md (100%) rename griesssuppe.md => content/griesssuppe.md (100%) rename gulaschsuppe.md => content/gulaschsuppe.md (100%) rename hackbaellchen-gefuellt-mit-mozzarella.md => content/hackbaellchen-gefuellt-mit-mozzarella.md (100%) rename hackbaellchen-in-senfsosse.md => content/hackbaellchen-in-senfsosse.md (100%) rename hackbaellchenpfanne-mit-paprikagemuese.md => content/hackbaellchenpfanne-mit-paprikagemuese.md (100%) rename hackfleischsosse.md => content/hackfleischsosse.md (100%) rename hackfleischsosse_lasagne.md => content/hackfleischsosse_lasagne.md (100%) rename haehnchenbrust-in-salbei-thymian-marinade.md => content/haehnchenbrust-in-salbei-thymian-marinade.md (100%) rename haehnchengeschnetzeltes-mit-spaetzle.md => content/haehnchengeschnetzeltes-mit-spaetzle.md (100%) rename haferbrei.md => content/haferbrei.md (100%) rename harissa-haenchen-mit-zucchini.md => content/harissa-haenchen-mit-zucchini.md (100%) rename hazans-basilikum-pesto.md => content/hazans-basilikum-pesto.md (100%) rename hefeteig.md => content/hefeteig.md (100%) rename joghurtdressing.md => content/joghurtdressing.md (100%) rename knoblauchbaguette.md => content/knoblauchbaguette.md (100%) rename lachssahnesosse.md => content/lachssahnesosse.md (100%) rename lasagne.md => content/lasagne.md (100%) rename lauchfrischkaese.md => content/lauchfrischkaese.md (100%) rename lauchgemuese.md => content/lauchgemuese.md (100%) rename linsenbolognese.md => content/linsenbolognese.md (100%) rename linseneintopf.md => content/linseneintopf.md (100%) rename marmorkuchen.md => content/marmorkuchen.md (100%) rename mediterranes-gemuese.md => content/mediterranes-gemuese.md (100%) rename nusszopf.md => content/nusszopf.md (100%) rename orzo-nudel-risotto-mit-rauchigen-pilzen.md => content/orzo-nudel-risotto-mit-rauchigen-pilzen.md (100%) rename pfannkuchen-auflauf.md => content/pfannkuchen-auflauf.md (100%) rename pfannkuchenteig.md => content/pfannkuchenteig.md (100%) rename power-muesli.md => content/power-muesli.md (100%) rename putengeschnetzeltes.md => content/putengeschnetzeltes.md (100%) rename quarkoelteig.md => content/quarkoelteig.md (100%) rename radieschenfrischkaese.md => content/radieschenfrischkaese.md (100%) rename rauchiges-schweinefilet.md => content/rauchiges-schweinefilet.md (100%) rename reiseintopf-mit-hackfleisch.md => content/reiseintopf-mit-hackfleisch.md (100%) rename reiseintopf-mit-haenchenbrust.md => content/reiseintopf-mit-haenchenbrust.md (100%) rename salatdressing-einfach.md => content/salatdressing-einfach.md (100%) rename salsa-dip.md => content/salsa-dip.md (100%) rename schinkennudeln.md => content/schinkennudeln.md (100%) rename schwedische-frikadellen-mit-kartoffelstampf.md => content/schwedische-frikadellen-mit-kartoffelstampf.md (100%) rename schweinelachssteaks-mit-schupfnudeln.md => content/schweinelachssteaks-mit-schupfnudeln.md (100%) rename selbstgebackenes-knoblauchbrot.md => content/selbstgebackenes-knoblauchbrot.md (100%) rename spaetzle.md => content/spaetzle.md (100%) rename spinatcannelloni.md => content/spinatcannelloni.md (100%) rename spinatquiche.md => content/spinatquiche.md (100%) rename thailaendisches-kokos-limetten-curry.md => content/thailaendisches-kokos-limetten-curry.md (100%) rename thunfischsalat.md => content/thunfischsalat.md (100%) rename thunfischsosse.md => content/thunfischsosse.md (100%) rename tomatensosse.md => content/tomatensosse.md (100%) rename tortilla.md => content/tortilla.md (100%) rename toskanischer-filettopf.md => content/toskanischer-filettopf.md (100%) rename vietnamesisches-rindfleisch.md => content/vietnamesisches-rindfleisch.md (100%) rename weihnachtliche-entenbrust-an-wintergemuese.md => content/weihnachtliche-entenbrust-an-wintergemuese.md (100%) rename wurstsalat.md => content/wurstsalat.md (100%) rename zitronen-haenchenkeule-mit-tabbouleh.md => content/zitronen-haenchenkeule-mit-tabbouleh.md (100%) rename zitronenkuchen.md => content/zitronenkuchen.md (100%) rename zucchini-lasagne.md => content/zucchini-lasagne.md (100%) rename zucchinicremesuppe.md => content/zucchinicremesuppe.md (100%) rename zuckerglasur.md => content/zuckerglasur.md (100%) create mode 100644 src/html.c create mode 100644 src/main.c create mode 100644 src/util.h diff --git a/.template.md.tmp b/content/.template.md.tmp similarity index 100% rename from .template.md.tmp rename to content/.template.md.tmp diff --git a/bechamelsosse.md b/content/bechamelsosse.md similarity index 100% rename from bechamelsosse.md rename to content/bechamelsosse.md diff --git a/bohnenmus.md b/content/bohnenmus.md similarity index 100% rename from bohnenmus.md rename to content/bohnenmus.md diff --git a/briegelschmiere.md b/content/briegelschmiere.md similarity index 100% rename from briegelschmiere.md rename to content/briegelschmiere.md diff --git a/burger.md b/content/burger.md similarity index 100% rename from burger.md rename to content/burger.md diff --git a/chilaquiles-rancheros.md b/content/chilaquiles-rancheros.md similarity index 100% rename from chilaquiles-rancheros.md rename to content/chilaquiles-rancheros.md diff --git a/chili-con-carne_karin.md b/content/chili-con-carne_karin.md similarity index 100% rename from chili-con-carne_karin.md rename to content/chili-con-carne_karin.md diff --git a/donauwelle.md b/content/donauwelle.md similarity index 100% rename from donauwelle.md rename to content/donauwelle.md diff --git a/feta-oliven-dip.md b/content/feta-oliven-dip.md similarity index 100% rename from feta-oliven-dip.md rename to content/feta-oliven-dip.md diff --git a/fettuccine-mit-trueffeloel.md b/content/fettuccine-mit-trueffeloel.md similarity index 100% rename from fettuccine-mit-trueffeloel.md rename to content/fettuccine-mit-trueffeloel.md diff --git a/gebratene-schweinemedaillons.md b/content/gebratene-schweinemedaillons.md similarity index 100% rename from gebratene-schweinemedaillons.md rename to content/gebratene-schweinemedaillons.md diff --git a/gebratene-spaetzle-mit-bacon.md b/content/gebratene-spaetzle-mit-bacon.md similarity index 100% rename from gebratene-spaetzle-mit-bacon.md rename to content/gebratene-spaetzle-mit-bacon.md diff --git a/gnocchi-bacon-pfanne.md b/content/gnocchi-bacon-pfanne.md similarity index 100% rename from gnocchi-bacon-pfanne.md rename to content/gnocchi-bacon-pfanne.md diff --git a/gnocchi-hackfleisch-auflauf.md b/content/gnocchi-hackfleisch-auflauf.md similarity index 100% rename from gnocchi-hackfleisch-auflauf.md rename to content/gnocchi-hackfleisch-auflauf.md diff --git a/gnocchi-mit-speck-zucchini.md b/content/gnocchi-mit-speck-zucchini.md similarity index 100% rename from gnocchi-mit-speck-zucchini.md rename to content/gnocchi-mit-speck-zucchini.md diff --git a/griesssuppe.md b/content/griesssuppe.md similarity index 100% rename from griesssuppe.md rename to content/griesssuppe.md diff --git a/gulaschsuppe.md b/content/gulaschsuppe.md similarity index 100% rename from gulaschsuppe.md rename to content/gulaschsuppe.md diff --git a/hackbaellchen-gefuellt-mit-mozzarella.md b/content/hackbaellchen-gefuellt-mit-mozzarella.md similarity index 100% rename from hackbaellchen-gefuellt-mit-mozzarella.md rename to content/hackbaellchen-gefuellt-mit-mozzarella.md diff --git a/hackbaellchen-in-senfsosse.md b/content/hackbaellchen-in-senfsosse.md similarity index 100% rename from hackbaellchen-in-senfsosse.md rename to content/hackbaellchen-in-senfsosse.md diff --git a/hackbaellchenpfanne-mit-paprikagemuese.md b/content/hackbaellchenpfanne-mit-paprikagemuese.md similarity index 100% rename from hackbaellchenpfanne-mit-paprikagemuese.md rename to content/hackbaellchenpfanne-mit-paprikagemuese.md diff --git a/hackfleischsosse.md b/content/hackfleischsosse.md similarity index 100% rename from hackfleischsosse.md rename to content/hackfleischsosse.md diff --git a/hackfleischsosse_lasagne.md b/content/hackfleischsosse_lasagne.md similarity index 100% rename from hackfleischsosse_lasagne.md rename to content/hackfleischsosse_lasagne.md diff --git a/haehnchenbrust-in-salbei-thymian-marinade.md b/content/haehnchenbrust-in-salbei-thymian-marinade.md similarity index 100% rename from haehnchenbrust-in-salbei-thymian-marinade.md rename to content/haehnchenbrust-in-salbei-thymian-marinade.md diff --git a/haehnchengeschnetzeltes-mit-spaetzle.md b/content/haehnchengeschnetzeltes-mit-spaetzle.md similarity index 100% rename from haehnchengeschnetzeltes-mit-spaetzle.md rename to content/haehnchengeschnetzeltes-mit-spaetzle.md diff --git a/haferbrei.md b/content/haferbrei.md similarity index 100% rename from haferbrei.md rename to content/haferbrei.md diff --git a/harissa-haenchen-mit-zucchini.md b/content/harissa-haenchen-mit-zucchini.md similarity index 100% rename from harissa-haenchen-mit-zucchini.md rename to content/harissa-haenchen-mit-zucchini.md diff --git a/hazans-basilikum-pesto.md b/content/hazans-basilikum-pesto.md similarity index 100% rename from hazans-basilikum-pesto.md rename to content/hazans-basilikum-pesto.md diff --git a/hefeteig.md b/content/hefeteig.md similarity index 100% rename from hefeteig.md rename to content/hefeteig.md diff --git a/joghurtdressing.md b/content/joghurtdressing.md similarity index 100% rename from joghurtdressing.md rename to content/joghurtdressing.md diff --git a/knoblauchbaguette.md b/content/knoblauchbaguette.md similarity index 100% rename from knoblauchbaguette.md rename to content/knoblauchbaguette.md diff --git a/lachssahnesosse.md b/content/lachssahnesosse.md similarity index 100% rename from lachssahnesosse.md rename to content/lachssahnesosse.md diff --git a/lasagne.md b/content/lasagne.md similarity index 100% rename from lasagne.md rename to content/lasagne.md diff --git a/lauchfrischkaese.md b/content/lauchfrischkaese.md similarity index 100% rename from lauchfrischkaese.md rename to content/lauchfrischkaese.md diff --git a/lauchgemuese.md b/content/lauchgemuese.md similarity index 100% rename from lauchgemuese.md rename to content/lauchgemuese.md diff --git a/linsenbolognese.md b/content/linsenbolognese.md similarity index 100% rename from linsenbolognese.md rename to content/linsenbolognese.md diff --git a/linseneintopf.md b/content/linseneintopf.md similarity index 100% rename from linseneintopf.md rename to content/linseneintopf.md diff --git a/marmorkuchen.md b/content/marmorkuchen.md similarity index 100% rename from marmorkuchen.md rename to content/marmorkuchen.md diff --git a/mediterranes-gemuese.md b/content/mediterranes-gemuese.md similarity index 100% rename from mediterranes-gemuese.md rename to content/mediterranes-gemuese.md diff --git a/nusszopf.md b/content/nusszopf.md similarity index 100% rename from nusszopf.md rename to content/nusszopf.md diff --git a/orzo-nudel-risotto-mit-rauchigen-pilzen.md b/content/orzo-nudel-risotto-mit-rauchigen-pilzen.md similarity index 100% rename from orzo-nudel-risotto-mit-rauchigen-pilzen.md rename to content/orzo-nudel-risotto-mit-rauchigen-pilzen.md diff --git a/pfannkuchen-auflauf.md b/content/pfannkuchen-auflauf.md similarity index 100% rename from pfannkuchen-auflauf.md rename to content/pfannkuchen-auflauf.md diff --git a/pfannkuchenteig.md b/content/pfannkuchenteig.md similarity index 100% rename from pfannkuchenteig.md rename to content/pfannkuchenteig.md diff --git a/power-muesli.md b/content/power-muesli.md similarity index 100% rename from power-muesli.md rename to content/power-muesli.md diff --git a/putengeschnetzeltes.md b/content/putengeschnetzeltes.md similarity index 100% rename from putengeschnetzeltes.md rename to content/putengeschnetzeltes.md diff --git a/quarkoelteig.md b/content/quarkoelteig.md similarity index 100% rename from quarkoelteig.md rename to content/quarkoelteig.md diff --git a/radieschenfrischkaese.md b/content/radieschenfrischkaese.md similarity index 100% rename from radieschenfrischkaese.md rename to content/radieschenfrischkaese.md diff --git a/rauchiges-schweinefilet.md b/content/rauchiges-schweinefilet.md similarity index 100% rename from rauchiges-schweinefilet.md rename to content/rauchiges-schweinefilet.md diff --git a/reiseintopf-mit-hackfleisch.md b/content/reiseintopf-mit-hackfleisch.md similarity index 100% rename from reiseintopf-mit-hackfleisch.md rename to content/reiseintopf-mit-hackfleisch.md diff --git a/reiseintopf-mit-haenchenbrust.md b/content/reiseintopf-mit-haenchenbrust.md similarity index 100% rename from reiseintopf-mit-haenchenbrust.md rename to content/reiseintopf-mit-haenchenbrust.md diff --git a/salatdressing-einfach.md b/content/salatdressing-einfach.md similarity index 100% rename from salatdressing-einfach.md rename to content/salatdressing-einfach.md diff --git a/salsa-dip.md b/content/salsa-dip.md similarity index 100% rename from salsa-dip.md rename to content/salsa-dip.md diff --git a/schinkennudeln.md b/content/schinkennudeln.md similarity index 100% rename from schinkennudeln.md rename to content/schinkennudeln.md diff --git a/schwedische-frikadellen-mit-kartoffelstampf.md b/content/schwedische-frikadellen-mit-kartoffelstampf.md similarity index 100% rename from schwedische-frikadellen-mit-kartoffelstampf.md rename to content/schwedische-frikadellen-mit-kartoffelstampf.md diff --git a/schweinelachssteaks-mit-schupfnudeln.md b/content/schweinelachssteaks-mit-schupfnudeln.md similarity index 100% rename from schweinelachssteaks-mit-schupfnudeln.md rename to content/schweinelachssteaks-mit-schupfnudeln.md diff --git a/selbstgebackenes-knoblauchbrot.md b/content/selbstgebackenes-knoblauchbrot.md similarity index 100% rename from selbstgebackenes-knoblauchbrot.md rename to content/selbstgebackenes-knoblauchbrot.md diff --git a/spaetzle.md b/content/spaetzle.md similarity index 100% rename from spaetzle.md rename to content/spaetzle.md diff --git a/spinatcannelloni.md b/content/spinatcannelloni.md similarity index 100% rename from spinatcannelloni.md rename to content/spinatcannelloni.md diff --git a/spinatquiche.md b/content/spinatquiche.md similarity index 100% rename from spinatquiche.md rename to content/spinatquiche.md diff --git a/thailaendisches-kokos-limetten-curry.md b/content/thailaendisches-kokos-limetten-curry.md similarity index 100% rename from thailaendisches-kokos-limetten-curry.md rename to content/thailaendisches-kokos-limetten-curry.md diff --git a/thunfischsalat.md b/content/thunfischsalat.md similarity index 100% rename from thunfischsalat.md rename to content/thunfischsalat.md diff --git a/thunfischsosse.md b/content/thunfischsosse.md similarity index 100% rename from thunfischsosse.md rename to content/thunfischsosse.md diff --git a/tomatensosse.md b/content/tomatensosse.md similarity index 100% rename from tomatensosse.md rename to content/tomatensosse.md diff --git a/tortilla.md b/content/tortilla.md similarity index 100% rename from tortilla.md rename to content/tortilla.md diff --git a/toskanischer-filettopf.md b/content/toskanischer-filettopf.md similarity index 100% rename from toskanischer-filettopf.md rename to content/toskanischer-filettopf.md diff --git a/vietnamesisches-rindfleisch.md b/content/vietnamesisches-rindfleisch.md similarity index 100% rename from vietnamesisches-rindfleisch.md rename to content/vietnamesisches-rindfleisch.md diff --git a/weihnachtliche-entenbrust-an-wintergemuese.md b/content/weihnachtliche-entenbrust-an-wintergemuese.md similarity index 100% rename from weihnachtliche-entenbrust-an-wintergemuese.md rename to content/weihnachtliche-entenbrust-an-wintergemuese.md diff --git a/wurstsalat.md b/content/wurstsalat.md similarity index 100% rename from wurstsalat.md rename to content/wurstsalat.md diff --git a/zitronen-haenchenkeule-mit-tabbouleh.md b/content/zitronen-haenchenkeule-mit-tabbouleh.md similarity index 100% rename from zitronen-haenchenkeule-mit-tabbouleh.md rename to content/zitronen-haenchenkeule-mit-tabbouleh.md diff --git a/zitronenkuchen.md b/content/zitronenkuchen.md similarity index 100% rename from zitronenkuchen.md rename to content/zitronenkuchen.md diff --git a/zucchini-lasagne.md b/content/zucchini-lasagne.md similarity index 100% rename from zucchini-lasagne.md rename to content/zucchini-lasagne.md diff --git a/zucchinicremesuppe.md b/content/zucchinicremesuppe.md similarity index 100% rename from zucchinicremesuppe.md rename to content/zucchinicremesuppe.md diff --git a/zuckerglasur.md b/content/zuckerglasur.md similarity index 100% rename from zuckerglasur.md rename to content/zuckerglasur.md diff --git a/src/html.c b/src/html.c new file mode 100644 index 0000000..801f0f4 --- /dev/null +++ b/src/html.c @@ -0,0 +1,325 @@ +/** + * @author + * Lukas Hägele + * + * @brief + * html handling + */ + +#include "util.h" + +#define ElementNameTable \ + X(invalid) \ + \ + X(html) \ + X(head) \ + X(meta) \ + X(title) \ + X(style) \ + X(body) \ + \ + X(h1) \ + X(h2) \ + X(h3) \ + X(h4) \ + X(h5) \ + X(h6) \ + X(p) \ + X(ul) \ + X(li) \ + \ + X(content) + +typedef enum +{ +#define X(Name_) HTML_ELEMENT_NAME_##Name_, + ElementNameTable +#undef X +} html_element_name; + +typedef struct html_attribute +{ + struct html_attribute* Next; + + str Key; + str Value; +} html_attribute; + +typedef struct html_element +{ + struct html_element* Prev; + struct html_element* Next; + struct html_element* Value; + + u8 ElementName; + union { + html_attribute* Attribute; + str Content; + }; +} html_element; + +typedef html_element html; + +typedef struct +{ + html_element Html; +} html_markdownContext; + +static void +appendElement(html_element* Sentinel, html_element* Element) +{ + /* todo: create sentinel in html_create() */ + Element->Prev = Sentinel->Prev; + Sentinel->Prev->Next = Element; + Element->Next = Sentinel; +} + +static inline html_element* +createContent(str ContentStr, arena* Arena) +{ + ContentStr = str_stripHead(ContentStr); + + html_element* Content = ARENA_PUSH_STRUCT(Arena, html_element); + { + Content->ElementName = HTML_ELEMENT_NAME_content; + Content->Content = ContentStr; + } + + return Content; +} + +static void +parseHeading(str Line, html_element* Html, arena* Arena) +{ + html_element* Heading = ARENA_PUSH_STRUCT(Arena, html_element); + + /* identify element name (depth level) */ + u8 Depth = 0u; + { + for (memory_size i = 0u; i < Line.Length; i++) + { + if (Line.Base[i] == '#') + { + Depth++; + } + else + { + break; + } + } + + u8 Offset = Depth - 1; + u8 Base = HTML_ELEMENT_NAME_h1; + Heading->ElementName = Base + Offset; + } + + /* process content of heading */ + { + str HeadingContent = Line; + { + HeadingContent.Base += Depth; + HeadingContent.Length -= Depth; + HeadingContent.Capacity -= Depth; + } + + Heading->Value = createContent(HeadingContent, Arena); + } + + /* todo: check if this is actually the sentinel! */ + html_element* Sentinel = Html; + appendElement(Sentinel, Heading); +} + +static void +parseListItem(str Line, html_element* Html, arena* Arena) +{ + html_element* ListItem = ARENA_PUSH_STRUCT(Arena, html_element); + { + ListItem->ElementName = HTML_ELEMENT_NAME_li; + } + + /* process content */ + { + str ListItemContent = Line; + { + ListItemContent.Base += 1u; + ListItemContent.Length -= 1u; + ListItemContent.Capacity -= 1u; + } + + ListItem->Value = createContent(ListItemContent, Arena); + } + + /* append to unordered list */ + { + html_element* List = 0; + html_element* PrevElement = Html->Prev; + if (PrevElement->ElementName == HTML_ELEMENT_NAME_ul) + { + List = PrevElement; + } + else + { + /* todo: allocate sentinel? */ + List = ARENA_PUSH_STRUCT(Arena, html_element); + List->ElementName = HTML_ELEMENT_NAME_ul; + } + + html_element* ListItemSentinel = List->Value; + appendElement(ListItemSentinel, ListItem); + } +} + +static void +parseParagraph(str Line, html_element* Html, arena* Arena) +{ + html_element* ParagraphItem = ARENA_PUSH_STRUCT(Arena, html_element); + { + ParagraphItem->ElementName = HTML_ELEMENT_NAME_content; + ParagraphItem->Value = createContent(Line, Arena); + } + + /* append to paragraph */ + { + html_element* Paragraph = 0; + html_element* PrevElement = Html->Prev; + + if ((str_isWhitespaceOnly(Line)) || + (PrevElement->ElementName != HTML_ELEMENT_NAME_p)) + { + Paragraph = ARENA_PUSH_STRUCT(Arena, html_element); + Paragraph->ElementName = HTML_ELEMENT_NAME_p; + } + else + { + Paragraph = PrevElement; + } + + html_element* ParagraphSentinel = Paragraph->Value; + appendElement(ParagraphSentinel, ParagraphItem); + } +} + +static void +parseLine(str Line, html_element* Html, arena* Arena) +{ + str Stripped = str_stripTail(Line); + + switch (Stripped.Base[0]) + { + case '#': + { + parseHeading(Stripped, Html, Arena); + } break; + + case '-': + case '*': + { + parseListItem(Stripped, Html, Arena); + } break; + + default: + { + parseParagraph(Stripped, Html, Arena); + } break; + } +} + +static html +html_parseMarkdown(str Source, arena* Arena) +{ + html_element Html = html_create(); + + for (str Line = str_getLine(Source); + str_isValid(Line); + Line = str_getLine(Source)) + { + parseLine(Line, &Html, Arena); + str_advance(Source, Line.Length); + } + + return Html; +} + +static inline b32 +isValidElement(html_element* Element) +{ + b32 Result = 0; + + if (Element != 0) + { + if (Element->ElementName != HTML_ELEMENT_NAME_invalid) + { + Result = 1; + } + } + + return Result; +} + +static inline b32 +isValidAttribute(html_attribute* Attribute) +{ + b32 Result = 0; + + if (Attribute != 0) + { + Result = 1; + } + + return Result; +} + +static void +serializeElement(str* Sentinel, html_element* Element) +{ + static char* ElementNames[] = + { + #define X(Name_) "#Name_", + ElementNameTable + #undef X + }; + + for (html_element* At = Element; isValidElement(At); At = At->Next) + { + if (Element->ElementName == HTML_ELEMENT_NAME_content) + { + str_append(Sentinel, Element->Content); + } + else + { + str_append(Sentinel, STR_LITERAL("<")); + str_append(Sentinel, STR_LITERAL(ElementNames[Element->ElementName])); + { + html_attribute* Attribute = Element->Attribute; + if (isValidAttribute(Attribute)) + { + str_append(Sentinel, Attribute->Key); + str_append(Sentinel, STR_LITERAL("=\"")); + str_append(Sentinel, Attribute->Value); + str_append(Sentinel, STR_LITERAL("\"")); + } + } + str_append(Sentinel, STR_LITERAL(">")); + + serializeElement(Sentinel, Element->Value); + + str_append(Sentinel, STR_LITERAL("<")); + str_append(Sentinel, STR_LITERAL(ElementNames[Element->ElementName])); + str_append(Sentinel, STR_LITERAL("\\>")); + } + } +} + +static str +html_toString(arena* Arena, html* Html) +{ + str_unbounded Unbounded = str_startUnbounded(Arena); + { + str* Line = &Unbounded.Str; + serializeElement(Line, Html); + } + str Result = str_endUnbounded(Unbounded); + return Result; +} + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b632ffb --- /dev/null +++ b/src/main.c @@ -0,0 +1,280 @@ +/** + * @author + * Lukas Hägele + * + * @brief + * static site generator for my recipe collection + * + * @todo + * - (german) unicode support + * + * - markdown parser + * - multithreaded (one thread per recipe) + * - output status from main thread? + * - front matter + * - encoding (file extension) + * - tags + * - date created + * - date changed (optional) + * + * - images (optional) + * + * - output to html directory + * - sortable table? (sort by most recent, by name, ...) + * - navigation (return to main page from recipe) + * + * - combine connected foods (lasagne) + */ + +#define _POSIX_C_SOURCE 200112L + +#include "util.h" +#include "html.c" + +#include +#include +#include +#include + +static u32 +generateHtmlFile(str Filename, html* Html) +{ + u32 Error = 0; + + enum { MAX_PATH = 1024 }; + char Path[MAX_PATH] = {0}; + + /* todo: create path as c string */ + str_toCString(Path, sizeof(Path), Filename); + + int FileDescriptor = open(Path, O_WRONLY|O_CREAT); + if (FileDescriptor == -1) + { + perror("open"); + Error = 1; + } + else + { + str HtmlContent = html_toString(Html); + + if (write(FileDescriptor, HtmlContent.Buffer, HtmlContent.Length) == -1) + { + perror("write"); + Error = 1; + } + + if (close(FileDescriptor) == -1) + { + perror("close"); + Error = 1; + } + } + + return Error; +} + +typedef struct node +{ + struct node* Next; + str Name; +} recipe, tag; + +int +main(int ArgumentCount, char** Arguments) +{ + struct + { + char* SourceDir; + char* OutputDir; + + recipe* Recipes; + tag* Tag; + + u64 RecipeCount; + arena MainArena; + } Context; + + /* handle commandline arguments */ + if (ArgumentCount != 3) + { + fprintf(stderr, "usage: converter \n"); + return -1; + } + else + { + Context.SourceDir = Arguments[1]; + Context.OutputDir = Arguments[2]; + } + + Context.MainArena = arena_create(MEGABYTES(16)); + + /* enumerate recipe files */ + { + DIR* Directory = opendir(Context.SourceDir); + if (Directory == NULL) + { + perror("opendir"); + return -1; + } + + struct dirent* Entry; + while ((Entry = readdir(Directory)) != NULL) + { + recipe* New = ARENA_PUSH_STRUCT(&Context.MainArena, recipe); + New->Name = str_fromCString(&Context.MainArena, Entry->d_name); + New->Next = Context.Recipes; + + Context.Recipes = New; + Context.RecipeCount++; + } + + if (closedir(Directory) == -1) + { + perror("closedir"); + return -1; + } + } + + /* todo: parse recipe files (multithreaded) */ + Recipe->Html = html_parseMarkdown(); + +#if 0 + /* todo: distill unique sorted tags */ + + /* sort recipes */ + { + u32 WasReordered = 0; + + /* todo: implement faster sorting */ + do + { + recipe* Previous = Recipes; + for (recipe* A = Recipes; + isValid(A) && isValid(A->Next); + A = A->Next) + { + recipe* B = A->Next; + + str AStr = A->Name; + str BStr = B->Name; + + s32 Result = 0; + + u64 CheckLength = AStr.Length; + if (AStr.Length != BStr.Length) + { + if (AStr.Length < BStr.Length) + { + CheckLength = AStr.Length; + Result = -1; + } + else + { + CheckLength = BStr.Length; + Result = 1; + } + } + + for (u64 StringIndex = 0; StringIndex < CheckLength; StringIndex) + { + u8 ALow = TO_LOWERCASE(AStr.Base[StringIndex]); + u8 BLow = TO_LOWERCASE(BStr.Base[StringIndex]); + + Result = ALow - BLow; + if (Result != 0) + { + break; + } + } + + if (Result != 0) + { + WasReordered = 1; + + if (Result > 0) + { + if (Previous == Recipes) + { + Recipes = B; + } + else + { + Previous->Next = B; + } + + A->Next = B->Next; + B->Next = A; + } + } + + Previous = A; + } + } + while (WasReordered == 1); + } +#endif + + /* generate html output */ + { + /* main page */ + { + /* todo: assemble html */ + html MainPage = html_create(); + { + html_append(MainPage, HTML_H2, "Tags"); + html_start(MainPage, HTML_UL); + for (tag* Tag = Tags; isValid(Tag); Tag = Tag->Next) + { + html_append(MainPage, HTML_LI, Tag->Name); + } + html_end(MainPage, HTML_UL); + + html_append(MainPage, HTML_H2, "Rezepte"); + html_start(MainPage, HTML_UL); + for (recipe* Recipe = Recipes; isValid(Recipe); Recipe = Recipe->Next) + { + html_append(MainPage, HTML_LI, Recipe->Name); + } + html_end(MainPage, HTML_UL); + } + + str Filename = STR_LITERAL("index.html"); + if (generateHtmlFile(Filename, MainPage) != 0) + { + fprintf(stderr, "Error while generating main page. Stopping.\n"); + return -1; + } + } + + /* detail pages */ + for (recipe* Recipe = Recipes; isValid(Recipe); Recipe = Recipe->Next) + { + /* recipes/... */ + str Filename = ...; + + if (generateHtmlFile(Filename, Recipe->Html) != 0) + { + fprintf(stderr, "Error while generating detail page. Stopping.\n"); + return -1; + } + } + + /* tag page? */ + for (tag* Tag = Tags; isValid(Tag); Tag = Tag->Next) + { + /* tags/... */ + str Filename = ...; + + if (generateHtmlFile(Filename, Tag->Html) != 0) + { + fprintf(stderr, "Error while generating tag page. Stopping.\n"); + return -1; + } + } + } + + fprintf(stdout, "Done.\n"); + + return 0; +} + diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..86c6a9c --- /dev/null +++ b/src/util.h @@ -0,0 +1,286 @@ +/** + * @author + * Lukas Hägele + * + * @brief + * utility stuff that is used in all modules + */ + +#pragma once + +#define ASSERT(Cond_) if ((Cond_) == 0) { *(volatile int*)0 = 0; } +#define INVALID_CODE_PATH ASSERT(!"invalid code path") + +#define MIN(A_, B_) (((A_) < (B_)) ? (A_) : (B_)) + +#define MEGABYTES(S_) ((S_) * 1024 * 1024) + +#include +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int32_t s32; + +typedef u32 b32; +typedef u64 memory_size; + +typedef struct +{ + u8* Base; + memory_size Length; + memory_size Capacity; +} buffer; + + +/* arena handling */ + +#include + +typedef buffer arena; + +static inline arena +arena_create(memory_size Size) +{ + arena Arena = {0}; + + void* Memory = malloc(Size); + if (Memory != NULL) + { + Arena.Base = (u8*)Memory; + Arena.Capacity = Size; + } + else + { + perror("malloc"); + } + + return Arena; +} + +static inline void* +arena_push(arena* Arena, memory_size Size, b32 Zero) +{ + ASSERT( (Arena->Length + Size) < Arena->Capacity); + + u8* Memory = Arena->Base + Arena->Length; + if (Zero != 0) + { + for (memory_size i = 0; i < Size; i++) + { + Memory[i] = 0; + } + } + + Arena->Length += Size; + + return Memory; +} + +#define ARENA_PUSH_STRUCT(Arena_, Struct_) (Struct_*)arena_push(Arena_, sizeof(Struct_) , 1) +#define ARENA_PUSH_ARRAY(Arena_, Struct_, Count_) (Struct_*)arena_push(Arena_, (sizeof(Struct_) * Count_), 1) + + +/* string handling */ + +#define TO_LOWERCASE(C_) ( ((C_ >= 'A') && (C_ <= 'Z')) ? (C_ | 0x20) : C_ ) + +typedef buffer str; + +#define STR_LOCAL(Buffer_) (str){ .Base = Buffer_, .Length = 0, .Capacity = sizeof(Buffer_) } +#define STR_LITERAL(Literal_) (str){ .Base = (u8*)Literal_, .Length = (sizeof(Literal_)-1), .Capacity = 0 } + +static inline void +str_append(str* Target, str Source) +{ + ASSERT( (Target->Length + Source.Length) < Target->Capacity); + + u8* Memory = Target->Base + Target->Length; + for (u64 i = 0u; i < Source.Length; i++) + { + Memory[i] = Source.Base[i]; + } + + Target->Length += Source.Length; +} + +static inline str +str_getLine(str Str) +{ + str Line = {0}; + + if (Str.Length > 0u) + { + Line.Base = Str.Base; + + memory_size Index = 0u; + for (memory_size i = 0u; i < Str.Length; i++) + { + if (Str.Base[i] == '\n') + { + Line.Length = i; + break; + } + } + } + + return Line; +} + +static inline void +str_advance(str Str, memory_size Increment) +{ + ASSERT( (Str.Length + Increment) < Str.Capacity ); + + Str.Length += Increment; +} + +static inline b32 +isWhitespace(char Char) +{ + b32 Result = 0u; + + if ((Char == ' ') || + (Char == '\t') || + (Char == '\n')) + { + Result = 1u; + } + + return Result; +} + +static inline str +str_stripHead(str Str) +{ + memory_size InitialLength = Str.Length; + + for (memory_size i = 0u; i < InitialLength; i++) + { + u8 Candidate = Str.Base[i]; + if (!isWhitespace(Candidate)) + { + Str.Base += i; + Str.Length -= i; + Str.Capacity -= i; + break; + } + } + + str Result = Str; + return Result; +} + +static inline str +str_stripTail(str Str) +{ + memory_size InitialLength = Str.Length; + memory_size OffsetToLast = InitialLength - 1; + + for (memory_size i = 0u; i < InitialLength; i++) + { + u8 Candidate = Str.Base[OffsetToLast - i]; + if (isWhitespace(Candidate)) + { + Str.Length--; + } + else + { + break; + } + } + + str Result = Str; + return Result; +} + +static inline b32 +str_isWhitespaceOnly(str Str) +{ + b32 Result = 1u; + + for (memory_size i = 0u; i < Str.Length; i++) + { + if (!isWhitespace(Str.Base[i])) + { + Result = 0u; + break; + } + } + + return Result; +} + +static inline b32 +str_isValid(str Str) +{ + b32 Result = 0; + + if (Str.Base != 0) + { + Result = 1; + } + + return Result; +} + + +typedef struct +{ + arena* Arena; + str Str; +} str_unbounded; + +static inline str_unbounded +str_startUnbounded(arena* Arena) +{ + str_unbounded Unbounded = + { + .Arena = Arena, + .Str = + { + .Base = Arena->Base + Arena->Length, + .Capacity = Arena->Capacity - Arena->Length + } + }; + + return Unbounded; +} + +static inline str +str_endUnbounded(str_unbounded Unbounded) +{ + arena_push(Unbounded.Arena, Unbounded.Str.Length, 0); + Unbounded.Str.Capacity = Unbounded.Str.Length; + return Unbounded.Str; +} + +static inline void +str_toCString(char* Destination, memory_size DestinationSize, str Source) +{ + ASSERT(DestinationSize > Source.Length); + + for (u64 i = 0; i < Source.Length; i++) + { + Destination[i] = Source.Base[i]; + } + + Destination[Source.Length] = '\0'; +} + +static inline str +str_fromCString(arena* Arena, char* CStr) +{ + char* Start = CStr; + + while (*CStr++ != '0'); + u64 Length = CStr - Start; + u8* Target = ARENA_PUSH_ARRAY(Arena, u8, Length); + + for (u64 i = 0u; i < Length; i++) + { + Target[i] = Start[i]; + } + + str Result = { .Base = Target, .Length = Length, .Capacity = Length }; + return Result; +} -- 2.39.5