diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | README.org | 10 | ||||
-rw-r--r-- | cookbook.c | 162 | ||||
-rw-r--r-- | emacs/recipe-mode.el | 67 | ||||
-rw-r--r-- | food.c | 64 | ||||
-rw-r--r-- | lib/minced-meat.rcp | 32 | ||||
-rw-r--r-- | lib/pasta-red-sauce.rcp | 5 | ||||
-rw-r--r-- | lib/pasta-with-minced-meat.rcp | 4 | ||||
-rw-r--r-- | lib/sandwich-base.rcp | 2 | ||||
-rw-r--r-- | lib/sandwich-sour.rcp | 13 | ||||
-rw-r--r-- | src/eval.c | 23 | ||||
-rw-r--r-- | src/lib.c | 48 | ||||
-rw-r--r-- | src/lib.h | 7 | ||||
-rw-r--r-- | src/parser.h | 6 | ||||
l--------- | src/pbg.c | 1 | ||||
l--------- | src/pbg.h | 1 | ||||
-rw-r--r-- | src/search.c | 118 | ||||
-rw-r--r-- | src/search.h | 2 | ||||
-rw-r--r-- | src/search_stuff.c | 126 | ||||
l--------- | src/teeny-sha1.c | 1 | ||||
-rw-r--r-- | src/types.c | 64 | ||||
-rw-r--r-- | src/types.h | 7 |
23 files changed, 745 insertions, 32 deletions
@@ -1,2 +1,3 @@ hs/* -food
\ No newline at end of file +food +cookbook
\ No newline at end of file @@ -1,13 +1,13 @@ CC=gcc -CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic -O3 -DBG_FLAGS=-Wall -g3 -D_FOOD_DEBUG -fsanitize=address -Og +CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic +DBG_FLAGS=-Wall -g -D_FOOD_DEBUG -fsanitize=address -O0 SRC=src/*.c .PHONY: all debug clean -all: food #eval +all: food cookbook #eval food: $(SRC) food.c $(CC) $(CFLAGS) -o food $^ $(LIBS) @@ -15,12 +15,15 @@ food: $(SRC) food.c eval: $(SRC) eval.c $(CC) $(CFLAGS) -o eval $^ $(LIBS) +cookbook: $(SRC) cookbook.c + $(CC) $(CFLAGS) -o cookbook $^ $(LIBS) + debug: CFLAGS += $(DBG_FLAGS) debug: all clean: rm -f *.o - rm -f food eval + rm -f food eval cookbook # tests: # $(MAKE) -C test @@ -81,3 +81,13 @@ } #+end_src + +* Food compiler + + recipes are text files defined above + + ingredients and results from steps are variables linking back to the quantity or the step that produced them + + the food compiler parses these files into internal C structures, the errors reported are syntax ones + + then they can be eval'd which merges any subrecipe ingredients and steps into one plain recipe diff --git a/cookbook.c b/cookbook.c new file mode 100644 index 0000000..ca435ec --- /dev/null +++ b/cookbook.c @@ -0,0 +1,162 @@ +#include <getopt.h> + +#include "src/util.h" +#include "src/parser.h" +#include "src/search.h" +#include "src/eval.h" +#include "src/lib.h" + +recipe ** cookbook; + +static struct opts { + int json; + int html; + int rcp; + char query[2048]; + int eval; + int list; + int hash; + int search; + int search_strict; + int help; +} opt = { + .json = 0, + .html = 0, + .rcp = 0, + .query = "", + .eval = 1, + .list = 0, + .hash = 0, + .search = 0, + .search_strict = 0, + .help = 0, +}; + +void +print_help(char * argv0) { + printf("%s [OPTION] FILE ...\n", argv0); + printf("OPTIONS:\n"); + printf("--format json,html,rcp\n"); + printf("--to-{json,html,rcp}\n"); + printf("-j json\n"); + printf("-w html\n"); + printf("-r rcp\n"); + printf("-l, --list-ingredients\n"); + printf("-s TERMS, --search TERMS\n"); + printf("-S TERMS, --strict TERMS\n"); + printf("-n, --no-eval\n"); +} + +int +main(int argc, char * argv[]) +{ + fdebug("--- Debug mode is on ---\n"); + + int c; + + while (1) { + static struct option long_options[] = + { + /* <span class="roman">These options set a flag.</span> */ + // {"verbose", no_argument, &verbose_flag, 1}, + // {"brief", no_argument, &verbose_flag, 0}, + {"help", no_argument, 0, 'h'}, + {"no-eval", no_argument, 0, 'n'}, + {"to-json", no_argument, 0, 'j'}, + {"to-html", no_argument, 0, 'w'}, + {"to-rcp", no_argument, 0, 'r'}, + {"format", required_argument, 0, 'f'}, + {"search", required_argument, 0, 's'}, + {"strict", required_argument, 0, 'S'}, + {"hash", required_argument, 0, 'H'}, + {"list-ingredients", no_argument, 0, 'l'}, + // {"to-rcp", required_argument, 0, 'r'}, + {0, 0, 0, 0} + }; + + int option_index = 0; + + c = getopt_long (argc, argv, "jnlhrwf:s:S:H:", + long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 0: + if (long_options[option_index].flag != 0) + break; + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + case 's': + opt.search = 1; + strcpy(opt.query, optarg); + break; + case 'S': + opt.search = 1; + opt.search_strict = 1; + strcpy(opt.query, optarg); + break; + case 'H': + opt.hash = 1; + strcpy(opt.query, optarg); + break; + case 'j': + opt.json = 1; + break; + case 'w': + opt.html = 1; + break; + case 'r': + opt.rcp = 1; + break; + case 'l': + opt.list = 1; + break; + case 'h': + opt.help = 1; + break; + case 'n': + opt.eval = 0; + break; + case '?': + return -1; + break; + default: + abort (); + } + } + + if (opt.help) { + print_help(argv[0]); + return 0; + } + + char ** lib = NULL; + int n = collect_library(&lib, argv, argc, optind); + + cookbook = (recipe **)malloc(sizeof(recipe *) * n); + for (int i = 0; i < n; i++) { + cookbook[i] = parse(lib[i], NULL); + if (opt.eval) { + recipe * _r = eval(cookbook[i]); + free_recipe(cookbook[i]); + cookbook[i] = _r; + } + } + free_library(lib, n); + + for (int i = 0; i < n; i++) { + listing(cookbook[i]); + } + + for (int i = 0; i < n; i++) { + free_recipe(cookbook[i]); + } + free(cookbook); + + return 0; +} diff --git a/emacs/recipe-mode.el b/emacs/recipe-mode.el new file mode 100644 index 0000000..c38008a --- /dev/null +++ b/emacs/recipe-mode.el @@ -0,0 +1,67 @@ +;;; recipe-mode.el --- major mode for foodtool's recipe files -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 + +;; Author: <gramanas@EYE> +;; Keywords: files, data + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(eval-when-compile + (require 'rx)) + +(setq recipe-highlights + '( + ;; comment + ("^ *#+.*$" . 'font-lock-comment-delimiter-face) + ;; title + ("^ *@\\(.*\\)$" . (1 'font-lock-preprocessor-face)) + ;; duration + ("\\[\\(.+?\\)\\]" . (1 'font-lock-builtin-face)) + ;; quantity measurements + ("= *[[:digit:]/]+\\([ [:graph:]]+?\\)$" . (1 'font-lock-builtin-face)) + ;; variable in assignment + ("^\\(.+?\\)=.+?$" . (1 'font-lock-variable-name-face)) + ;; variable in ${} + ("\\${\\(.+?\\)}" . (1 'font-lock-variable-name-face)) + ;; single word variable + ("\\$\\([^ {\n]+\\)" . (1 'font-lock-variable-name-face)) + ;; step result + ("=>\\(.*\\)" . (1 'font-lock-variable-name-face)) + ;; include + ("^[ [:digit:]]*!" . 'font-lock-constant-face) + ;; symbols + ("@\\|-\\|>\\|+\\|=>" . 'font-lock-constant-face) + )) + +(define-derived-mode recipe-mode prog-mode "rcp" + "Mode for rcp files +\\{recipe-mode-map}" + + ;;(setq-local font-lock-defaults '(conf-font-lock-keywords nil t nil nil)) + (setq-local font-lock-defaults '(recipe-highlights)) + (setq-local comment-use-syntax nil) + (setq-local comment-start "#") + (setq-local parse-sexp-ignore-comments t)) + +(add-to-list 'auto-mode-alist '("\\.rcp\\'" . recipe-mode)) + +(provide 'recipe-mode) +;;; recipe-mode.el ends here @@ -4,6 +4,7 @@ #include "src/parser.h" #include "src/search.h" #include "src/eval.h" +#include "src/lib.h" recipe ** cookbook; @@ -14,6 +15,7 @@ static struct opts { char query[2048]; int eval; int list; + int hash; int search; int search_strict; int help; @@ -24,6 +26,7 @@ static struct opts { .query = "", .eval = 1, .list = 0, + .hash = 0, .search = 0, .search_strict = 0, .help = 0, @@ -65,6 +68,7 @@ main(int argc, char * argv[]) {"format", required_argument, 0, 'f'}, {"search", required_argument, 0, 's'}, {"strict", required_argument, 0, 'S'}, + {"hash", required_argument, 0, 'H'}, {"list-ingredients", no_argument, 0, 'l'}, // {"to-rcp", required_argument, 0, 'r'}, {0, 0, 0, 0} @@ -72,7 +76,7 @@ main(int argc, char * argv[]) int option_index = 0; - c = getopt_long (argc, argv, "jnlhrwf:s:S:", + c = getopt_long (argc, argv, "jnlhrwf:s:S:H:", long_options, &option_index); if (c == -1) @@ -106,6 +110,10 @@ main(int argc, char * argv[]) opt.search_strict = 1; strcpy(opt.query, optarg); break; + case 'H': + opt.hash = 1; + strcpy(opt.query, optarg); + break; case 'j': opt.json = 1; break; @@ -137,33 +145,49 @@ main(int argc, char * argv[]) return 0; } - if (optind < argc) { - while (optind < argc) { - recipe * r = parse(argv[optind++], NULL); - if (r) { - if (opt.search) { - if (!query_for_items(r, opt.query, opt.search_strict)) - continue; - } - if (opt.eval) { - recipe * r_eval = eval(r); + char ** lib = NULL; + int n = collect_library(&lib, argv, argc, optind); + + for (int i = 0; i < n; i++) { + recipe * r = parse(lib[i], NULL); + if (r) { + if (opt.eval) { + recipe * r_eval = eval(r); + free_recipe(r); + r = r_eval; + } + if (opt.hash) { + if (!check_hash(r, opt.query)) { free_recipe(r); - r = r_eval; + continue; } - if (opt.list) { - pprint_items(r); + } + if (opt.search) { + int c; + if (!(c = query_for_items_pbn(r, opt.query, opt.search_strict))) { + free_recipe(r); + continue; } + if (c < 0) + exit(1); + } + + if (opt.list) { + pprint_items(r); + } + else { + if (opt.json) tojson(r); + else if (opt.html) tohtml(r); + else if (opt.rcp) torcp(r); else { - if (opt.json) tojson(r); - if (opt.html) tohtml(r); - if (opt.rcp) torcp(r); + listing(r); } - free_recipe(r); } + free_recipe(r); } - } else { - fprintf(stderr, "Specify filenames\n"); } + free_library(lib, n); + return 0; } diff --git a/lib/minced-meat.rcp b/lib/minced-meat.rcp new file mode 100644 index 0000000..1b25998 --- /dev/null +++ b/lib/minced-meat.rcp @@ -0,0 +1,32 @@ +@minced meat + +minced veal = 400g +minced pork = 100g + +salt, pepper = * +kayen pepper = 1/2 tbsp +cinamon = 1/2 tbsp +cumin = 1/2 tbsp +bay leaf = 2 + +sun dried tomato = 6 pieces + +white onion = 1 +garlic = 2 cloves +carrot = 1 small + +white wine = 1/2 glass +tomato sauce = 2 cans +olive oil = 3 tbsp + +--- + +- chop the ${white onion} and $garlic, slice the $carrot +> heat ${olive oil} in a pan and add ${white onion} and $garlic and $salt and [brown for 3 minutes] +> add the $carrot and [stir until well browned] +> add the ${minced pork} and ${minced veal} +> add the $pepper ${kayen pepper} $cinamon $cumin ${bay leaf} +> stir [until the meat is brown and starts to stick on the pan] +> deglaze with ${white wine} and stir [until most wine has evaporated] +> add the ${tomato sauce} and stir, if too thick add some water +> [cook for 30 minutesd] diff --git a/lib/pasta-red-sauce.rcp b/lib/pasta-red-sauce.rcp index 2b56894..a57f30c 100644 --- a/lib/pasta-red-sauce.rcp +++ b/lib/pasta-red-sauce.rcp @@ -1,4 +1,4 @@ -@pasta with red sauce 3
+@ pasta with red sauce 3
3!pasta.rcp
salt = *
@@ -14,8 +14,7 @@ olive oil = * - prepare $pasta
> heat some ${olive oil} in the pan
> fry $garlic [until light brown]
-> add $onion and some $salt, fry [until \
-onion is translucent]
+> add $onion and some $salt, fry [until onion is translucent]
> deglaze with the $wine and cook [until it reduces in half]
> add the ${tomato sauce}, and some warm water to rinse the can
> add $pasta to the sauce, turn the heat off, and cook [for 1-2 minutes]
diff --git a/lib/pasta-with-minced-meat.rcp b/lib/pasta-with-minced-meat.rcp new file mode 100644 index 0000000..3a4a6bc --- /dev/null +++ b/lib/pasta-with-minced-meat.rcp @@ -0,0 +1,4 @@ +@ pasta with minced meat + +!pasta.rcp +!minced-meat.rcp diff --git a/lib/sandwich-base.rcp b/lib/sandwich-base.rcp index 98568a7..7a485b2 100644 --- a/lib/sandwich-base.rcp +++ b/lib/sandwich-base.rcp @@ -5,5 +5,5 @@ butter or mayo = * --- -- toast the $bread => toasted bread +- toast the $bread [for 3 minutes] => toasted bread - spead the ${butter or mayo} on the ${toasted bread} diff --git a/lib/sandwich-sour.rcp b/lib/sandwich-sour.rcp new file mode 100644 index 0000000..de06b3d --- /dev/null +++ b/lib/sandwich-sour.rcp @@ -0,0 +1,13 @@ +@ sandwich with sun dried tomatoes + +!sandwich-base.rcp + +sun dried tomates = 3 +turkey = 2pcs +cheddar = 2pcs + +--- + +> chop the ${sun dried tomatoes} into litle pieces +> slice the $turkey and ${cheddar} in half ++ assembly order: bread mayo tomatoes cheddar turkey bread @@ -1,5 +1,25 @@ #include "eval.h" #include "util.h" +#include <stdint.h> + +extern int +sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes); + +void +create_hash(recipe * r) +{ + char data[65536] = "\0"; + + for (int i = 0; i < r->in; i++) { + strcat(data, r->i[i]->name); + strcat(data, r->i[i]->qty); + } + if (r->sn) { + for (int i = 0; i < r->sn; i++) + strcat(data, r->s[i]->inst); + } + sha1digest(NULL, r->sha1, (uint8_t *)data, strlen(data)); +} recipe * eval(recipe * r) @@ -10,7 +30,10 @@ eval(recipe * r) merge_items(_r, r); merge_steps(_r, r); copy_metadata(_r, r); + copy_subrecipes(_r, r); + create_hash(_r); + return _r; } diff --git a/src/lib.c b/src/lib.c new file mode 100644 index 0000000..f43bdc2 --- /dev/null +++ b/src/lib.c @@ -0,0 +1,48 @@ +#include "lib.h" +#include "util.h" + +int +collect_library(char *** dst, char * argv[], int argc, int optind) +{ + int n = 0; + FILE *fp; + char path[1035]; + + char ** lib = NULL; + + for (int i = optind; i < argc; i++) { + lib = realloc(lib, sizeof(char **) * (n + 1)); + lib[n] = strdup(argv[i]); + fdebug("%d: %s\n", n, lib[n]); + n = n + 1; + } + + fp = popen("/bin/find /home/gramanas/code/foodtools/lib/ -name '*.rcp'", "r"); + if (fp == NULL) { + fprintf(stderr, "Couldn't run /bin/find\n"); + exit(1); + } + + /* Read the output a line at a time */ + while (fgets(path, sizeof(path), fp) != NULL) { + lib = realloc(lib, sizeof(char **) * (n + 1)); + trim(path); + lib[n] = strdup(path); + fdebug("%d: %s\n", n, lib[n]); + n = n + 1; + } + + /* close */ + pclose(fp); + + *dst = lib; + return n; // no of items +} + +void +free_library(char ** lib, int n) { + for (int i = 0; i < n; i++) { + free(lib[i]); + } + free(lib); +} diff --git a/src/lib.h b/src/lib.h new file mode 100644 index 0000000..005a330 --- /dev/null +++ b/src/lib.h @@ -0,0 +1,7 @@ +#ifndef __LIB_H +#define __LIB_H + +int collect_library(char *** lib, char * argv[], int argc, int optind); +void free_library(char ** lib, int n); + +#endif /* __LIB_H */ diff --git a/src/parser.h b/src/parser.h index 2680da5..d47bad5 100644 --- a/src/parser.h +++ b/src/parser.h @@ -5,6 +5,12 @@ #define LINE_SIZE 4096 +/** +* Return a recipe struct after parsing file in path. +* +* Path can be relative or full, ~ will be resolved as $HOME, and - will accept +* input from stdin +*/ recipe * parse(char * path, const char * prev); diff --git a/src/pbg.c b/src/pbg.c new file mode 120000 index 0000000..c707a6b --- /dev/null +++ b/src/pbg.c @@ -0,0 +1 @@ +../pbg/pbg.c
\ No newline at end of file diff --git a/src/pbg.h b/src/pbg.h new file mode 120000 index 0000000..36a1da6 --- /dev/null +++ b/src/pbg.h @@ -0,0 +1 @@ +../pbg/pbg.h
\ No newline at end of file diff --git a/src/search.c b/src/search.c index cf2eab9..a038cfb 100644 --- a/src/search.c +++ b/src/search.c @@ -1,5 +1,115 @@ #include "util.h" #include "search.h" +#include "pbg.h" + +static char * +form_string(const recipe * r, const char * s, int strict) +{ + char _s[4048] = ""; + int l = 0; + char buf[256] = ""; + + for (int i = 0; i < strlen(s); i++) { + if (s[i] == '(' || + s[i] == ')' || + s[i] == ' ') { + if (strlen(buf)) { + if (!strcmp(buf, "not") || + !strcmp(buf, "NOT") || + !strcmp(buf, "!")) { + _s[l++] = '!'; + } + else if (!strcmp(buf, "and") || + !strcmp(buf, "AND") || + !strcmp(buf, "&&") || + !strcmp(buf, "&")) { + _s[l++] = '&'; + } + else if (!strcmp(buf, "or") || + !strcmp(buf, "OR") || + !strcmp(buf, "||") || + !strcmp(buf, "|")) { + _s[l++] = '|'; + } + else { + fdebug("querying %s for %s\n", r->title, buf); + if (query_for_items(r, buf, strict)) { + strcat(_s, "TRUE"); + l+=4; + } + else { + strcat(_s, "FALSE"); + l+=5; + } + } + strcpy(buf, ""); + } + _s[l++] = s[i]; + } + else { + int k = strlen(buf); + buf[k] = s[i]; + buf[k+1] = '\0'; + } + } + _s[l] = '\0'; + + return strdup(_s); +} + +int +query_for_items_pbn(const recipe * r, const char * s, int strict) +{ + if (!strlen(s)) { + return 0; + } + + char * query = NULL; + + if (s[0] != '(') { + if (strstr(s, "and ") || + strstr(s, "or ") || + strstr(s, "not ")) { + query = (char *)malloc(sizeof(char) * strlen(s) + 3); + strcpy(query, "("); + strcat(query, s); + strcat(query, ")"); + } else { + return query_for_items(r, s, strict); + } + } else { + query = strdup(s); + } + + pbg_error err; + pbg_expr e; + int result; + + fdebug("PBG Query string: %s\n", query); + char* str = form_string(r, query, strict); + fdebug("PBG Query eval'd string: %s\n", str); + + /* Parse the expression string and check if + * there were any compilation errors. */ + pbg_parse(&e, &err, str); + if(pbg_iserror(&err)) { + fprintf(stderr, "Parsing error: %s => %s\n", s, str); + free(query); free(str); free(err._data); + return -1; + } + + /* Evaluate the expression string and check if + * there were any runtime errors. */ + result = pbg_evaluate(&e, &err, NULL); + if(pbg_iserror(&err)) { + fprintf(stderr, "Eval error: %s => %s\n", s, str); + free(query); free(str); pbg_free(&e); free(err._data); + return -1; + } + + free(query); free(str); pbg_free(&e); + return (result == PBG_TRUE) ? 1 : 0; +} /** * Query recipe `r` for input `s` and return 1 if found 0 otherwise @@ -17,3 +127,11 @@ query_for_items(const recipe * r, const char * s, int strict) } return 0; } + +int +check_hash(const recipe * r, const char * s) +{ + char _sha1[9]; + sprintf(_sha1, "%.*s", 8, r->sha1); + return (!strcmp(s, _sha1) || !strcmp(s, r->sha1)) ? 1 : 0; +} diff --git a/src/search.h b/src/search.h index 1295cd4..1c7c61f 100644 --- a/src/search.h +++ b/src/search.h @@ -3,6 +3,8 @@ #include "types.h" +int check_hash(const recipe * r, const char * s); int query_for_items(const recipe * r, const char * s, int strict); +int query_for_items_pbn(const recipe * r, const char * s, int strict); #endif diff --git a/src/search_stuff.c b/src/search_stuff.c new file mode 100644 index 0000000..5d4cb6c --- /dev/null +++ b/src/search_stuff.c @@ -0,0 +1,126 @@ +#include "util.h" +#include "search.h" + +char stk[4048]; +int p = 0; + +/* + * expr : (expr) | expr op expr | not expr | term + * op : and | or + * term : <string> + * + */ + +void +reverse_str(char ** s) +{ + int l = strlen(*s); + char * rev_s = strdup(*s); + int j = 0; + for (int i = l - 1; i >= 0; i--, j++) { + rev_s[j] = (*s)[i]; + } + rev_s[j] = '\0'; + free(*s); + *s = rev_s; +} + + +/* + * Implementing https://www.javatpoint.com/convert-infix-to-prefix-notation + * + * ascii values: + * & -> 38 + * | -> 124 + * ( -> 40 + * ) -> 41 + * a-z -> 97-122 + * A-Z -> 64-90 + * + * precidence: + * & > | + * (http://www.cs.ucc.ie/~gavin/cs1001/Notes/chap40/ch40_16.html) + */ +int +precedence(char c) +{ + switch (c) { + case '&': + return 100; + case '|': + return 50; + default: + return 0; + } +} + +void +infix_to_prefix(char ** s) +{ + char * prefixed = malloc(sizeof(char) * strlen(*s) * 2); + prefixed[0] = '\0'; + char * rev_s = strdup(*s); + reverse_str(&rev_s); + + for (int i = 0; i < strlen(rev_s); i++) { + if (rev_s[i] == '&' || + rev_s[i] == '|') { + if (p == 0) { + stk[p++] = rev_s[i]; + } else { + if (precedence(rev_s[i] > precedence(stk[p - 1]))) { + stk[p++] = rev_s[i]; + } else { + int k = strlen(prefixed); + for (int j = p; j > 0 || precedence(rev_s[i]) < precedence(stk[p - 1]); j--) { + prefixed[k++] = stk[j - 1]; + p--; + } + prefixed[k] = '\0'; + stk[p++] = rev_s[i]; + } + } + } + else if (rev_s[i] == '(') { + stk[p++] = rev_s[i]; + } + else if (rev_s[i] == ')') { + int k = strlen(prefixed); + prefixed[k++] = rev_s[i]; + for (int j = p; j > 0 && stk[j] != ')'; j--) { + prefixed[k++] = stk[j - 1]; + p--; + } + prefixed[k] = '\0'; + } + else { + int k = strlen(prefixed); + prefixed[k] = rev_s[i]; + prefixed[k+1] = '\0'; + } + } + + int k = strlen(prefixed); + prefixed[k++] = ' '; // add space after the first operator + for (int j = p; j > 0; j--) { + prefixed[k++] = stk[j - 1]; + p--; + } + prefixed[k] = '\0'; + + printf("rev %s\n", prefixed); + for (int i = 0; i < strlen(prefixed); i++) { + if (prefixed[i] == '(' && + i < strlen(prefixed) && + (prefixed[i+1] == '&' || prefixed[i+1] == '|')) { + prefixed[i] = prefixed[i+1]; + prefixed[i+1] = '('; + } + } + + printf("rev fixed %s\n", prefixed); + reverse_str(&prefixed); + free(rev_s); + free(*s); + *s = prefixed; +} diff --git a/src/teeny-sha1.c b/src/teeny-sha1.c new file mode 120000 index 0000000..4208eb3 --- /dev/null +++ b/src/teeny-sha1.c @@ -0,0 +1 @@ +../teeny-sha1/teeny-sha1.c
\ No newline at end of file diff --git a/src/types.c b/src/types.c index dbc7458..3233323 100644 --- a/src/types.c +++ b/src/types.c @@ -21,6 +21,7 @@ new_recipe() r->filename = NULL; r->path = NULL; r->title = NULL; + r->sha1[0] = '\0'; return r; } @@ -137,6 +138,12 @@ pprint_items(recipe * r) } void +listing(recipe * r) +{ + printf("%.*s %d:%d\t%d\t%s\n", 8, (strcmp(r->sha1, "") ? r->sha1 : "neval"), r->in, r->sn, r->rn, r->title); +} + +void show(recipe * r) { printf("Filename\t%s\n", r->filename); @@ -158,7 +165,9 @@ tojson(recipe * r) printf("{\"filename\":\"%s\",", r->filename); printf("\"dirname\":\"%s\",", r->path); printf("\"title\":\"%s\",", r->title); - printf("\"n\":\"%d\"", r->n); + printf("\"n\":\"%d\",", r->n); + printf("\"sha1_short\":\"%.*s\",", 8, r->sha1); + printf("\"sha1\":\"%s\"", r->sha1); if (r->rn) { printf(",\"subrecipes\":["); int i = 0; @@ -189,16 +198,51 @@ tojson(recipe * r) void tohtml(recipe * r) { - printf("todo\n"); + printf("<div class=\"recipe\">\n"); + printf("<!-- %s/%s -->\n", r->path, r->filename); + printf("<h1>%s</h1>\n\n", r->title); + if (r->rn) { + printf("<ul class=\"list items subrecipes\">\n"); + for (int i = 0; i < r->rn; i++) { + printf("<li>!%s/%s</li>\n", r->r[i]->path, r->r[i]->filename); + } + printf("</ul>\n"); + } + if (r->in) { + printf("<ul class=\"list items\">\n"); + for (int i = 0; i < r->in; i++) { + printf("<li>%s = %s</li>\n", r->i[i]->name, r->i[i]->qty); + } + printf("</ul>\n"); + } + if (r->sn) { + printf("<br><p>---</p><br>\n"); + printf("<ul class=\"list steps\">\n"); + for (int i = 0; i < r->sn; i++) { + char c; + if (r->s[i]->type == PREP) + c = '-'; + else if (r->s[i]->type == COOK) + c = '>'; + else + c = '+'; + printf("<li>%c %s</li>\n", c, r->s[i]->inst); + } + printf("</ul>\n"); + } + printf("<hr>\n</div>\n"); } void torcp(recipe * r) { printf("# %s/%s\n\n", r->path, r->filename); + if (r->sha1[0] != '\0') { + printf("# %s\n\n", r->sha1); + } printf("@%s\n\n", r->title); for (int i = 0; i < r->rn; i++) { - printf("!%s/%s\n", r->r[i]->path, r->r[i]->filename); + printf("%s!%s/%s\n", (r->sha1[0] != '\0') ? "# ": "", r->r[i]->path, r->r[i]->filename); } for (int i = 0; i < r->in; i++) printf("%s = %s\n", r->i[i]->name, r->i[i]->qty); @@ -218,6 +262,20 @@ torcp(recipe * r) } void +copy_subrecipes(recipe * dst, recipe * src) +{ + if (!dst || !src) return; + for (int i = 0; i < src->rn; i++) { + recipe * r = new_recipe(); + r->title = strdup(src->r[i]->title); + r->path = strdup(src->r[i]->path); + r->filename = strdup(src->r[i]->filename); + r->n = src->r[i]->n; + new_subrecipe(dst, r); + } +} + +void copy_metadata(recipe * dst, recipe * src) { if (!dst || !src) return; diff --git a/src/types.h b/src/types.h index 2c58537..867368b 100644 --- a/src/types.h +++ b/src/types.h @@ -30,6 +30,7 @@ typedef struct recipe_t { int sn; struct recipe_t **r; int rn; + char sha1[41]; } recipe; recipe * new_recipe(); @@ -42,6 +43,7 @@ void free_item(item * i); void free_step(step * s); void pprint_items(recipe * r); +void listing(recipe * r); void show(recipe * r); void tojson(recipe * r); void tohtml(recipe * r); @@ -52,6 +54,11 @@ void torcp(recipe * r); /** * Copy metadata from `src` to `dst` */ +void copy_subrecipes(recipe * dst, recipe * src); + +/** + * Copy metadata from `src` to `dst` + */ void copy_metadata(recipe * dst, recipe * src); /** |