logo

Grimgrains

[mirror] Plant-based cooking website <https://grimgrains.com/>

main.c (13817B)


  1. #include <ctype.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <time.h>
  5. #define STR_BUF_LEN 64
  6. #define NAME "Grimgrains"
  7. #define DOMAIN "https://grimgrains.com/"
  8. /* Types */
  9. enum RecipeType {
  10. maindish,
  11. sidedish,
  12. sweet,
  13. toppings,
  14. snack,
  15. basic,
  16. tropical,
  17. lifestyle
  18. };
  19. typedef struct Ingredient {
  20. int id, featured;
  21. char *name;
  22. char *description;
  23. struct Ingredient *parent;
  24. } Ingredient;
  25. typedef struct {
  26. Ingredient *ingredient;
  27. char *quantity;
  28. } Serving;
  29. typedef struct {
  30. char *name;
  31. int instructions_len;
  32. char *instructions[16];
  33. int servings_len;
  34. Serving servings[16];
  35. } RecipePart;
  36. typedef struct {
  37. char *name;
  38. enum RecipeType type;
  39. char *portions;
  40. char *description;
  41. int date;
  42. int time;
  43. int parts_len;
  44. RecipePart *parts[10];
  45. } Recipe;
  46. /* Helpers */
  47. int
  48. slen(char *s)
  49. {
  50. int i = 0;
  51. while(s[i] && s[++i])
  52. ;
  53. return i;
  54. }
  55. int
  56. clca(int c)
  57. {
  58. return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
  59. }
  60. char *
  61. scpy(char *src, char *dst)
  62. {
  63. int i = 0;
  64. while((dst[i] = src[i]))
  65. i++;
  66. return dst;
  67. }
  68. int
  69. scmp(char *a, char *b)
  70. {
  71. int i = 0;
  72. while(a[i] == b[i])
  73. if(!a[i++])
  74. return 1;
  75. return 0;
  76. }
  77. char *
  78. slca(char *s)
  79. {
  80. int i;
  81. for(i = 0; i < slen(s); i++)
  82. s[i] = clca(s[i]);
  83. return s;
  84. }
  85. char *
  86. scsw(char *s, char a, char b)
  87. {
  88. int i;
  89. for(i = 0; i < slen(s); i++)
  90. s[i] = s[i] == a ? b : s[i];
  91. return s;
  92. }
  93. void
  94. to_lowercase(char *str, char *target, size_t tsize)
  95. {
  96. for(size_t i = 0; i < tsize; i++) {
  97. target[i] = str[i];
  98. if(!target[i])
  99. break;
  100. if(target[i] == ' ')
  101. target[i] = '_';
  102. else
  103. target[i] = tolower(target[i]);
  104. }
  105. target[tsize - 1] = '\0';
  106. }
  107. void
  108. fpRFC2822(FILE *f, time_t t)
  109. {
  110. struct tm *tm = localtime(&t);
  111. char *days[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  112. char *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  113. fprintf(f, "%s, %02d %s %d 00:00:00 +0900", days[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], tm->tm_year + 1900);
  114. }
  115. time_t
  116. intdate(int date)
  117. {
  118. struct tm str_time;
  119. int y = date / 10000;
  120. int m = (date / 100) % 100;
  121. int d = date % 100;
  122. str_time.tm_year = y - 1900;
  123. str_time.tm_mon = m - 1;
  124. str_time.tm_mday = d - 1;
  125. str_time.tm_hour = 0;
  126. str_time.tm_min = 0;
  127. str_time.tm_sec = 0;
  128. str_time.tm_isdst = 0;
  129. return mktime(&str_time);
  130. }
  131. /* Generics */
  132. Ingredient
  133. create_ingredient(char *name, char *description, int featured)
  134. {
  135. Ingredient a;
  136. a.name = name;
  137. a.description = description;
  138. a.featured = featured;
  139. a.parent = NULL;
  140. return a;
  141. }
  142. Ingredient
  143. create_child_ingredient(Ingredient *parent, char *name, char *description, int featured)
  144. {
  145. Ingredient a;
  146. a.name = name;
  147. a.description = description;
  148. a.featured = featured;
  149. a.parent = parent;
  150. return a;
  151. }
  152. Serving
  153. create_serving(Ingredient *ingredient, char *quantity)
  154. {
  155. Serving a;
  156. a.ingredient = ingredient;
  157. a.quantity = quantity;
  158. return a;
  159. }
  160. char recipe_type_names[20][32] = {"main", "sidedish", "sweet", "toppings", "snack", "basic", "tropical", "lifestyle"};
  161. int recipes_by_types_len[lifestyle + 1] = {0, 0, 0, 0, 0, 0, 0, 0};
  162. char *recipes_by_types[lifestyle + 1][100];
  163. void
  164. categorize_recipe(char *name, enum RecipeType type)
  165. {
  166. recipes_by_types[type][recipes_by_types_len[type]] = name;
  167. recipes_by_types_len[type]++;
  168. }
  169. Recipe
  170. create_recipe(char *name, enum RecipeType type, char *portions, int date, int time)
  171. {
  172. Recipe a;
  173. a.name = name;
  174. a.type = type;
  175. a.portions = portions;
  176. a.date = date;
  177. a.time = time;
  178. a.parts_len = 0;
  179. categorize_recipe(name, type);
  180. return a;
  181. }
  182. RecipePart
  183. create_part(char *name)
  184. {
  185. RecipePart a;
  186. a.name = name;
  187. a.instructions_len = 0;
  188. a.servings_len = 0;
  189. return a;
  190. }
  191. void
  192. set_description(Recipe *r, char *description)
  193. {
  194. r->description = description;
  195. }
  196. void
  197. add_instruction(RecipePart *p, char *instruction)
  198. {
  199. p->instructions[p->instructions_len] = instruction;
  200. p->instructions_len++;
  201. }
  202. void
  203. add_serving(RecipePart *p, Ingredient *i, char *quantity)
  204. {
  205. p->servings[p->servings_len] = create_serving(i, quantity);
  206. p->servings_len++;
  207. }
  208. void
  209. add_part(Recipe *r, RecipePart *p)
  210. {
  211. r->parts[r->parts_len] = p;
  212. r->parts_len++;
  213. }
  214. char *html_head = "<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'><meta name='description' content='Grim Grains is an illustrated food blog, it features plant-based (vegan) recipes.'><meta name='viewport' content='width=device-width, initial-scale=1.0'><meta name='twitter:card' content='summary'><meta name='twitter:site' content='@hundredrabbits'><meta name='twitter:title' content='Grimgrains'><meta name='twitter:description' content='An illustrated food blog.'><meta name='twitter:creator' content='@hundredrabbits'><meta name='twitter:image' content='https://grimgrains.com/media/services/icon.jpg'><meta property='og:title' content='Grimgrains'><meta property='og:type' content='article'><meta property='og:url' content='http://grimgrains.com/'><meta property='og:image' content='https://grimgrains.com/media/services/icon.jpg'><meta property='og:description' content='An illustrated food blog.'><meta property='og:site_name' content='Grimgrains'><link rel='icon' type='image/x-icon' href='../media/services/favicon.ico'><link rel='icon' type='image/png' href='../media/services/icon.jpg'><link rel='apple-touch-icon' href='../media/services/apple-touch-icon.png' /><title>GrimGrains — %s</title><link rel='alternate' type='application/rss+xml' title='RSS Feed' href='../links/rss.xml' /><link rel='stylesheet' type='text/css' href='../links/main.css'></head><body class='%s'>";
  215. char *html_header = "<header><a id='logo' href='home.html'><img src='../media/interface/logo.png' alt='Grimgrains'></a></header>";
  216. char *html_nav = "<nav><ul><li class='home'><a href='home.html'>Home</a></li><li class='about'><a href='about.html'>About</a></li><li class='tools'><a href='tools.html'>Tools</a></li><li class='nutrition'><a href='nutrition.html'>Nutrition</a></li><li class='sprouting'><a href='sprouting.html'>Sprouting</a></li><li class='lactofermentation'><a href='lactofermentation.html'>Lacto-fermentation</a></li><li class='right'><a href='https://grimgrains.com/links/rss.xml'>RSS feed</a> | <a href='https://merveilles.town/@rek' target='_blank'>Mastodon</a></li></ul></nav>";
  217. char *html_footer = "<footer><a href='about.html'>Grimgrains</a> © 2014—2022 <a href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank'> BY-NC-SA-4.0</a><br><a href='http://100r.co/' target='_blank'>Hundred Rabbits</a></footer></body></html>";
  218. void
  219. build_recipe(Recipe *recipe)
  220. {
  221. char filename[STR_BUF_LEN];
  222. to_lowercase(recipe->name, filename, STR_BUF_LEN);
  223. char filepath[128];
  224. snprintf(filepath, 128, "../site/%s.html", filename);
  225. FILE *f = fopen(filepath, "w");
  226. fprintf(f, html_head, recipe->name, "recipe");
  227. fputs(html_header, f);
  228. fputs(html_nav, f);
  229. fputs("<main class='recipe'>", f);
  230. fprintf(f, "<h1>%s</h1>", recipe->name);
  231. fprintf(f, "<h2>%s — %d minutes</h2>", recipe->portions, recipe->time);
  232. fprintf(f, "<img src='../media/recipes/%s.jpg'/>", filename);
  233. fprintf(f, "<div class='col2'>%s</div>", recipe->description);
  234. for(int i = 0; i < recipe->parts_len; ++i) {
  235. fputs("<dl class='ingredients'>", f);
  236. fprintf(f, "<h3>%s</h3>", recipe->parts[i]->name);
  237. for(int i2 = 0; i2 < recipe->parts[i]->servings_len; ++i2) {
  238. char ingr_path[STR_BUF_LEN];
  239. to_lowercase(recipe->parts[i]->servings[i2].ingredient->name, ingr_path, STR_BUF_LEN);
  240. fprintf(f,
  241. "<dt><a href='%s.html'><img src='../media/ingredients/%s.png'/><b>%s</b></a><u>%s</u></dt>",
  242. ingr_path,
  243. ingr_path,
  244. recipe->parts[i]->servings[i2].ingredient->name,
  245. recipe->parts[i]->servings[i2].quantity);
  246. }
  247. fputs("</dl>", f);
  248. fputs("<ul class='instructions'>", f);
  249. for(int i2 = 0; i2 < recipe->parts[i]->instructions_len; ++i2) {
  250. fprintf(f, "<li>%s</li>", recipe->parts[i]->instructions[i2]);
  251. }
  252. fputs("</ul>", f);
  253. }
  254. fputs("</main>", f);
  255. fputs(html_footer, f);
  256. fclose(f);
  257. }
  258. void
  259. build_ingredient(Recipe *recipes[], int recipes_len, Ingredient *ingredient)
  260. {
  261. char filename[STR_BUF_LEN];
  262. to_lowercase(ingredient->name, filename, STR_BUF_LEN);
  263. char filepath[128];
  264. snprintf(filepath, 128, "../site/%s.html", filename);
  265. FILE *f = fopen(filepath, "w");
  266. fprintf(f, html_head, ingredient->name, "ingredient");
  267. fputs(html_header, f);
  268. fputs(html_nav, f);
  269. fputs("<main class='ingredient'>", f);
  270. fprintf(f, "<h1>%s</h1>", ingredient->name);
  271. fprintf(f, "<img class='right' src='../media/ingredients/%s.png'/>", filename);
  272. fprintf(f, "<div>%s</div>", ingredient->description);
  273. if(ingredient->parent) {
  274. fprintf(f, "<h2>%s</h2>", ingredient->parent->name);
  275. fprintf(f, "<div class='small'>%s</div>", ingredient->parent->description);
  276. }
  277. /* Related recipes */
  278. fputs("<ul>", f);
  279. for(int i = 0; i < recipes_len; ++i) {
  280. for(int j = 0; j < recipes[i]->parts_len; ++j) {
  281. for(int k = 0; k < recipes[i]->parts[j]->servings_len; ++k) {
  282. if(scmp(ingredient->name, recipes[i]->parts[j]->servings[k].ingredient->name)) {
  283. char recipe_filename[STR_BUF_LEN];
  284. to_lowercase(recipes[i]->name, recipe_filename, STR_BUF_LEN);
  285. fprintf(f, "<li><a href='%s.html'>%s</a></li>", recipe_filename, recipes[i]->name);
  286. }
  287. }
  288. }
  289. }
  290. fputs("</ul>", f);
  291. fputs("<hr/>", f);
  292. fputs("</main>", f);
  293. fputs(html_footer, f);
  294. fclose(f);
  295. }
  296. void
  297. build_home(Ingredient *ingredients[], int ingredients_len, int recipes_len)
  298. {
  299. char *filename = "home";
  300. char filepath[STR_BUF_LEN];
  301. snprintf(filepath, STR_BUF_LEN, "../site/%s.html", filename);
  302. FILE *f = fopen(filepath, "w");
  303. fprintf(f, html_head, "Home", "home");
  304. fputs(html_header, f);
  305. fputs(html_nav, f);
  306. fputs("<main class='home'>", f);
  307. fputs("<dl class='ingredients'>", f);
  308. for(int i = 0; i < ingredients_len; ++i) {
  309. if(!ingredients[i]->featured)
  310. continue;
  311. char ingr_path[STR_BUF_LEN];
  312. to_lowercase(ingredients[i]->name, ingr_path, STR_BUF_LEN);
  313. fprintf(f,
  314. "<dt><a href='%s.html'><img src='../media/ingredients/%s.png' loading='lazy'/><b>%s</b></a></dt>",
  315. ingr_path,
  316. ingr_path,
  317. ingredients[i]->name);
  318. }
  319. fputs("</dl>", f);
  320. fprintf(f, "<h2 id='recipes'>%d Recipes</h2>", recipes_len);
  321. fputs("<ul class='recipes col3'>", f);
  322. for(int i = 0; i < lifestyle + 1; ++i) {
  323. fprintf(f, "<h3>%s</h3>", recipe_type_names[i]);
  324. for(int j = 0; j < recipes_by_types_len[i]; ++j) {
  325. char recipe_path[STR_BUF_LEN];
  326. to_lowercase(recipes_by_types[i][j], recipe_path, STR_BUF_LEN);
  327. fprintf(f, "<li><a href='%s.html'>%s</a></li>", recipe_path, recipes_by_types[i][j]);
  328. }
  329. }
  330. fputs("</ul>", f);
  331. fputs("</main>", f);
  332. fputs(html_footer, f);
  333. fclose(f);
  334. }
  335. void
  336. build_inc(char *name)
  337. {
  338. char *filename = name;
  339. char filepath[STR_BUF_LEN];
  340. snprintf(filepath, STR_BUF_LEN, "../site/%s.html", filename);
  341. FILE *f = fopen(filepath, "w");
  342. char incpath[STR_BUF_LEN];
  343. snprintf(incpath, STR_BUF_LEN, "inc/%s.htm", filename);
  344. fprintf(f, html_head, name, name);
  345. fputs(html_header, f);
  346. fputs(html_nav, f);
  347. fprintf(f, "<main class='%s'>", name);
  348. char buffer[4096];
  349. FILE *fp = fopen(incpath, "r");
  350. if(!fp)
  351. return;
  352. for(;;) {
  353. size_t sz = fread(buffer, 1, sizeof(buffer), fp);
  354. if(sz)
  355. fwrite(buffer, 1, sz, f);
  356. else if(feof(fp) || ferror(fp))
  357. break;
  358. }
  359. fclose(fp);
  360. fputs("</main>", f);
  361. fputs(html_footer, f);
  362. fclose(f);
  363. }
  364. void
  365. build_rss(FILE *f, Recipe **recipes, int len)
  366. {
  367. int i;
  368. time_t now;
  369. fputs("<?xml version='1.0' encoding='UTF-8' ?>\n", f);
  370. fputs("<rss version='2.0' xmlns:dc='http://purl.org/dc/elements/1.1/'>\n", f);
  371. fputs("<channel>\n", f);
  372. fputs("<title>" NAME "</title>\n", f);
  373. fputs("<link>" DOMAIN "</link>\n", f);
  374. fputs("<description>Grimgrains — a plantbased cooking blog</description>\n", f);
  375. /* Date */
  376. fputs("<lastBuildDate>", f);
  377. fpRFC2822(f, time(&now));
  378. fputs("</lastBuildDate>\n", f);
  379. /* Image */
  380. fputs("<image>\n", f);
  381. fputs(" <url>" DOMAIN "media/services/rss.jpg</url>\n", f);
  382. fputs(" <title>Grimgrains — a plantbased cooking blog</title>\n", f);
  383. fputs(" <link>" DOMAIN "</link>\n", f);
  384. fputs("</image>\n", f);
  385. for(i = 0; i < len; ++i) {
  386. Recipe *r = recipes[i];
  387. char filename[256];
  388. scsw(slca(scpy(r->name, filename)), ' ', '_');
  389. fputs("<item>\n", f);
  390. fprintf(f, " <title>%s</title>\n", r->name);
  391. fprintf(f, " <link>" DOMAIN "site/%s.html</link>\n", filename);
  392. fprintf(f, " <guid isPermaLink='false'>%s</guid>\n", filename);
  393. fputs(" <pubDate>", f);
  394. fpRFC2822(f, intdate(r->date));
  395. fputs("</pubDate>\n", f);
  396. fputs(" <dc:creator><![CDATA[Rekka Bellum]]></dc:creator>\n", f);
  397. fputs(" <description>\n", f);
  398. fputs("<![CDATA[", f);
  399. fprintf(f, "<img src='" DOMAIN "media/recipes/%s.jpg' width='600'/>\n", filename);
  400. fprintf(f, "<div>%s</div>", r->description);
  401. fprintf(f, "<p><a href='" DOMAIN "site/%s.html'>Continue reading</a></p>", filename);
  402. fputs("]]>\n", f);
  403. fputs(" </description>\n", f);
  404. fputs("</item>\n", f);
  405. }
  406. fputs("</channel>", f);
  407. fputs("</rss>", f);
  408. fclose(f);
  409. }
  410. int
  411. main(void)
  412. {
  413. #include "ingredients.c"
  414. #include "recipes.c"
  415. int ingredients_len = sizeof ingredients / sizeof ingredients[0];
  416. int recipes_len = sizeof recipes / sizeof recipes[0];
  417. printf("Found Ingredients: %d, Recipes: %d\n", ingredients_len, recipes_len);
  418. for(int i = 0; i < ingredients_len; ++i) {
  419. build_ingredient(recipes, recipes_len, ingredients[i]);
  420. }
  421. printf("Built %d ingredients\n", ingredients_len);
  422. for(int i = 0; i < recipes_len; ++i) {
  423. build_recipe(recipes[i]);
  424. }
  425. printf("Built %d recipes\n", recipes_len);
  426. build_home(ingredients, ingredients_len, recipes_len);
  427. printf("Built home\n");
  428. build_inc("about");
  429. build_inc("nutrition");
  430. build_inc("sprouting");
  431. build_inc("tools");
  432. build_inc("meals");
  433. build_inc("lactofermentation");
  434. build_inc("pressurecooker");
  435. build_rss(fopen("../links/rss.xml", "w"), recipes, recipes_len);
  436. return (0);
  437. }