}
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;
static inline void
html_appendChild(html_element* Target, html_element* Child)
{
- appendElement(Target->Child, Child);
+ html_appendElement(Target->Child, Child);
}
static inline void
Content->Content = ContentStr;
}
- appendElement(Target->Child, Content);
+ html_appendElement(Target->Child, Content);
}
static html*
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);
+ }
}
}
}
}
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
#include <sys/stat.h>
-typedef struct node
+typedef struct recipe
{
- struct node* Next;
+ struct recipe* Next;
+ struct recipe* Prev;
+
str Name;
html* Html;
} 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
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
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;
}
}
char* OutputDir;
recipe* Recipes;
- //tag* Tags;
+ tag* Tags;
- u64 RecipeCount;
arena MainArena;
} Context = {0};
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;
}
}
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;
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;
}
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;
}
if (close(File) == -1)
{
- perror("close");
+ printError("close");
return -1;
}
}
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 */
{
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)
{
return -1;
}
}
- }
#endif
+ }
fprintf(stdout, "Done.\n");