From 36feb39d0bb52f9651d195a2d047c3a3adfe85e4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Lukas=20H=C3=A4gele?= Date: Sun, 13 Oct 2024 17:27:02 +0200 Subject: [PATCH] made progress on the frontmatter parser --- Makefile | 2 +- src/html.c | 155 ++++++++++++++++++++++++++++++++++++++++++++--------- src/main.c | 103 +++++++++++++++++++++++++++++------ src/util.h | 78 ++++++++++++++++++++++++++- 4 files changed, 296 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index c5b1cbc..2e51ad6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all: build build: mkdir -p bin - cc -Wall -Wextra -o bin/converter src/main.c + cc -Wall -Wextra -O0 -g3 -o bin/converter src/main.c convert: mkdir -p html diff --git a/src/html.c b/src/html.c index 8e42ca4..2e295ac 100644 --- a/src/html.c +++ b/src/html.c @@ -62,17 +62,32 @@ typedef struct html_element }; } html_element; +typedef struct html_tag +{ + struct html_tag* Prev; + struct html_tag* Next; + + str Content; +} html_tag; + +typedef struct +{ + html_tag* Tags; +} html_meta; + typedef struct { html_element* Root; html_element* Title; html_element* Article; + + html_meta* Meta; } html; static void appendElement(html_element* Sentinel, html_element* Element) { - /* todo: create sentinel in html_create() */ + /* todo: create sentinel in html_create() (inline and get rid of this function) */ Element->Prev = Sentinel->Prev; Sentinel->Prev->Next = Element; Element->Next = Sentinel; @@ -171,6 +186,7 @@ html_createDefault(arena* Arena) { Html->Title = Title; + /* todo: use static "nullstring"? */ html_appendContent(Title, STR_LITERAL(""), Arena); html_appendValue(Head, Title); } @@ -206,6 +222,19 @@ html_createDefault(arena* Arena) } } + html_meta* Meta = ARENA_PUSH_STRUCT(Arena, html_meta); + { + Html->Meta = Meta; + + html_tag* Sentinel = ARENA_PUSH_STRUCT(Arena, html_tag); + { + Sentinel->Next = Sentinel; + Sentinel->Prev = Sentinel; + + Meta->Tags = Sentinel; + } + } + return Html; } @@ -224,6 +253,43 @@ createContent(str ContentStr, arena* Arena) return Content; } +static inline void +insertTag(html_tag* Sentinel, str Stripped, arena* Arena) +{ + for (html_tag* Tag = Sentinel->Next; + (Tag != Sentinel) && (Tag->Next != Sentinel); + Tag = Tag->Next) + { + str Candidate = Tag->Content; + str Reference = Tag->Next->Content; + + s32 Result = str_compare(Candidate, Reference); + if (0); + else if (Result < 0) + { + html_tag* Next = Tag->Next; + + Tag->Next = Next; + Tag->Prev = Next->Prev; + + Next->Prev->Next = Tag; + Next->Prev = Tag; + + break; + } + else if (Result == 0) + { + /* tag already exists. stop insertion */ + break; + } + else if (Result > 0) + { + /* continue insertion */ + continue; + } + } +} + static void parseHeading(str Line, html_element* Html, arena* Arena) { @@ -340,26 +406,6 @@ parseParagraph(str Line, html_element* Html, arena* Arena) 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* @@ -367,13 +413,65 @@ html_parseMarkdown(str Source, arena* Arena) { html* Html = html_createDefault(Arena); - /* todo: parse front matter */ + b32 Frontmatter = 0; + b32 ExpectShebang = 0; for (str Line = str_getLine(Source); str_isValid(Line); Line = str_getLine(Source)) { - parseLine(Line, Html->Article, Arena); + str Stripped = str_stripTail(Line); + + if (!Frontmatter) + { + if (0); + else if (str_startsWith(Stripped, STR_LITERAL("---"))) + { + Frontmatter = 1; + ExpectShebang = 1; + } + else if (str_startsWith(Stripped, STR_LITERAL("#"))) + { + parseHeading(Stripped, Html->Root, Arena); + } + else if (str_startsWith(Line, STR_LITERAL("-")) || + str_startsWith(Line, STR_LITERAL("*"))) + { + parseListItem(Stripped, Html->Root, Arena); + } + else + { + parseParagraph(Stripped, Html->Root, Arena); + } + } + else + { + if (ExpectShebang) + { + ExpectShebang = 0; + /* could be used to differentiate between different frontmatter languages */ + ASSERT(str_equals(Stripped, STR_LITERAL("#!ini"))); + } + else + { + if (0); + else if (str_startsWith(Stripped, STR_LITERAL("["))) + { + ASSERT(str_equals(Stripped, STR_LITERAL("[tags]"))); + /* skip section header */ + } + else if (str_startsWith(Stripped, STR_LITERAL("---"))) + { + Frontmatter = 0; + } + else + { + html_tag* Sentinel = Html->Meta->Tags; + insertTag(Sentinel, Stripped, Arena); + } + } + } + str_advance(Source, Line.Length); } @@ -450,13 +548,22 @@ serializeElement(str* Sentinel, html_element* Element) } } +static void +html_setTitle(html* Html, str TitleStr, arena* Arena) +{ + html_element* Title = Html->Title; + { + html_appendContent(Title, TitleStr, Arena); + } +} + static str html_toString(html* Html, arena* Arena) { str_unbounded Unbounded = str_startUnbounded(Arena); { str* Line = &Unbounded.Str; - serializeElement(Line, Html); + serializeElement(Line, Html->Root); } str Result = str_endUnbounded(Unbounded); return Result; diff --git a/src/main.c b/src/main.c index c48f312..45a68f8 100644 --- a/src/main.c +++ b/src/main.c @@ -35,16 +35,39 @@ #include #include #include +#include + +typedef struct node +{ + struct node* Next; + str Name; + html* Html; +} recipe; + +static b32 +isValid_recipe(recipe* Recipe) +{ + b32 Result = 0; + + if (Recipe != NULL) + { + Result = 1; + } + + return Result; +} + +enum /* Limits */ +{ + MAX_PATH = 1024 +}; static u32 generateHtmlFile(str Filename, html* Html, arena* Arena) { 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); @@ -73,12 +96,6 @@ generateHtmlFile(str Filename, html* Html, arena* Arena) return Error; } -typedef struct node -{ - struct node* Next; - str Name; -} recipe, tag; - int main(int ArgumentCount, char** Arguments) { @@ -88,11 +105,11 @@ main(int ArgumentCount, char** Arguments) char* OutputDir; recipe* Recipes; - tag* Tags; + //tag* Tags; u64 RecipeCount; arena MainArena; - } Context; + } Context = {0}; /* handle commandline arguments */ if (ArgumentCount != 3) @@ -120,8 +137,17 @@ main(int ArgumentCount, char** Arguments) struct dirent* Entry; while ((Entry = readdir(Directory)) != NULL) { + str Name = str_fromCString(&Context.MainArena, Entry->d_name); + if (str_equals(Name, STR_LITERAL(".") ) || + str_equals(Name, STR_LITERAL("..")) || + str_equals(Name, STR_LITERAL(".template.md.tmp"))) + { + /* skip entry */ + continue; + } + recipe* New = ARENA_PUSH_STRUCT(&Context.MainArena, recipe); - New->Name = str_fromCString(&Context.MainArena, Entry->d_name); + New->Name = Name; New->Next = Context.Recipes; Context.Recipes = New; @@ -135,9 +161,55 @@ main(int ArgumentCount, char** Arguments) } } - /* todo: parse recipe files (multithreaded) */ - Recipe->Html = html_parseMarkdown(); + /* todo: parse recipe files multithreaded */ + for (recipe* Recipe = Context.Recipes; isValid_recipe(Recipe); Recipe = Recipe->Next) + { + /* copy file */ + str FileStr; + { + char Path[MAX_PATH] = {0}; + str_toCString(Path, sizeof(Path), Recipe->Name); + + int File = open(Path, O_RDONLY); + if (File == -1) + { + perror("open"); + return -1; + } + + /* read file */ + { + struct stat FileStat; + + if (stat(Path, &FileStat) == -1) + { + perror("stat"); + return -1; + } + + u8* Memory = ARENA_PUSH_ARRAY(&Context.MainArena, u8, FileStat.st_size); + if (read(File, Memory, FileStat.st_size) == -1) + { + perror("read"); + return -1; + } + + FileStr.Base = Memory; + FileStr.Length = FileStat.st_size; + FileStr.Capacity = FileStat.st_size; + } + + if (close(File) == -1) + { + perror("close"); + return -1; + } + } + + Recipe->Html = html_parseMarkdown(FileStr, &Context.MainArena); + } +#if 0 #if 0 /* todo: distill unique sorted tags */ @@ -222,7 +294,7 @@ main(int ArgumentCount, char** Arguments) html* MainPage = html_createDefault(&Context.MainArena); { /* todo: remove title from overview? */ - html_setTitle(MainPage, STR_LITERAL("Überblick")); + html_setTitle(MainPage, STR_LITERAL("Überblick"), &Context.MainArena); /* tags */ { @@ -289,6 +361,7 @@ main(int ArgumentCount, char** Arguments) } } } +#endif fprintf(stdout, "Done.\n"); diff --git a/src/util.h b/src/util.h index 86c6a9c..8b26040 100644 --- a/src/util.h +++ b/src/util.h @@ -112,7 +112,6 @@ str_getLine(str Str) { Line.Base = Str.Base; - memory_size Index = 0u; for (memory_size i = 0u; i < Str.Length; i++) { if (Str.Base[i] == '\n') @@ -193,6 +192,54 @@ str_stripTail(str Str) return Result; } +static inline b32 +str_equals(str A, str B) +{ + b32 Result = 1; + + if (A.Length != B.Length) + { + Result = 0; + } + else + { + for (memory_size i = 0u; i < A.Length; i++) + { + if (A.Base[i] != B.Base[i]) + { + Result = 0; + break; + } + } + } + + return Result; +} + +static inline b32 +str_startsWith(str A, str B) +{ + b32 Result = 1; + + if (A.Length < B.Length) + { + Result = 0; + } + else + { + for (memory_size i = 0u; i < B.Length; i++) + { + if (A.Base[i] != B.Base[i]) + { + Result = 0; + break; + } + } + } + + return Result; +} + static inline b32 str_isWhitespaceOnly(str Str) { @@ -223,6 +270,33 @@ str_isValid(str Str) return Result; } +static inline s32 +str_compare(str A, str B) +{ + s32 Result = 0; + + if (!str_equals(A, B)) + { + Result = -1; + + for (u32 i = 0u; i < A.Length; i++) + { + if (A.Base[i] > B.Base[i]) + { + break; + } + + if (B.Base[i] > A.Base[i]) + { + Result = 1; + break; + } + } + } + + return Result; +} + typedef struct { @@ -272,7 +346,7 @@ str_fromCString(arena* Arena, char* CStr) { char* Start = CStr; - while (*CStr++ != '0'); + while (*++CStr != '\0'); u64 Length = CStr - Start; u8* Target = ARENA_PUSH_ARRAY(Arena, u8, Length); -- 2.39.5