add logic to include one image per recipe
authorLukas Hägele <lukas.haegele93@web.de>
Tue, 11 Feb 2025 18:49:06 +0000 (19:49 +0100)
committerLukas Hägele <lukas.haegele93@web.de>
Tue, 11 Feb 2025 18:49:06 +0000 (19:49 +0100)
Makefile
src/html.c
src/main.c

index 9e0c52493f5776aea223d054785c8f7305284202..f3eb6fafec87efbc114dc96874f2ab2039e4d40b 100644 (file)
--- 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
index 6f2105f3de204b19c1e6c776cdee3f26da9fc501..681cb1170834d0712e88c697322fa3fe426d9a46 100644 (file)
@@ -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 */
             }
index 6231c04283cb7d089c84645c7b501c0f201188f1..084958f27024b19382188878cf07006245d91c7f 100644 (file)
@@ -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, ...) */