str Content;
} html_tag;
+typedef struct
+{
+ u16 Year;
+ u8 Month;
+ u8 Day;
+ u8 Hour;
+ u8 Minute;
+} html_datetime;
+
+typedef struct
+{
+ str Str;
+ html_datetime Datetime;
+} html_date;
+
typedef struct
{
html_tag* Tags;
+ html_date Date;
} html_meta;
typedef struct
#endif
}
+static inline u16
+u16AtOffset(str Str, memory_size Offset, memory_size Length)
+{
+ str Substr = str_view(Str, Offset, Length);
+ u16 Result = str_toU16(Substr);
+ return Result;
+}
+
+static inline html_datetime
+calcEpoch(str Str)
+{
+ html_datetime Datetime = {0};
+
+ /* DD.MM.YYYY HH:MM */
+ Datetime.Day = u16AtOffset(Str, 0, 2);
+ Datetime.Month = u16AtOffset(Str, 3, 2);
+ Datetime.Year = u16AtOffset(Str, 6, 4);
+ Datetime.Hour = u16AtOffset(Str, 11, 2);
+ Datetime.Minute = u16AtOffset(Str, 14, 2);
+
+ return Datetime;
+}
+
+typedef enum
+{
+ SECTION_TYPE_none,
+
+ SECTION_TYPE_details,
+ SECTION_TYPE_tags
+} SECTION_TYPE;
+
static html*
html_parseMarkdown(str Source, arena* Arena)
{
b32 ExpectShebang = 0;
b32 SkipIniSection = 0;
- // todo: remove
- //b32 PreviousLineWasEmpty = 0;
+ SECTION_TYPE SectionType = SECTION_TYPE_none;
html_element* PrevElement = Html->Article;
for (str Line = str_getLine(Source);
{
Frontmatter = 1;
ExpectShebang = 1;
- //PreviousLineWasEmpty = 0;
}
else if (str_startsWith(Stripped, STR_LITERAL("#")))
{
str_startsWith(Line, STR_LITERAL("*")))
{
PrevElement = parseListItem(Stripped, PrevElement, Arena);
- //PreviousLineWasEmpty = 0;
}
else if (Stripped.Length > 0)
{
- /* todo: check if `PreviousLineWasEmpty` is really necessary */
PrevElement = parseParagraph(Stripped, PrevElement, Arena);
- //PrevElement = parseParagraph(Stripped, PreviousLineWasEmpty, PrevElement, Arena);
- //PreviousLineWasEmpty = 0;
}
}
else
{
- //PreviousLineWasEmpty = 1;
PrevElement = Html->Article;
}
}
if (0);
else if (str_startsWith(Stripped, STR_LITERAL("[")))
{
- if (str_equals(Stripped, STR_LITERAL("[tags]")))
+ if (str_equals(Stripped, STR_LITERAL("[details]")))
{
- /* skip section header */
+ SectionType = SECTION_TYPE_details;
+ }
+ else if (str_equals(Stripped, STR_LITERAL("[tags]")))
+ {
+ SectionType = SECTION_TYPE_tags;
}
else
{
}
else if (Stripped.Length > 0)
{
- html_tag* Sentinel = Html->Meta->Tags;
- appendTag(Sentinel, Stripped, Arena);
+ switch (SectionType)
+ {
+ case SECTION_TYPE_details:
+ {
+ if (str_startsWith(Stripped, STR_LITERAL("date")))
+ {
+ html_date* Date = &Html->Meta->Date;
+ Stripped = str_removeStart(Stripped, STR_LITERAL("date"));
+ Stripped = str_removeStart(Stripped, STR_LITERAL("="));
+ Date->Str = Stripped;
+ Date->Datetime = calcEpoch(Date->Str);
+ }
+ else if (str_startsWith(Stripped, STR_LITERAL("portions")))
+ {
+ /* todo: implement to process "portions" info */
+ }
+ } break;
+
+ case SECTION_TYPE_tags:
+ {
+ html_tag* Sentinel = Html->Meta->Tags;
+ appendTag(Sentinel, Stripped, Arena);
+ } break;
+
+ INVALID_DEFAULT;
+ }
}
else
{
str Name;
} recipe;
+typedef struct date
+{
+ struct date* Next;
+ struct date* Prev;
+
+ recipe* Recipe;
+} date;
+
typedef struct image
{
struct image* Next;
return Image;
}
+static inline b32 isValid_datetime(html_datetime Datetime)
+{
+ b32 Result = 0;
+
+ if (Datetime.Year && Datetime.Month && Datetime.Day &&
+ Datetime.Hour && Datetime.Minute)
+ {
+ Result = 1;
+ }
+
+ return Result;
+}
+
+static inline b32 isNewerThan(html_datetime D, html_datetime Ref)
+{
+ b32 Result = 0;
+
+ if (D.Year > Ref.Year)
+ {
+ Result = 1;
+ }
+ else if (D.Month > Ref.Month)
+ {
+ Result = 1;
+ }
+ else if (D.Day > Ref.Day)
+ {
+ Result = 1;
+ }
+ else if (D.Hour > Ref.Hour)
+ {
+ Result = 1;
+ }
+ else if (D.Minute > Ref.Minute)
+ {
+ Result = 1;
+ }
+
+ return Result;
+}
+
static u32
generateHtmlFile(str Filename, html* Html, arena* Arena)
{
}
static void
-appendRecipeLink(html_element* RecipeElement, recipe* Recipe, arena* Arena)
+appendLink(html_element* Parent, str Link, str Content, arena* Arena)
{
html_element* RecipeLink = html_createElement(HTML_ELEMENT_NAME_a, Arena);
+ html_appendContent(RecipeLink, Content, Arena);
+ html_prependAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena);
+ html_appendChild(Parent, RecipeLink);
+}
+
+static str
+generateRecipePath(recipe* Recipe, arena* Arena)
+{
+ str_unbounded uPath = str_startUnbounded(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);
+ str_append(&uPath.Str, STR_LITERAL("/recipes/"));
+ str_append(&uPath.Str, str_stripExtension(Recipe->Filename));
+ str_append(&uPath.Str, STR_LITERAL(".html"));
+ }
+
+ str Path = str_endUnbounded(uPath);
+ return Path;
+}
- html_prependAttribute(RecipeLink, STR_LITERAL("href"), Link, Arena);
- html_appendContent(RecipeLink, Recipe->Name, Arena);
- html_appendChild(RecipeElement, RecipeLink);
+static void
+appendRecipeLink(html_element* RecipeElement, recipe* Recipe, arena* Arena)
+{
+ str Path = generateRecipePath(Recipe, Arena);
+ appendLink(RecipeElement, Path, Recipe->Name, Arena);
+}
+
+static void
+appendRecipeLinkWithDatetime(html_element* RecipeElement, date* Date, arena* Arena)
+{
+ recipe* Recipe = Date->Recipe;
+
+ str Path = generateRecipePath(Recipe, Arena);
+
+ str_unbounded uContent = str_startUnbounded(Arena);
+ {
+ str_append(&uContent.Str, Recipe->Name);
+ str_append(&uContent.Str, STR_LITERAL(" ("));
+ html_date Date = Recipe->Html->Meta->Date;
+ /* cut off the time */
+ str DateStr = str_view(Date.Str, 0u, 10u);
+ str_append(&uContent.Str, DateStr);
+ str_append(&uContent.Str, STR_LITERAL(")"));
}
+ str Content = str_endUnbounded(uContent);
+
+ appendLink(RecipeElement, Path, Content, Arena);
}
int
recipe* Recipes;
tag* Tags;
image* Images;
+ date* Dates;
arena MainArena;
} Context = {0};
}
}
+ /* allocate date sentinel */
+ {
+ date* DateSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, date);
+ {
+ DateSentinel->Next = DateSentinel;
+ DateSentinel->Prev = DateSentinel;
+
+ Context.Dates = DateSentinel;
+ }
+ }
+
+ /* distill sorted date list */
+ {
+ recipe* RecipeSentinel = Context.Recipes;
+ for (recipe* At = RecipeSentinel->Next; At != RecipeSentinel; At = At->Next)
+ {
+ html_datetime Datetime = At->Html->Meta->Date.Datetime;
+ if (isValid_datetime(Datetime))
+ {
+ date* Date = ARENA_PUSH_STRUCT(&Context.MainArena, date);
+ Date->Recipe = At;
+
+ date* DateSentinel = Context.Dates;
+ date* Next = DateSentinel;
+ if (DateSentinel->Next != DateSentinel)
+ {
+ for (date* Comp = DateSentinel->Next; Comp != DateSentinel; Comp = Comp->Next)
+ {
+ html_datetime CompDatetime = Comp->Recipe->Html->Meta->Date.Datetime;
+ if (isNewerThan(Datetime, CompDatetime))
+ {
+ Next = Comp;
+ break;
+ }
+ }
+ }
+
+ Date->Next = Next;
+ Date->Prev = Next->Prev;
+
+ Next->Prev->Next = Date;
+ Next->Prev = Date;
+ }
+ }
+ }
+
/* generate html output */
{
/* main page */
html* MainPage = html_createDefault(&Context.MainArena);
html_setArticleClass(MainPage, STR_LITERAL("overview"), &Context.MainArena);
+ /* recently added */
+ {
+ html_element* Recent = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
+ html_appendArticleSection(MainPage, Recent);
+ html_appendContent(Recent, STR_LITERAL("Zuletzt hinzugefügt"), &Context.MainArena);
+
+ html_element* RecentList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
+ html_appendArticleSection(MainPage, RecentList);
+ html_prependAttribute(RecentList, STR_LITERAL("class"), STR_LITERAL("recent"), &Context.MainArena);
+
+ u8 RecentCount = 0u;
+ date* Sentinel = Context.Dates;
+ for (date* At = Sentinel->Next; At != Sentinel; At = At->Next)
+ {
+ html_element* RecentElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
+ {
+ html_appendChild(RecentList, RecentElement);
+ appendRecipeLinkWithDatetime(RecentElement, At, &Context.MainArena);
+ }
+
+ if (++RecentCount > 3)
+ {
+ break;
+ }
+ }
+ }
+
/* recipes */
{
html_element* Recipes = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
}
}
}
+ }
- /* tags */
+ /* tags */
+ {
+ 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_element* Tags = html_createElement(HTML_ELEMENT_NAME_h2, &Context.MainArena);
- html_appendArticleSection(MainPage, Tags);
- html_appendContent(Tags, STR_LITERAL("Tags"), &Context.MainArena);
+ html_appendArticleSection(MainPage, TagList);
+ html_prependAttribute(TagList, STR_LITERAL("class"), STR_LITERAL("tags"), &Context.MainArena);
- html_element* TagList = html_createElement(HTML_ELEMENT_NAME_ul, &Context.MainArena);
+ tag* Sentinel = Context.Tags;
+ for (tag* Tag = Sentinel->Next; Tag != Sentinel; Tag = Tag->Next)
{
- html_appendArticleSection(MainPage, TagList);
- html_prependAttribute(TagList, STR_LITERAL("class"), STR_LITERAL("tags"), &Context.MainArena);
-
- 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_element* TagElement = html_createElement(HTML_ELEMENT_NAME_li, &Context.MainArena);
- {
- html_appendChild(TagList, TagElement);
+ html_appendChild(TagList, TagElement);
- html_element* TagLink = html_createElement(HTML_ELEMENT_NAME_a, &Context.MainArena);
+ html_element* TagLink = html_createElement(HTML_ELEMENT_NAME_a, &Context.MainArena);
+ {
+ str_unbounded uPath = str_startUnbounded(&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_prependAttribute(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);
+ 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_prependAttribute(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);
}
}
}
}
+ }
- /* serialize html tree */
+
+ /* serialize html tree */
+ {
+ arena TempArena = Context.MainArena;
+ str_unbounded uPath = str_startUnbounded(&TempArena);
{
- 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);
+ 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 (generateHtmlFile(Filepath, MainPage, &Context.MainArena) != 0)
+ {
+ fprintf(stderr, "Error while generating main page. Stopping.\n");
+ return -1;
}
}
}