#include #include "parser.h" #include "util.h" typedef enum parser_type { TITLE = 0, ITEMS, STEPS, } pt; static const int parse_title(const char * s, recipe * r, pt * type) { fdebug("^ title\n"); if (s[0] == '@' && strlen(s) > 1) { char tmp[LINE_SIZE] = ""; strcpy(tmp, ++s); trim(tmp); r->title = strdup(tmp); } else { r->title = strdup(r->filename); } *type = ITEMS; return 0; } static const int parse_item(const char * s, recipe * r, pt * type, char * error) { fdebug("^ item\n"); if (!strcmp(s, "---")) { *type = STEPS; return 0; } if (s[0] == '!') { fdebug("INCLUDING: %s\n", s); char tmp[LINE_SIZE] = ""; char path[LINE_SIZE] = ""; strcpy(tmp, ++s); trim(tmp); if (!strlen(tmp)) { sprintf(error, "malformed include: %s", s); return 1; } if (tmp[0] == '/') { strcpy(path, tmp); } else { strcpy(path, r->path); strcat(path, "/"); strcat(path, tmp); } recipe * rr = parse(path, r->filename); if (!rr) { sprintf(error, "Couldn't include %s", path); return 1; } copy_items(r, rr); free_recipe(rr); return 0; } int l = strlen(s); int val = 1; /* key vs value flag */ int itemc = 0; int c = 0; char buf[LINE_SIZE] = ""; for (int i = 0; i < l; i++) { if (val) { if (s[i] == ',' || s[i] == '=') { buf[c++] = '\0'; if (!strlen(buf)) { sprintf(error, "malformed ingredient: %s", s); return 1; } trim(buf); if (strlen(buf)) { new_item(r); r->i[r->in - 1]->name = strdup(buf); itemc++; buf[0] = '\0'; c = 0; } if (s[i] == '=') val = 0; continue; } } buf[c++] = s[i]; } buf[c] = '\0'; trim(buf); if (!strlen(buf)) { sprintf(error, "empty ingredient quantity: %s", s); return 1; } for (int i = 0; i < itemc; i++) { r->i[r->in - 1 - i]->qty = strdup(buf); } return 0; } static const int parse_step(const char * s, recipe * r, char * error) { fdebug("^ step\n"); if ((s[0] == '>' || s[0] == '-') && strlen(s+1)) { new_step(r); r->s[r->sn - 1]->type = s[0] == '>' ? COOK : PREP; char *tmp = strdup(s+1); trim(tmp); r->s[r->sn - 1]->inst = tmp; return 0; } sprintf(error, "malformed step: %s", s); return 1; } static const int handle_line(const char * s, recipe * r, char * error, pt * type, const char * prev) { fdebug("Handling line: %s\n", s); if (strlen(s) == 0) return 0; if (s[0] == '#') return 0; switch (*type) { case TITLE: if (!parse_title(s, r, type)) if (parse_item(s, r, type, error)) return 1; break; case ITEMS: if (parse_item(s, r, type, error)) return 1; break; case STEPS: if (parse_step(s, r, error)) return 1; } return 0; } static const int next_escaped_line(FILE *f, char * line, int * lino, char * error) { if (!fgets(line, LINE_SIZE, f)) return 1; (*lino)++; if (line[strlen(line) - 1] != '\n' && !feof(f)) { sprintf(error, "line exceeds %d characters", LINE_SIZE); return -1; } trim(line); int l = strlen(line); /* Last char is `\n` check if */ /* (last - 1) is `\` */ while (l > 0 && line[l - 1] == '\\') { char tmp[LINE_SIZE] = ""; if (!fgets(tmp, LINE_SIZE, f)) /* return line on EOF */ break; (*lino)++; trim(tmp); if (strlen(tmp) + l >= LINE_SIZE) { sprintf(error, "line exceeds %d characters", LINE_SIZE); return -1; } /* remove `\` */ line[l - 1] = '\0'; strcat(line, tmp); l = strlen(line); } return 0; } recipe * parse(char * path, const char * prev) { fdebug("Parsing: %s\n", path); FILE *f; if (!strcmp(path, "-")) { f = stdin; } else { f = fopen(path, "r"); if (!f) return NULL; } recipe * r = new_recipe(); char line[LINE_SIZE] = ""; char *dirc, *bname; dirc = strdup(path); bname = strdup(path); r->path = strdup(dirname(dirc)); r->filename = strdup(basename(bname)); free(dirc); free(bname); /* (re)init px */ char error[LINE_SIZE] = ""; int lino = 0; enum parser_type type = TITLE; while (!next_escaped_line(f, line, &lino, error)) { if (handle_line(line, r, error, &type, prev)) break; } if (strlen(error) > 0) { fprintf(stderr, "%s:%d: %s\n", path, lino, error); free_recipe(r); return NULL; } return r; }