summaryrefslogtreecommitdiffstats
path: root/quart.c
diff options
context:
space:
mode:
Diffstat (limited to 'quart.c')
-rw-r--r--quart.c419
1 files changed, 419 insertions, 0 deletions
diff --git a/quart.c b/quart.c
new file mode 100644
index 0000000..4f2ad3b
--- /dev/null
+++ b/quart.c
@@ -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);
+}