From: Lukas Hägele Date: Tue, 11 Feb 2025 18:49:06 +0000 (+0100) Subject: add logic to include one image per recipe X-Git-Url: https://git.lhaegele.de/?a=commitdiff_plain;h=9439fd932dd5aee11ff8e3a5cec9ddef131c7199;p=recipes.git add logic to include one image per recipe --- diff --git a/Makefile b/Makefile index 9e0c524..f3eb6fa 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,10 @@ all: build convert build: mkdir -p bin cc -Wall -Wextra -O2 -o bin/converter src/main.c + #cc -Wall -Wextra -O0 -g3 -o bin/converter src/main.c convert: mkdir -p html - rm -r html/recipes/* - rm -r html/tags/* + rm -rf html/recipes/* + rm -rf html/tags/* bin/converter content html diff --git a/src/html.c b/src/html.c index 6f2105f..681cb11 100644 --- a/src/html.c +++ b/src/html.c @@ -33,6 +33,7 @@ X(ul) \ X(li) \ X(a) \ + X(img) \ \ X(content) @@ -206,6 +207,28 @@ html_setArticleClass(html* Html, str ClassName, arena* Arena) html_prependAttribute(Article, STR_LITERAL("class"), ClassName, Arena); } +static inline void +html_appendImageToArticle(html* Html, str ImageDir, str Filename, arena* Arena) +{ + html_element* ImageSection = html_createElement(HTML_ELEMENT_NAME_h2, Arena); + html_appendContent(ImageSection, STR_LITERAL("Bild"), Arena); + html_appendArticleSection(Html, ImageSection); + + html_element* Image = html_createElement(HTML_ELEMENT_NAME_img, Arena); + html_prependAttribute(Image, STR_LITERAL("class"), STR_LITERAL("fit-picture"), Arena); + { + str_unbounded uPath = str_startUnbounded(Arena); + { + str_append(&uPath.Str, ImageDir); + str_append(&uPath.Str, Filename); + } + str Path = str_endUnbounded(uPath); + + html_prependAttribute(Image, STR_LITERAL("src"), Path, Arena); + } + html_appendArticleSection(Html, Image); +} + static inline void html_appendTagListToArticle(html* Html, str TagsDir, arena* Arena) { @@ -677,7 +700,8 @@ serializeElement(str* Target, html_element* Parent) str_append(Target, STR_LITERAL(">")); if ((Child->ElementName == HTML_ELEMENT_NAME_meta) || - (Child->ElementName == HTML_ELEMENT_NAME_link)) + (Child->ElementName == HTML_ELEMENT_NAME_link) || + (Child->ElementName == HTML_ELEMENT_NAME_img)) { /* void elements don't have a closing tag */ } diff --git a/src/main.c b/src/main.c index 6231c04..084958f 100644 --- a/src/main.c +++ b/src/main.c @@ -22,8 +22,6 @@ * - output to html directory * - sortable table? (sort by most recent, by name, ...) * - navigation (return to main page from recipe) - * - * - combine connected foods (lasagne) */ #define _POSIX_C_SOURCE 200112L @@ -48,12 +46,23 @@ typedef struct recipe struct recipe* Next; struct recipe* Prev; + struct image* Image; html* Html; str Filename; str Name; } recipe; +typedef struct image +{ + struct image* Next; + struct image* Prev; + + struct recipe* Recipe; + + str Filename; +} image; + static int provideDirectory(str Path) { @@ -164,6 +173,27 @@ appendTerminatedDirectory(str* Target, str Dir) } } +static inline image* +searchImage(recipe* Recipe, image* Images) +{ + image* Image = 0; + + for (image* At = Images->Next; At != Images; At = At->Next) + { + if (str_equals(str_stripExtension(At->Filename), + str_stripExtension(Recipe->Filename))) + { + Image = At; + Image->Recipe = Recipe; + Recipe->Image = Image; + + break; + } + } + + return Image; +} + static u32 generateHtmlFile(str Filename, html* Html, arena* Arena) { @@ -237,6 +267,7 @@ main(int ArgumentCount, char** Arguments) recipe* Recipes; tag* Tags; + image* Images; arena MainArena; } Context = {0}; @@ -326,6 +357,67 @@ main(int ArgumentCount, char** Arguments) } } + /* allocate image sentinel */ + { + image* ImageSentinel = ARENA_PUSH_STRUCT(&Context.MainArena, image); + { + ImageSentinel->Next = ImageSentinel; + ImageSentinel->Prev = ImageSentinel; + + Context.Images = ImageSentinel; + } + } + + /* enumerate image files */ + { + str_unbounded uPath = str_startUnbounded(&Context.MainArena); + { + appendTerminatedDirectory(&uPath.Str, Context.OutputDir); + appendTerminatedDirectory(&uPath.Str, STR_LITERAL("images")); + } + str ImagePath = str_endUnbounded(uPath); + + char PathCStr[MAX_PATH] = {0}; + str_toCString(PathCStr, sizeof(PathCStr), ImagePath); + + DIR* Directory = opendir(PathCStr); + if (Directory == NULL) + { + printError("opendir"); + return -1; + } + + 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(".."))) + { + /* skip entry */ + continue; + } + + image* New = ARENA_PUSH_STRUCT(&Context.MainArena, image); + { + New->Filename = Name; + + image* Sentinel = Context.Images; + New->Next = Sentinel; + New->Prev = Sentinel->Prev; + Sentinel->Prev->Next = New; + Sentinel->Prev = New; + } + } + + if (closedir(Directory) == -1) + { + printError("closedir"); + return -1; + } + } + /* ensure existence of output directories */ { if (provideDirectory(Context.OutputDir) == -1) @@ -423,6 +515,7 @@ main(int ArgumentCount, char** Arguments) Recipe->Html = html_parseMarkdown(FileStr, &Context.MainArena); Recipe->Name = html_getTitleStr(Recipe->Html); + Recipe->Image = searchImage(Recipe, Context.Images); } /* allocate tag sentinel */ @@ -624,7 +717,10 @@ main(int ArgumentCount, char** Arguments) recipe* Sentinel = Context.Recipes; for (recipe* Recipe = Sentinel->Next; Recipe != Sentinel; Recipe = Recipe->Next) { - /* append tag list */ + if (Recipe->Image) + { + html_appendImageToArticle(Recipe->Html, STR_LITERAL("/images/"), Recipe->Image->Filename, &Context.MainArena); + } html_appendTagListToArticle(Recipe->Html, STR_LITERAL("/tags/"), &Context.MainArena); /* serialize html tree */ @@ -648,6 +744,26 @@ main(int ArgumentCount, char** Arguments) } } + /* look for unused images */ + { + image* Sentinel = Context.Images; + for (image* At = Sentinel->Next; At != Sentinel; At = At->Next) + { + if (!At->Recipe) + { + str_unbounded uMsg = str_startUnbounded(&Context.MainArena); + { + str_append(&uMsg.Str, STR_LITERAL("image '")); + str_append(&uMsg.Str, At->Filename); + str_append(&uMsg.Str, STR_LITERAL("' is not linked to any recipe\n")); + } + str Message = str_endUnbounded(uMsg); + + write(STDOUT_FILENO, Message.Base, Message.Length); + } + } + } + fprintf(stdout, "Done.\n"); /* todo: output stats (memory consumption, runtime, ...) */