X(p) \
X(ul) \
X(li) \
+ X(a) \
\
X(content)
typedef struct
{
+ str Title;
html_tag* Tags;
} html_meta;
}
static inline void
-addAttribute(html_element* Element, str Key, str Child, arena* Arena)
+html_addAttribute(html_element* Element, str Key, str Child, arena* Arena)
{
html_attribute* Attribute = ARENA_PUSH_STRUCT(Arena, html_attribute);
{
html_appendElement(Target->Child, Content);
}
+static inline void
+html_appendArticleSection(html* Html, html_element* Section)
+{
+ html_element* Article = Html->Article;
+ html_appendChild(Article, Section);
+}
+
+static inline void
+html_appendTagListToArticle(html* Html, str TagsDir, arena* Arena)
+{
+ html_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, Arena);
+ {
+ html_appendArticleSection(Html, Tags);
+ html_appendContent(Tags, STR_LITERAL("Tags"), Arena);
+
+ html_element* TagList = html_createElement(HTML_ELEMENT_NAME_ul, Arena);
+ {
+ html_appendArticleSection(Html, TagList);
+
+ html_tag* Sentinel = Html->Meta->Tags;
+ for (html_tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
+ {
+ html_element* TagElement = html_createElement(HTML_ELEMENT_NAME_li, Arena);
+ {
+ html_appendChild(TagList, TagElement);
+
+ html_element* TagLink = html_createElement(HTML_ELEMENT_NAME_a, Arena);
+ {
+ str_unbounded uPath = str_startUnbounded(Arena);
+ {
+ str_append(&uPath.Str, TagsDir);
+ str_append(&uPath.Str, Tag->Content);
+ str_append(&uPath.Str, STR_LITERAL(".html"));
+ }
+ str Link = str_endUnbounded(uPath);
+
+ html_addAttribute(TagLink, STR_LITERAL("href"), Link, Arena);
+ html_appendContent(TagLink, STR_LITERAL("#"), Arena);
+ html_appendContent(TagLink, Tag->Content, Arena);
+ html_appendChild(TagElement, TagLink);
+ }
+ }
+ }
+ }
+ }
+}
+
static html*
html_createDefault(arena* Arena)
{
html_element* HtmlElement = html_createElement(HTML_ELEMENT_NAME_html, Arena);
{
- addAttribute(HtmlElement, STR_LITERAL("lang"), STR_LITERAL("de-DE"), Arena);
+ html_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_element* MetaCharset = html_createElement(HTML_ELEMENT_NAME_meta, Arena);
{
- addAttribute(MetaCharset, STR_LITERAL("charset"), STR_LITERAL("utf-8"), Arena);
+ html_addAttribute(MetaCharset, STR_LITERAL("charset"), STR_LITERAL("utf-8"), Arena);
html_appendChild(Head, MetaCharset);
}
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_addAttribute(MetaViewport, STR_LITERAL("name"), STR_LITERAL("viewport"), Arena);
+ html_addAttribute(MetaViewport, STR_LITERAL("content"), STR_LITERAL("width=device-width, initial-scale=1"), Arena);
html_appendChild(Head, MetaViewport);
}
Sentinel->Prev = Tag;
}
-static html_element*
-parseHeading(str Line, html_element* Article, arena* Arena)
+static void
+parseHeading(str Line, html* Html, arena* Arena)
{
/* actual element name will be set below */
html_element* Heading = html_createElement(HTML_ELEMENT_NAME_invalid, Arena);
}
html_appendContent(Heading, HeadingContent, Arena);
- }
- html_appendChild(Article, Heading);
+ if (Heading->ElementName == HTML_ELEMENT_NAME_h1)
+ {
+ Html->Meta->Title = HeadingContent;
+ }
+ }
- return Article;
+ html_appendChild(Html->Article, Heading);
}
static html_element*
}
else if (str_startsWith(Stripped, STR_LITERAL("#")))
{
- /* todo: remove return value? */
- PrevElement = parseHeading(Stripped, Html->Article, Arena);
- //PreviousLineWasEmpty = 0;
+ parseHeading(Stripped, Html, Arena);
}
else if (str_startsWith(Line, STR_LITERAL("-")) ||
str_startsWith(Line, STR_LITERAL("*")))
Content->Content = TitleStr;
}
-static inline void
-html_appendArticleSection(html* Html, html_element* Section)
+static str
+html_getTitle(html* Html)
{
- html_element* Article = Html->Article;
- html_appendChild(Article, Section);
+ html_meta* Meta = Html->Meta;
+
+ str Title = Meta->Title;
+ return Title;
}
static str
{
str_unbounded Unbounded = str_startUnbounded(Arena);
{
- str* Line = &Unbounded.Str;
- serializeElement(Line, Html->Root);
+ str* Target = &Unbounded.Str;
+ serializeElement(Target, Html->Root);
}
str Result = str_endUnbounded(Unbounded);
return Result;
#include <sys/stat.h>
+enum /* Limits */
+{
+ MAX_PATH = 1024
+};
+
typedef struct recipe
{
struct recipe* Next;
struct recipe* Prev;
- str Name;
html* Html;
+
+ str Filename;
+ str Name;
} recipe;
+static int
+provideDirectory(str Path)
+{
+ char PathCStr[MAX_PATH] = {0};
+ str_toCString(PathCStr, sizeof(PathCStr), Path);
+
+ DIR* Directory = opendir(PathCStr);
+ if (Directory == NULL)
+ {
+ if (mkdir(PathCStr, 0755) == -1)
+ {
+ printError("mkdir");
+ return -1;
+ }
+ }
+ else
+ {
+ if (closedir(Directory) == -1)
+ {
+ printError("closedir");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
static b32
isValid_recipe(recipe* Recipe)
{
}
static inline void
-appendTerminatedDirectory(str* Target, char* Dir)
+appendTerminatedDirectory(str* Target, str Dir)
{
- str_appendCString(Target, Dir);
+ str_append(Target, Dir);
if (!str_endsWith(*Target, STR_LITERAL("/")))
{
str_append(Target, STR_LITERAL("/"));
}
}
-enum /* Limits */
-{
- MAX_PATH = 1024
-};
-
static u32
generateHtmlFile(str Filename, html* Html, arena* Arena)
{
return Error;
}
+static void
+appendRecipeLink(html_element* RecipeElement, recipe* Recipe, arena* Arena)
+{
+ html_element* RecipeLink = html_createElement(HTML_ELEMENT_NAME_a, Arena);
+ {
+ str_unbounded uPath = str_startUnbounded(Arena);
+ {
+ str_append(&uPath.Str, STR_LITERAL("/recipes/"));
+ str_append(&uPath.Str, str_stripExtension(Recipe->Filename));
+ str_append(&uPath.Str, STR_LITERAL(".html"));
+ }
+ str Link = str_endUnbounded(uPath);
+
+ html_addAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena);
+ html_appendContent(RecipeLink, Recipe->Name, Arena);
+ html_appendChild(RecipeElement, RecipeLink);
+ }
+}
+
int
main(int ArgumentCount, char** Arguments)
{
struct
{
- char* SourceDir;
- char* OutputDir;
+ str SourceDir;
+ str OutputDir;
+ str OutputDirTags;
+ str OutputDirRecipes;
recipe* Recipes;
tag* Tags;
arena MainArena;
} Context = {0};
+ Context.MainArena = arena_create(MEGABYTES(16));
+
/* handle commandline arguments */
if (ArgumentCount != 3)
{
}
else
{
- Context.SourceDir = Arguments[1];
- Context.OutputDir = Arguments[2];
+ Context.SourceDir = str_fromCString(&Context.MainArena, Arguments[1]);
+ Context.OutputDir = str_fromCString(&Context.MainArena, Arguments[2]);
}
- Context.MainArena = arena_create(MEGABYTES(16));
-
/* allocate recipe sentinel */
{
recipe* RecipeSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, recipe);
/* enumerate recipe files */
{
- DIR* Directory = opendir(Context.SourceDir);
+ char PathCStr[MAX_PATH] = {0};
+ str_toCString(PathCStr, sizeof(PathCStr), Context.SourceDir);
+
+ DIR* Directory = opendir(PathCStr);
if (Directory == NULL)
{
printError("opendir");
recipe* New = ARENA_PUSH_STRUCT(&Context.MainArena, recipe);
{
+ New->Filename = Name;
+
recipe* Sentinel = Context.Recipes;
+ recipe* Next = Sentinel;
+ if (Sentinel->Next != Sentinel)
+ {
+ for (recipe* At = Sentinel->Next;
+ At != Sentinel;
+ At = At->Next)
+ {
+ if (str_compare(New->Filename, At->Filename) > 0)
+ {
+ Next = At;
+ break;
+ }
+ }
+ }
- New->Name = Name;
- New->Next = Sentinel;
+ New->Next = Next;
+ New->Prev = Next->Prev;
- Sentinel->Prev->Next = New;
- Sentinel->Prev = New;
+ Next->Prev->Next = New;
+ Next->Prev = New;
}
}
}
}
- /* ensure existence of output directory */
+ /* ensure existence of output directories */
{
- DIR* Directory = opendir(Context.OutputDir);
- if (Directory == NULL)
+ if (provideDirectory(Context.OutputDir) == -1)
{
- if (mkdir(Context.OutputDir, 0755) == -1)
+ return -1;
+ }
+
+ /* tags directory */
+ {
+ str_unbounded uPath = str_startUnbounded(&Context.MainArena);
+ {
+ appendTerminatedDirectory(&uPath.Str, Context.OutputDir);
+ appendTerminatedDirectory(&uPath.Str, STR_LITERAL("tags"));
+ }
+ Context.OutputDirTags = str_endUnbounded(uPath);
+
+ if (provideDirectory(Context.OutputDirTags) == -1)
{
- printError("mkdir");
return -1;
}
}
- else
+
+ /* recipe directory */
{
- if (closedir(Directory) == -1)
+ str_unbounded uPath = str_startUnbounded(&Context.MainArena);
+ {
+ appendTerminatedDirectory(&uPath.Str, Context.OutputDir);
+ appendTerminatedDirectory(&uPath.Str, STR_LITERAL("recipes"));
+ }
+ Context.OutputDirRecipes = str_endUnbounded(uPath);
+
+ if (provideDirectory(Context.OutputDirRecipes) == -1)
{
- printError("closedir");
return -1;
}
}
}
-
/* todo: parse recipe files multithreaded */
for (recipe* Recipe = Context.Recipes->Next;
Recipe != Context.Recipes;
str_unbounded uPath = str_startUnbounded(&TempArena);
{
appendTerminatedDirectory(&uPath.Str, Context.SourceDir);
- str_append(&uPath.Str, Recipe->Name);
+ str_append(&uPath.Str, Recipe->Filename);
}
str PathStr = str_endUnbounded(uPath);
}
Recipe->Html = html_parseMarkdown(FileStr, &Context.MainArena);
+ Recipe->Name = html_getTitle(Recipe->Html);
}
/* allocate tag sentinel */
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);
+ {
+ html_appendChild(TagList, TagElement);
+
+ html_element* TagLink = html_createElement(HTML_ELEMENT_NAME_a, &Context.MainArena);
+ {
+ str_unbounded uPath = str_startUnbounded(&Context.MainArena);
+ {
+ str_append(&uPath.Str, STR_LITERAL("/tags/"));
+ str_append(&uPath.Str, Tag->Name);
+ str_append(&uPath.Str, STR_LITERAL(".html"));
+ }
+ str Link = str_endUnbounded(uPath);
+
+ html_addAttribute(TagLink, STR_LITERAL("href"), Link, &Context.MainArena);
+ html_appendContent(TagLink, STR_LITERAL("#"), &Context.MainArena);
+ html_appendContent(TagLink, Tag->Name, &Context.MainArena);
+ html_appendChild(TagElement, TagLink);
+ }
+ }
}
}
}
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);
+ {
+ html_appendChild(RecipeList, RecipeElement);
+ appendRecipeLink(RecipeElement, Recipe, &Context.MainArena);
+ }
}
}
}
}
}
-#if 0
- /* detail pages */
- for (recipe* Recipe = Recipes; isValid_recipe(Recipe); Recipe = Recipe->Next)
+ /* tag pages */
{
- /* recipes/... */
- str Filename = ...;
-
- if (generateHtmlFile(Filename, Recipe->Html) != 0)
+ tag* Sentinel = Context.Tags;
+ for (tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
{
- fprintf(stderr, "Error while generating detail page. Stopping.\n");
- return -1;
+ /* todo: combine createDefault and setTitle in helper function? */
+ html* TagPage = html_createDefault(&Context.MainArena);
+ {
+ html_setTitle(TagPage, Tag->Name);
+
+ html_element* RecipeList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
+ {
+ html_appendArticleSection(TagPage, RecipeList);
+
+ tag_recipe* Sentinel = Tag->Recipes;
+ for (tag_recipe* TagRecipe = Sentinel->Next; TagRecipe != Sentinel; TagRecipe = TagRecipe->Next)
+ {
+ html_element* RecipeElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
+ {
+ html_appendChild(RecipeList, RecipeElement);
+ appendRecipeLink(RecipeElement, TagRecipe->Recipe, &Context.MainArena);
+ }
+ }
+ }
+ }
+
+ /* serialize html tree */
+ {
+ arena TempArena = Context.MainArena;
+ str_unbounded uPath = str_startUnbounded(&TempArena);
+ {
+ str_append(&uPath.Str, Context.OutputDirTags);
+ str_append(&uPath.Str, Tag->Name);
+ str_append(&uPath.Str, STR_LITERAL(".html"));
+ }
+ str Filepath = str_endUnbounded(uPath);
+
+ if (generateHtmlFile(Filepath, TagPage, &Context.MainArena) != 0)
+ {
+ fprintf(stderr, "Error while generating main page. Stopping.\n");
+ return -1;
+ }
+ }
}
}
- /* tag page? */
- for (tag* Tag = Tags; isValid_tag(Tag); Tag = Tag->Next)
+ /* detail pages */
{
- /* tags/... */
- str Filename = ...;
-
- if (generateHtmlFile(Filename, Tag->Html) != 0)
+ recipe* Sentinel = Context.Recipes;
+ for (recipe* Recipe = Sentinel->Next; Recipe != Sentinel; Recipe = Recipe->Next)
{
- fprintf(stderr, "Error while generating tag page. Stopping.\n");
- return -1;
+ /* append tag list */
+ html_appendTagListToArticle(Recipe->Html, STR_LITERAL("/tags/"), &Context.MainArena);
+
+ /* serialize html tree */
+ {
+ arena TempArena = Context.MainArena;
+ str_unbounded uPath = str_startUnbounded(&TempArena);
+ {
+ str_append(&uPath.Str, Context.OutputDirRecipes);
+ str_append(&uPath.Str, str_stripExtension(Recipe->Filename));
+ str_append(&uPath.Str, STR_LITERAL(".html"));
+ }
+ str Filepath = str_endUnbounded(uPath);
+
+ if (generateHtmlFile(Filepath, Recipe->Html, &Context.MainArena) != 0)
+ {
+ fprintf(stderr, "Error while generating main page. Stopping.\n");
+ return -1;
+ }
+ }
}
}
-#endif
}
fprintf(stdout, "Done.\n");