diff options
Diffstat (limited to 'quart.c')
-rw-r--r-- | quart.c | 419 |
1 files changed, 419 insertions, 0 deletions
@@ -0,0 +1,419 @@ +/** + * 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 <stdio.h> +#include <string.h> +#include <time.h> +#include <stdlib.h> +#include <ctype.h> +#include <math.h> +#include <getopt.h> + +#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); +} |