/**
* 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);
}