/** * quart -- manage the day one quarter at a time * Copyright (C) 2019 Anastasis Grammenos * * 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #define BUFFER_LEN 8192 static char buf[BUFFER_LEN] = ""; static char prev[BUFFER_LEN] = ""; static uint line = 0; #define FERR(format, ...) do { \ fprintf(stderr, "%s:%d error: ", ctx.file, line); \ fprintf(stderr, format, __VA_ARGS__); \ fprintf(stderr, "\n"); } while(0); typedef enum mods { NONE = 0, URGENT, MAYBE, DEPENDS, } mods; typedef struct Q { time_t start; char **entry; mods *m; unsigned int n; } Q; struct context { char *file; int later; int current; int next; int remaining; }; static struct context ctx = { NULL, 0, 0, 0, 1 }; void trim(char *str) { int i; int begin = 0; int end = strlen(str) - 1; while (isspace((unsigned char) str[begin])) begin++; while ((end >= begin) && isspace((unsigned char) str[end])) end--; // Shift all characters back to the start of the string array. for (i = begin; i <= end; i++) str[i - begin] = str[i]; str[i - begin] = '\0'; // Null terminate string. } static void print_help(char *argv0) { fprintf(stderr, "quart - manage the day one quarter at a time\n"); fprintf(stderr, "Copyright (C) 2019 Anastasis Grammenos\n"); fprintf(stderr, "This program is licenced under GPLv2. See source tree for details.\n\n"); fprintf(stderr, "Usage:\n~~~~~~\n"); fprintf(stderr, "%s [file] [...]\n\n", argv0); fprintf(stderr, "%8s %4s %10s %55s\n", "Options", "", "Mnemonic", "Description"); fprintf(stderr, "%8s %4s %10s %55s\n", "~~~~~~~", "", "~~~~~~~~", "~~~~~~~~~~~"); fprintf(stderr, "%8s %4s %10s %55s\n", "-h", "", "help", "Print this help message"); fprintf(stderr, "%8s %4s %10s %55s\n\n", "file", "[..]", "", "The file to parse the daily schedule from"); fprintf(stderr, "%8s %4s %10s %55s\n", "-c", "", "current", "Print what you should be doing now"); fprintf(stderr, "%8s %4s %10s %55s\n", "-n", "", "next", "Print what you should be doing next"); fprintf(stderr, "%8s %4s %10s %55s\n", "-l", "[..]", "later", "Print what you should be doing `l` quarters later"); fprintf(stderr, "%8s %4s %10s %55s\n", "", "", "", "(Accepts numbers. -l 1 is the same as -n)"); fprintf(stderr, "%8s %4s %10s %55s\n", "-s", "", "summary", "Print the day's summary"); fprintf(stderr, "%8s %4s %10s %55s\n", "-r", "", "remaining", "Print what remains to be done (The default)"); } static int parse_cli(const int argc, char * const argv[]) { if (argc < 2) return -1; char c; while ((c = getopt(argc, argv, "-schrnl:")) != -1) { switch (c) { case 'h': print_help(argv[0]); return -1; case '\1': ctx.file = optarg; break; case 's': ctx.remaining = 0; ctx.current = 0; ctx.next = 0; ctx.later = 0; break; case 'c': ctx.remaining = 0; ctx.current = 1; ctx.later = 0; ctx.next = 0; break; case 'n': ctx.remaining = 0; ctx.next = 1; ctx.current = 0; ctx.later = 0; break; case 'l': ctx.remaining = 0; ctx.next = 0; ctx.current = 0; ctx.later = atoi(optarg); break; case 'r': ctx.remaining = 1; ctx.next = 0; ctx.current = 0; ctx.later = 0; break; default: return -1; } } return 0; } void initQ(Q *q) { q->n = 0; q->start = 0; q->entry = (char **) malloc(sizeof(char *)); q->m = (mods *) malloc(sizeof(mods)); } void freeQ(Q *q) { for (uint i = 0; i < q->n; i++) free(q->entry[i]); free(q->entry); free(q->m); } /** * str must be in the form of 00:00 */ int str_to_time(time_t *t, char *str) { char *c = strtok(str, ":"); if (!c) return -1; uint hour = atoi(c); if (hour > 23) return -1; c = strtok(NULL, ":"); if (!c) return -1; uint minute = atoi(c); if (minute > 59) return -1; time_t tmp; time(&tmp); struct tm *tt = localtime(&tmp); tt->tm_hour = hour; tt->tm_min = minute; tt->tm_sec = 0; *t = mktime(tt); if (*t == -1) return -1; return 0; } int parse_header(Q *q) { char *c = strtok(buf, " "); if (!c) return -1; if ((strcmp(c, "#+start:") != 0) && (strcmp(c, "#+START:") != 0)) return -1; c = strtok(NULL, " "); if (!c) return -1; if (str_to_time(&q->start, c)) { FERR("can't convert string \"%s\" to timestamp", c); return -1; } return 0; } int add(Q *q) { const char mod = buf[0]; char *ret = strchr(buf, mod); if (ret) { switch(mod) { case '!': q->m[q->n] = URGENT; break; case '?': q->m[q->n] = MAYBE; break; case '@': q->m[q->n] = DEPENDS; break; default: q->m[q->n] = NONE; } if (q->m[q->n] != NONE) { ret++; trim(ret); } q->entry[q->n] = strdup(ret); q->n++; return 0; } return -1; } int add_entry(Q *q) { char *str = NULL; q->entry = (char **)realloc(q->entry, sizeof(char *) * (q->n + 1)); q->m = (mods *)realloc(q->m, sizeof(mods) * (q->n + 1)); if (buf[0] == '=') { if (strcmp(prev,"") == 0) FERR("\"%s\" there is no previous entry", buf); strcpy(buf, prev); return add(q); } else if (buf[0] == '-') { q->entry[q->n] = strdup(""); q->m[q->n] = NONE; q->n++; return 0; } else { if (add(q)) return -1; } if (str) strcpy(prev, str); else strcpy(prev, buf); return 0; } int parseQ(FILE *f, Q *q) { initQ(q); int head = 0; int entries = 0; while (fgets(buf, BUFFER_LEN, f) != NULL) { line++; trim(buf); if (buf[0] == '#' && buf[1] == '+') { if (entries) { FERR("\"%s\" can't put header information inside entry list", buf); return -1; } if (parse_header(q)) { FERR("\"%s\" wrong header", buf); return -1; } head = 1; } else if (buf[0] == '#') { continue; } else if (buf[0] == '*') { entries = 1; buf[0] = ' '; trim(buf); if (!head) { FERR("Can't add \"%s\", missing #start header", buf) return -1; } if (strchr("123456789", buf[0]) != NULL) { int i = atoi(buf); int digits = (int)(floor(log(i)/log(10)) + 1); for (int j = 0; j < digits; j++) { buf[j] = ' '; } trim(buf); for (int j = 1; j < i; j++) { add_entry(q); } } if(add_entry(q)) { FERR("\"%s\" could not add entry", buf) return -1; } } else if (buf[0] == ';' || buf[0] == '\0') { continue; } else { FERR("\"%s\" is not a valid entry", buf) } } return head ? 0 : -1; } const char * get_mod(const mods m) { switch(m) { case NONE: return "---"; case URGENT: return "!!!"; case MAYBE: return "???"; case DEPENDS: return "@@@"; default: return ""; } } #define P(before, timestr, entry, mod) \ printf("%s%5s %3s %s\n", before, timestr, mod, entry); void printQ(const Q *q) { time_t now; time(&now); char timestr[100]; time_t t = q->start; for (uint i = 0; i < q->n; i++) { strftime(timestr, sizeof(timestr), "%R", localtime(&t)); double d = difftime(t, now); if (ctx.current) { if (d > -900 && d <= 0) P("Now: ", timestr, q->entry[i], get_mod(q->m[i])); } else if (ctx.next) { if (d > 0 && d <= 900) P("Next: ", timestr, q->entry[i], get_mod(q->m[i])); } else if (ctx.later) { if (d > 900 * (ctx.later - 1) && d <= 900 * (ctx.later)) P("Later: ", timestr, q->entry[i], get_mod(q->m[i])); } else if (ctx.remaining) { if (d > -900) P("", timestr, q->entry[i], get_mod(q->m[i])); } else { P("", timestr, q->entry[i], get_mod(q->m[i])); } t+=(15*60); } } int main(int argc, char *argv[]) { if (argc < 2) { print_help(argv[0]); return 0; } if (parse_cli(argc, argv)) return -1; FILE *f = fopen(ctx.file, "r"); if (!f) { printf("file %s does not exist\n", ctx.file); return -1; } Q q; if (parseQ(f, &q)) { goto end; } printQ(&q); end: fclose(f); freeQ(&q); }