add generation of index.html
authorLukas Hägele <lukas.haegele93@web.de>
Sun, 27 Oct 2024 14:54:15 +0000 (15:54 +0100)
committerLukas Hägele <lukas.haegele93@web.de>
Sun, 27 Oct 2024 14:54:15 +0000 (15:54 +0100)
src/html.c
src/main.c
src/util.h

index 5a59ca2968de0e4ecdb7c577461165d9fd9e2132..15e7cf4bf7ea877330a19c12624e07ba16b8e6a5 100644 (file)
@@ -134,7 +134,7 @@ addAttribute(html_element* Element, str Key, str Child, arena* Arena)
 }
 
 static void
-appendElement(html_element* Sentinel, html_element* Element)
+html_appendElement(html_element* Sentinel, html_element* Element)
 {
     /* todo: create sentinel in html_create() (inline and get rid of this function) */
     Element->Prev = Sentinel->Prev;
@@ -146,7 +146,7 @@ appendElement(html_element* Sentinel, html_element* Element)
 static inline void
 html_appendChild(html_element* Target, html_element* Child)
 {
-    appendElement(Target->Child, Child);
+    html_appendElement(Target->Child, Child);
 }
 
 static inline void
@@ -158,7 +158,7 @@ html_appendContent(html_element* Target, str ContentStr, arena* Arena)
         Content->Content     = ContentStr;
     }
 
-    appendElement(Target->Child, Content);
+    html_appendElement(Target->Child, Content);
 }
 
 static html*
@@ -174,63 +174,63 @@ html_createDefault(arena* Arena)
 
         html_element* HtmlElement = html_createElement(HTML_ELEMENT_NAME_html, Arena);
         {
-            addAttribute(HtmlElement, STR_LITERAL("lang"), STR_LITERAL("'de-DE'"), Arena);
+            addAttribute(HtmlElement, STR_LITERAL("lang"), STR_LITERAL("de-DE"), Arena);
             html_appendChild(Root, HtmlElement);
-        }
-
-        html_element* Head = html_createElement(HTML_ELEMENT_NAME_head, Arena);
-        {
-            html_appendChild(Root, Head);
 
-            html_element* MetaCharset = html_createElement(HTML_ELEMENT_NAME_meta, Arena);
+            html_element* Head = html_createElement(HTML_ELEMENT_NAME_head, Arena);
             {
-                addAttribute(MetaCharset, STR_LITERAL("charset"), STR_LITERAL("'utf-8'"), Arena);
-                html_appendChild(Head, MetaCharset);
-            }
+                html_appendChild(HtmlElement, Head);
 
-            html_element* MetaViewport = html_createElement(HTML_ELEMENT_NAME_meta, Arena);
-            {
-                addAttribute(MetaViewport, STR_LITERAL("name"), STR_LITERAL("'viewport'"), Arena);
-                addAttribute(MetaViewport, STR_LITERAL("content"), STR_LITERAL("'width=device-width, initial-scale=1'"), Arena);
-                html_appendChild(Head, MetaViewport);
-            }
+                html_element* MetaCharset = html_createElement(HTML_ELEMENT_NAME_meta, Arena);
+                {
+                    addAttribute(MetaCharset, STR_LITERAL("charset"), STR_LITERAL("utf-8"), Arena);
+                    html_appendChild(Head, MetaCharset);
+                }
 
-            /* todo: add later? */
-            html_element* Title = html_createElement(HTML_ELEMENT_NAME_title, Arena);
-            {
-                Html->Title = Title;
+                html_element* MetaViewport = html_createElement(HTML_ELEMENT_NAME_meta, Arena);
+                {
+                    addAttribute(MetaViewport, STR_LITERAL("name"), STR_LITERAL("viewport"), Arena);
+                    addAttribute(MetaViewport, STR_LITERAL("content"), STR_LITERAL("width=device-width, initial-scale=1"), Arena);
+                    html_appendChild(Head, MetaViewport);
+                }
 
-                /* todo: use static "nullstring"? */
-                html_appendContent(Title, STR_LITERAL("<untitled>"), Arena);
-                html_appendChild(Head, Title);
-            }
+                /* todo: add later? */
+                html_element* Title = html_createElement(HTML_ELEMENT_NAME_title, Arena);
+                {
+                    static str Untitled = STR_LITERAL("[untitled]");
+                    Html->Title = Title;
 
-            /* todo: add style? */
-        }
+                    html_appendContent(Title, Untitled, Arena);
+                    html_appendChild(Head, Title);
+                }
 
-        html_element* Body = html_createElement(HTML_ELEMENT_NAME_body, Arena);
-        {
-            html_appendChild(Root, Body);
+                /* todo: add style? */
+            }
 
-            html_element* Header = html_createElement(HTML_ELEMENT_NAME_header, Arena);
+            html_element* Body = html_createElement(HTML_ELEMENT_NAME_body, Arena);
             {
-                html_appendChild(Body, Header);
+                html_appendChild(HtmlElement, Body);
 
-                html_element* Heading = html_createElement(HTML_ELEMENT_NAME_h1, Arena);
+                html_element* Header = html_createElement(HTML_ELEMENT_NAME_header, Arena);
                 {
-                    html_appendContent(Heading, STR_LITERAL("Meine Rezeptsammlung"), Arena);
-                    html_appendChild(Header, Heading);
-                }
-            }
+                    html_appendChild(Body, Header);
 
-            html_element* Main = html_createElement(HTML_ELEMENT_NAME_main, Arena);
-            {
-                html_appendChild(Body, Main);
+                    html_element* Heading = html_createElement(HTML_ELEMENT_NAME_h1, Arena);
+                    {
+                        html_appendContent(Heading, STR_LITERAL("Meine Rezeptsammlung"), Arena);
+                        html_appendChild(Header, Heading);
+                    }
+                }
 
-                html_element* Article = html_createElement(HTML_ELEMENT_NAME_article, Arena);
+                html_element* Main = html_createElement(HTML_ELEMENT_NAME_main, Arena);
                 {
-                    Html->Article = Article;
-                    html_appendChild(Main, Article);
+                    html_appendChild(Body, Main);
+
+                    html_element* Article = html_createElement(HTML_ELEMENT_NAME_article, Arena);
+                    {
+                        Html->Article = Article;
+                        html_appendChild(Main, Article);
+                    }
                 }
             }
         }
@@ -508,53 +508,79 @@ isValidAttribute(html_attribute* Attribute)
 }
 
 static void
-serializeElement(str* Sentinel, html_element* Element)
+serializeElement(str* Target, html_element* Parent)
 {
+#if 1
+    static str ElementNames[] =
+    {
+    #define X(Name_) { .Base = (u8*)#Name_, .Length = (sizeof(#Name_) - 1), .Capacity = 0 },
+        ElementNameTable
+    #undef X
+    };
+#else
     static char* ElementNames[] =
     {
-    #define X(Name_) "#Name_",
+    #define X(Name_) #Name_,
         ElementNameTable
     #undef X
     };
+#endif
 
-    for (html_element* At = Element; isValidElement(At); At = At->Next)
+    html_element* Sentinel = Parent->Child;
+    for (html_element* Child = Sentinel->Next;
+         Child != Sentinel;
+         Child = Child->Next)
     {
-        if (Element->ElementName == HTML_ELEMENT_NAME_content)
+        if (Child->ElementName == HTML_ELEMENT_NAME_content)
         {
-            str_append(Sentinel, Element->Content);
+            str_append(Target, Child->Content);
         }
         else
         {
-            str_append(Sentinel, STR_LITERAL("<"));
-            str_append(Sentinel, STR_LITERAL(ElementNames[Element->ElementName]));
+            str_append(Target, STR_LITERAL("<"));
+            str_append(Target, ElementNames[Child->ElementName]);
             {
-                html_attribute* Attribute = Element->Attribute;
-                if (isValidAttribute(Attribute))
+                for (html_attribute* Attribute = Child->Attribute;
+                     isValidAttribute(Attribute);
+                     Attribute = Attribute->Next)
                 {
-                    str_append(Sentinel, Attribute->Key);
-                    str_append(Sentinel, STR_LITERAL("=\""));
-                    str_append(Sentinel, Attribute->Child);
-                    str_append(Sentinel, STR_LITERAL("\""));
+                    str_append(Target, STR_LITERAL(" "));
+                    str_append(Target, Attribute->Key);
+                    str_append(Target, STR_LITERAL("=\""));
+                    str_append(Target, Attribute->Child);
+                    str_append(Target, STR_LITERAL("\""));
                 }
             }
-            str_append(Sentinel, STR_LITERAL(">"));
+            str_append(Target, STR_LITERAL(">"));
 
-            serializeElement(Sentinel, Element->Child);
+            if (Child->ElementName != HTML_ELEMENT_NAME_meta)
+            {
+                serializeElement(Target, Child);
 
-            str_append(Sentinel, STR_LITERAL("<"));
-            str_append(Sentinel, STR_LITERAL(ElementNames[Element->ElementName]));
-            str_append(Sentinel, STR_LITERAL("\\>"));
+                str_append(Target, STR_LITERAL("</"));
+                str_append(Target, ElementNames[Child->ElementName]);
+                str_append(Target, STR_LITERAL(">"));
+            }
         }
     }
 }
 
 static void
-html_setTitle(html* Html, str TitleStr, arena* Arena)
+html_setTitle(html* Html, str TitleStr)
 {
     html_element* Title = Html->Title;
-    {
-        html_appendContent(Title, TitleStr, Arena);
-    }
+    html_element* ChildSentinel = Title->Child;
+    html_element* Content = ChildSentinel->Next;
+
+    ASSERT(Content->ElementName = HTML_ELEMENT_NAME_content);
+    Content->Content = TitleStr;
+}
+
+static inline void
+html_appendArticleSection(html* Html, html_element* Section)
+{
+    html_element* Article = Html->Article;
+    html_appendChild(Article, Section);
 }
 
 static str
index ea7aa3a44f0ab3b49b9165ccafffc836db5509d4..6cf95a6c39ab278b246264baba0258a604db8f90 100644 (file)
 
 #include <sys/stat.h>
 
-typedef struct node
+typedef struct recipe
 {
-    struct node* Next;
+    struct recipe* Next;
+    struct recipe* Prev;
+
     str Name;
     html* Html;
 } recipe;
@@ -58,6 +60,89 @@ isValid_recipe(recipe* Recipe)
     return Result;
 }
 
+typedef struct tag_recipe
+{
+    struct tag_recipe* Next;
+    struct tag_recipe* Prev;
+    recipe* Recipe;
+} tag_recipe;
+
+typedef struct tag
+{
+    struct tag* Next;
+    struct tag* Prev;
+
+    tag_recipe* Recipes;
+    str Name;
+} tag;
+
+static tag_recipe*
+createTagRecipeSentinel(arena* Arena)
+{
+    tag_recipe* Sentinel = ARENA_PUSH_STRUCT(Arena, tag_recipe);
+    Sentinel->Next = Sentinel;
+    Sentinel->Prev = Sentinel;
+
+    return Sentinel;
+}
+
+static tag*
+getOrCreateTag(tag* Sentinel, str Name, arena* Arena)
+{
+    tag* Result = 0;
+
+    for (tag* GlobalTag = Sentinel->Next;
+         GlobalTag != Sentinel;
+         GlobalTag = GlobalTag->Next)
+    {
+        if (str_equals(Name, GlobalTag->Name))
+        {
+            Result = GlobalTag;
+            break;
+        }
+    }
+
+    if (Result == 0)
+    {
+        Result = ARENA_PUSH_STRUCT(Arena, tag);
+        Result->Name = Name;
+        Result->Recipes = createTagRecipeSentinel(Arena);
+
+        tag* Next = Sentinel;
+        if (Sentinel->Next != Sentinel)
+        {
+            for (tag* At = Sentinel->Next;
+                 At != Sentinel;
+                 At = At->Next)
+            {
+                if (str_compare(Result->Name, At->Name) > 0)
+                {
+                    Next = At;
+                    break;
+                }
+            }
+        }
+
+        Result->Prev = Next->Prev;
+        Result->Next = Next;
+
+        Next->Prev->Next = Result;
+        Next->Prev = Result;
+    }
+
+    return Result;
+}
+
+static inline void
+appendTerminatedDirectory(str* Target, char* Dir)
+{
+    str_appendCString(Target, Dir);
+    if (!str_endsWith(*Target, STR_LITERAL("/")))
+    {
+        str_append(Target, STR_LITERAL("/"));
+    }
+}
+
 enum /* Limits */
 {
     MAX_PATH = 1024
@@ -71,10 +156,12 @@ generateHtmlFile(str Filename, html* Html, arena* Arena)
     char Name[MAX_PATH] = {0};
     str_toCString(Name, sizeof(Name), Filename);
 
-    int FileDescriptor = open(Name, O_WRONLY|O_CREAT);
+    int Flags = O_WRONLY|O_CREAT;
+    int Mode = S_IRWXU | (S_IRGRP|S_IXGRP) | (S_IROTH|S_IXOTH);
+    int FileDescriptor = open(Name, Flags, Mode);
     if (FileDescriptor == -1)
     {
-        perror("open");
+        printError("open");
         Error = 1;
     }
     else
@@ -83,13 +170,19 @@ generateHtmlFile(str Filename, html* Html, arena* Arena)
 
         if (write(FileDescriptor, HtmlContent.Base, HtmlContent.Length) == -1)
         {
-            perror("write");
+            printError("write");
+            Error = 1;
+        }
+
+        if (ftruncate(FileDescriptor, HtmlContent.Length) == -1)
+        {
+            printError("ftruncate");
             Error = 1;
         }
 
         if (close(FileDescriptor) == -1)
         {
-            perror("close");
+            printError("close");
             Error = 1;
         }
     }
@@ -106,9 +199,8 @@ main(int ArgumentCount, char** Arguments)
         char* OutputDir;
 
         recipe* Recipes;
-        //tag*    Tags;
+        tag*    Tags;
 
-        u64 RecipeCount;
         arena MainArena;
     } Context = {0};
 
@@ -126,12 +218,23 @@ main(int ArgumentCount, char** Arguments)
 
     Context.MainArena = arena_create(MEGABYTES(16));
 
+    /* allocate recipe sentinel */
+    {
+        recipe* RecipeSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, recipe);
+        {
+            RecipeSentinel->Next = RecipeSentinel;
+            RecipeSentinel->Prev = RecipeSentinel;
+
+            Context.Recipes = RecipeSentinel;
+        }
+    }
+
     /* enumerate recipe files */
     {
         DIR* Directory = opendir(Context.SourceDir);
         if (Directory == NULL)
         {
-            perror("opendir");
+            printError("opendir");
             return -1;
         }
 
@@ -149,22 +252,50 @@ main(int ArgumentCount, char** Arguments)
             }
 
             recipe* New = ARENA_PUSH_STRUCT(&Context.MainArena, recipe);
-            New->Name = Name;
-            New->Next = Context.Recipes;
+            {
+                recipe* Sentinel = Context.Recipes;
 
-            Context.Recipes = New;
-            Context.RecipeCount++;
+                New->Name = Name;
+                New->Next = Sentinel;
+
+                Sentinel->Prev->Next = New;
+                Sentinel->Prev = New;
+            }
         }
 
         if (closedir(Directory) == -1)
         {
-            perror("closedir");
+            printError("closedir");
             return -1;
         }
     }
 
+    /* ensure existence of output directory */
+    {
+        DIR* Directory = opendir(Context.OutputDir);
+        if (Directory == NULL)
+        {
+            if (mkdir(Context.OutputDir, 0755) == -1)
+            {
+                printError("mkdir");
+                return -1;
+            }
+        }
+        else
+        {
+            if (closedir(Directory) == -1)
+            {
+                printError("closedir");
+                return -1;
+            }
+        }
+    }
+
+
     /* todo: parse recipe files multithreaded */
-    for (recipe* Recipe = Context.Recipes; isValid_recipe(Recipe); Recipe = Recipe->Next)
+    for (recipe* Recipe = Context.Recipes->Next;
+         Recipe != Context.Recipes;
+         Recipe = Recipe->Next)
     {
         /* copy file */
         str FileStr;
@@ -174,17 +305,18 @@ main(int ArgumentCount, char** Arguments)
                 arena TempArena = Context.MainArena;
                 str_unbounded uPath = str_startUnbounded(&TempArena);
                 {
-                    str_appendCString(&uPath.Str, Context.SourceDir);
+                    appendTerminatedDirectory(&uPath.Str, Context.SourceDir);
                     str_append(&uPath.Str, Recipe->Name);
                 }
                 str PathStr = str_endUnbounded(uPath);
+
                 str_toCString(Path, sizeof(Path), PathStr);
             }
 
             int File = open(Path, O_RDONLY);
             if (File == -1)
             {
-                perror("open");
+                printError("open");
                 return -1;
             }
 
@@ -194,14 +326,14 @@ main(int ArgumentCount, char** Arguments)
 
                 if (stat(Path, &FileStat) == -1)
                 {
-                    perror("stat");
+                    printError("stat");
                     return -1;
                 }
 
                 u8* Memory = ARENA_PUSH_ARRAY(&Context.MainArena, u8, FileStat.st_size);
                 if (read(File, Memory, FileStat.st_size) == -1)
                 {
-                    perror("read");
+                    printError("read");
                     return -1;
                 }
 
@@ -212,7 +344,7 @@ main(int ArgumentCount, char** Arguments)
 
             if (close(File) == -1)
             {
-                perror("close");
+                printError("close");
                 return -1;
             }
         }
@@ -220,82 +352,65 @@ main(int ArgumentCount, char** Arguments)
         Recipe->Html = html_parseMarkdown(FileStr, &Context.MainArena);
     }
 
-#if 0
-#if 0
-    /* todo: distill unique sorted tags */
-
-    /* sort recipes */
+    /* allocate tag sentinel */
     {
-        u32 WasReordered = 0;
-
-        /* todo: implement faster sorting */
-        do
+        tag* TagSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, tag);
         {
-            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;
+            TagSentinel->Recipes = createTagRecipeSentinel(&Context.MainArena);
+            TagSentinel->Next = TagSentinel;
+            TagSentinel->Prev = TagSentinel;
 
-                s32 Result = 0;
+            Context.Tags = TagSentinel;
+        }
 
-                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;
-                    }
-                }
+        tag_recipe* TagRecipeSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, tag_recipe);
+        {
+            TagRecipeSentinel->Next = TagRecipeSentinel;
+            TagRecipeSentinel->Prev = TagRecipeSentinel;
 
-                for (u64 StringIndex = 0; StringIndex < CheckLength; StringIndex)
-                {
-                    u8 ALow = TO_LOWERCASE(AStr.Base[StringIndex]);
-                    u8 BLow = TO_LOWERCASE(BStr.Base[StringIndex]);
+            TagSentinel->Recipes = TagRecipeSentinel;
+        }
+    }
 
-                    Result = ALow - BLow;
-                    if (Result != 0)
-                    {
-                        break;
-                    }
-                }
+    /* todo: distill unique sorted tags */
+    for (recipe* Recipe = Context.Recipes->Next;
+         Recipe != Context.Recipes;
+         Recipe = Recipe->Next)
+    {
+        html* Html = Recipe->Html;
+        html_meta* Meta = Html->Meta;
+        html_tag* Sentinel = Meta->Tags;
+        for (html_tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
+        {
+            tag* GlobalTag = getOrCreateTag(Context.Tags, Tag->Content, &Context.MainArena);
+            tag_recipe* GlobalSentinel = GlobalTag->Recipes;
+            tag_recipe* TagRecipe = ARENA_PUSH_STRUCT(&Context.MainArena, tag_recipe);
+            {
+                TagRecipe->Recipe = Recipe;
 
-                if (Result != 0)
+                tag_recipe* Next = GlobalSentinel;
+                if (GlobalSentinel->Next != GlobalSentinel)
                 {
-                    WasReordered = 1;
-
-                    if (Result > 0)
+                    for (tag_recipe* At = GlobalSentinel->Next;
+                         At != GlobalSentinel;
+                         At = At->Next)
                     {
-                        if (Previous == Recipes)
-                        {
-                            Recipes = B;
-                        }
-                        else
+                        if (str_compare(TagRecipe->Recipe->Name, At->Recipe->Name) > 0)
                         {
-                            Previous->Next = B;
+                            Next = At;
+                            break;
                         }
-
-                        A->Next = B->Next;
-                        B->Next = A;
                     }
                 }
 
-                Previous = A;
+                TagRecipe->Next = Next;
+                TagRecipe->Prev = Next->Prev;
+
+                Next->Prev->Next = TagRecipe;
+                Next->Prev = TagRecipe;
             }
         }
-        while (WasReordered == 1);
     }
-#endif
 
     /* generate html output */
     {
@@ -305,47 +420,69 @@ main(int ArgumentCount, char** Arguments)
             html* MainPage = html_createDefault(&Context.MainArena);
             {
                 /* todo: remove title from overview? */
-                html_setTitle(MainPage, STR_LITERAL("Überblick"), &Context.MainArena);
+                html_setTitle(MainPage, STR_LITERAL("Überblick"));
 
                 /* tags */
+                html_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
                 {
-                    html_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
                     html_appendArticleSection(MainPage, Tags);
+                    html_appendContent(Tags, STR_LITERAL("Tags"), &Context.MainArena);
 
                     html_element* TagList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
-                    html_appendValue(Tags, TagList);
-
-                    for (tag* Tag = Context.Tags; isValid_tag(Tag); Tag = Tag->Next)
                     {
-                        html_element* TagElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
-                        html_appendContent(TagElement, Tag->Name, &Context.MainArena);
+                        html_appendArticleSection(MainPage, TagList);
+
+                        tag* Sentinel = Context.Tags;
+                        for (tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
+                        {
+                            html_element* TagElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
+                            html_appendContent(TagElement, Tag->Name, &Context.MainArena);
+                            html_appendChild(TagList, TagElement);
+                        }
                     }
                 }
 
                 /* recipes */
+                html_element* Recipes = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
                 {
-                    html_element* Recipes = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
                     html_appendArticleSection(MainPage, Recipes);
+                    html_appendContent(Recipes, STR_LITERAL("Rezepte"), &Context.MainArena);
 
                     html_element* RecipeList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
-                    html_appendValue(Recipes, RecipeList);
-
-                    for (recipe* Recipe = Context.Recipes; isValid_recipe(Recipe); Recipe = Recipe->Next)
                     {
-                        html_element* RecipeElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
-                        html_appendContent(RecipeElement, Recipe->Name, &Context.MainArena);
+                        html_appendArticleSection(MainPage, RecipeList);
+
+                        for (recipe* Recipe = Context.Recipes->Next;
+                             Recipe != Context.Recipes;
+                             Recipe = Recipe->Next)
+                        {
+                            html_element* RecipeElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
+                            html_appendContent(RecipeElement, Recipe->Name, &Context.MainArena);
+                            html_appendChild(RecipeList, RecipeElement);
+                        }
                     }
                 }
-            }
 
-            str Filename = STR_LITERAL("index.html");
-            if (generateHtmlFile(Filename, MainPage, &Context.MainArena) != 0)
-            {
-                fprintf(stderr, "Error while generating main page. Stopping.\n");
-                return -1;
+                /* serialize html tree */
+                {
+                    arena TempArena = Context.MainArena;
+                    str_unbounded uPath = str_startUnbounded(&TempArena);
+                    {
+                        appendTerminatedDirectory(&uPath.Str, Context.OutputDir);
+                        str_append(&uPath.Str, STR_LITERAL("index.html"));
+                    }
+                    str Filepath = str_endUnbounded(uPath);
+
+                    if (generateHtmlFile(Filepath, MainPage, &Context.MainArena) != 0)
+                    {
+                        fprintf(stderr, "Error while generating main page. Stopping.\n");
+                        return -1;
+                    }
+                }
             }
         }
 
+#if 0
         /* detail pages */
         for (recipe* Recipe = Recipes; isValid_recipe(Recipe); Recipe = Recipe->Next)
         {
@@ -371,8 +508,8 @@ main(int ArgumentCount, char** Arguments)
                 return -1;
             }
         }
-    }
 #endif
+    }
 
     fprintf(stdout, "Done.\n");
 
index ba64c36d8e259a71d6c73c2eb8c68fea5725f9f7..3ae15f09006ff6cd3e796a842b2af416f8a76e72 100644 (file)
@@ -80,6 +80,14 @@ arena_push(arena* Arena, memory_size Size, b32 Zero)
 #define ARENA_PUSH_ARRAY(Arena_, Struct_, Count_) (Struct_*)arena_push(Arena_, (sizeof(Struct_) * Count_), 1)
 
 
+/* error handling */
+static inline void
+printError(char* Message)
+{
+    perror(Message);
+}
+
+
 /* string handling */
 
 #define TO_LOWERCASE(C_) ( ((C_ >= 'A') && (C_ <= 'Z')) ? (C_ | 0x20) : C_ )
@@ -243,19 +251,19 @@ str_equals(str A, str B)
 }
 
 static inline b32
-str_startsWith(str A, str B)
+str_startsWith(str A, str Start)
 {
     b32 Result = 1;
 
-    if (A.Length < B.Length)
+    if (A.Length < Start.Length)
     {
         Result = 0;
     }
     else
     {
-        for (memory_size i = 0u; i < B.Length; i++)
+        for (memory_size i = 0u; i < Start.Length; i++)
         {
-            if (A.Base[i] != B.Base[i])
+            if (A.Base[i] != Start.Base[i])
             {
                 Result = 0;
                 break;
@@ -266,6 +274,31 @@ str_startsWith(str A, str B)
     return Result;
 }
 
+static inline b32
+str_endsWith(str A, str End)
+{
+    b32 Result = 1;
+
+    if (A.Length < End.Length)
+    {
+        Result = 0;
+    }
+    else
+    {
+        for (memory_size i = 0u; i < End.Length; i++)
+        {
+            u8* Candidate = A.Base + (A.Length-1) - i;
+            u8* Reference = End.Base + (End.Length-1) - i;
+            if (*Candidate != *Reference)
+            {
+                Result = 0;
+            }
+        }
+    }
+
+    return Result;
+}
+
 static inline b32
 str_isWhitespaceOnly(str Str)
 {