made progress on the frontmatter parser
authorLukas Hägele <lukas.haegele93@web.de>
Sun, 13 Oct 2024 15:27:02 +0000 (17:27 +0200)
committerLukas Hägele <lukas.haegele93@web.de>
Sun, 13 Oct 2024 15:27:02 +0000 (17:27 +0200)
Makefile
src/html.c
src/main.c
src/util.h

index c5b1cbcf17a5638abcf1655d90f25275d7437606..2e51ad6c1549c3562d6fc11124ee1ba6b5a46047 100644 (file)
--- 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
index 8e42ca4cc0a8d7af99d84eea43cc806b05e7b7dd..2e295ac3c941efd299f3a99b5275eff549f80255 100644 (file)
@@ -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("<untitled>"), 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;
index c48f312631add0ad8cd970fd6ba1e2311e7d942e..45a68f8f5197436c9fad45fc47f890154a3596ab 100644 (file)
 #include <fcntl.h>
 #include <stdio.h>
 #include <unistd.h>
+#include <sys/stat.h>
+
+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");
 
index 86c6a9ce79892cb58c8aa9f27c98966a5c681824..8b260401b8a0d3fd427afa2b76c7dd7a82ca5694 100644 (file)
@@ -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);