summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgramanas <anastasis.gramm2@gmail.com>2019-03-27 19:56:24 +0200
committergramanas <anastasis.gramm2@gmail.com>2020-12-25 21:34:36 +0200
commit5b4a10af81c60c6c4787d95f82004094f358459c (patch)
tree6f7b358288926bd5c501755d1e8f2872fb68d046
downloadquart-5b4a10af81c60c6c4787d95f82004094f358459c.tar.gz
quart-5b4a10af81c60c6c4787d95f82004094f358459c.tar.bz2
quart-5b4a10af81c60c6c4787d95f82004094f358459c.zip
Initial..
-rw-r--r--.gitignore3
-rw-r--r--Makefile36
-rwxr-xr-xqc178
l---------qc.11
-rw-r--r--quart.1114
-rw-r--r--quart.1.scd79
-rw-r--r--quart.5129
-rw-r--r--quart.5.scd90
-rw-r--r--quart.c419
-rwxr-xr-xquart.i3blocks15
-rw-r--r--sample.org8
11 files changed, 1072 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e34fb33
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+quart
+*.test
+*.q \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f05117c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+TARGET=quart
+SRC=*.c
+CC=gcc
+CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic -lm
+REL_FLAGS=-O3
+DBG_FLAGS=-Wall -g3
+COMPILE=$(CC) -o $(TARGET) $(SRC) $(CFLAGS) -Ilib
+
+make: $(SRC)
+ $(CC) $(REL_FLAGS) $(CFLAGS) -o $(TARGET) $^
+
+debug: $(SRC)
+ $(CC) $(DBG_FLAGS) -fsanitize=address $(CFLAGS) -o $(TARGET) $^
+
+noasan: $(SRC)
+ $(CC) $(DBG_FLAGS) $(CFLAGS) -o $(TARGET) $^
+
+docs:
+ scdoc < quart.1.scd > quart.1
+ scdoc < quart.5.scd > quart.5
+ ln -fs quart.1 qc.1
+
+.PHONY: clean
+
+clean:
+ rm -f *.o
+ rm -f *.1
+ rm -f *.5
+ rm -f $(TARGET)
+
+install: $(TARGET)
+ @cp -v $(TARGET) /usr/local/bin/$(TARGET)
+ @cp -v qc /usr/local/bin/qc
+
+uninstall:
+ @rm -v /usr/local/bin/$(TARGET)
diff --git a/qc b/qc
new file mode 100755
index 0000000..0e32109
--- /dev/null
+++ b/qc
@@ -0,0 +1,178 @@
+#!/bin/bash
+#
+## Wrapper script for quart
+#
+## Takes care of ~/.quart.d
+#
+## .quart.d
+## |-- archive
+## | |-- 190706_Saturday_220700.org
+## | |-- 190707_Sunday_220701.org
+## |-- today.org
+
+#
+##
+## Initialize values
+##
+#
+
+ED="${EDITOR:-emacs -nw}"
+QHOME="${QUART_HOME:-$HOME/.quart.d}"
+ARCHIVE="$QHOME"/archive
+TODAY_FILE="$QHOME"/today.org
+DATE_CMD="date +%y%m%d_%A_%H%m%S"
+
+if [[ -z $(which quart) ]]; then
+ echo "quart is not installed"
+ exit 1
+fi
+
+QUART_CMD=$(which quart)
+
+function archive {
+ if [[ ! -d "$ARCHIVE" ]]; then
+ echo "archive directory does not exist"
+ exit 1
+ fi
+
+ if [[ -f "$TODAY_FILE" ]]; then
+ mv "$TODAY_FILE" "$ARCHIVE/$($DATE_CMD)"
+ return 1
+ fi
+
+ return 0
+}
+
+function make_today {
+ if [[ ! -d "$QHOME" ]]; then
+ echo "quart home directory does not exist"
+ exit 1
+ fi
+
+ cat > "$TODAY_FILE" << EOF
+#+START: 9:00
+
+# quart schedule created at $(date "+%A %d of %B the year of our lord %Y")
+
+# Avaliable task flags:
+# ! Urgent, ? Maybe, @ External
+# Use `=` to signal a task repeat
+
+EOF
+}
+
+function edit_today {
+ if [[ ! -f "$TODAY_FILE" ]]; then
+ echo "run new first"
+ exit 1
+ fi
+
+ $ED "$TODAY_FILE"
+}
+
+function summary_today {
+ if [[ ! -f "$TODAY_FILE" ]]; then
+ return 0
+ fi
+
+ "$QUART_CMD" "$TODAY_FILE" -s
+}
+
+function make_qhome {
+ if [[ -d "$QHOME" ]]; then
+ echo "QUART_HOME already exists in $QHOME"
+ else
+ mkdir "$QHOME"
+ echo "QUART_HOME initialized in $QHOME"
+ fi
+
+ if [[ -d "$ARCHIVE" ]]; then
+ echo "archive directory already exists"
+ else
+ mkdir "$ARCHIVE"
+ echo "archive directory created"
+ fi
+
+}
+
+if [ $# -lt 1 ]; then
+ if [[ ! -d "$QHOME" ]]; then
+ echo "run init first"
+ exit 1
+ fi
+
+ if [[ ! -f "$TODAY_FILE" ]]; then
+ echo "run new first"
+ exit 1
+ fi
+
+ echo -e "Current schedule:\n"
+ "$QUART_CMD" "$TODAY_FILE" -s
+ exit 0
+fi
+
+if [[ "$1" == "-" ]]; then
+ shift
+ "$QUART_CMD" "$TODAY_FILE" $@
+ exit $?
+fi
+
+case $1 in
+ init|i)
+ make_qhome
+ exit 0
+ # create ~/.quart.d director structure
+ ;;
+ new|n)
+ shift
+ archive
+ make_today
+ edit_today
+ summary_today
+ exit 0
+ # backup the old schedule and create a new one
+ ;;
+ edit|e)
+ shift
+ edit_today
+ summary_today
+ exit 0
+ # edit the current schedule
+ ;;
+ remove|rm)
+ shift
+ archive
+ exit 0
+ # rm current schedule, after backing it up
+ ;;
+ cat|c)
+ shift
+ cat "$TODAY_FILE"
+ exit 0
+ ;;
+ help|h|-h|--help)
+ shift
+ cat << EOF
+qc -- bash wrapper for quart
+
+Usage:
+$0 [OPTION]
+$0 - [quart option]
+
+OPTIONS:
+ init | i initialize quart home
+ new | n create new schedule (archives current one)
+ remove | rm remove current schedule (and archive it)
+ edit | e edit current schedule
+ cat | c print current schedule
+ help | h show this help
+ - pass arguments to quart
+EOF
+ exit 0
+ ;;
+ *)
+ echo "qc: '$1' is not a valid option"
+ shift
+ exit 1
+ ;;
+esac
diff --git a/qc.1 b/qc.1
new file mode 120000
index 0000000..4ef8e08
--- /dev/null
+++ b/qc.1
@@ -0,0 +1 @@
+quart.1 \ No newline at end of file
diff --git a/quart.1 b/quart.1
new file mode 100644
index 0000000..37b3c34
--- /dev/null
+++ b/quart.1
@@ -0,0 +1,114 @@
+.\" Generated by scdoc 1.11.1
+.\" Complete documentation for this program is not available as a GNU info page
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.nh
+.ad l
+.\" Begin generated content:
+.TH "quart" "1" "2020-12-25"
+.P
+.SH NAME
+.P
+quart - plan your day, a quarter at a time
+.P
+.SH SYNOPSIS
+.P
+\fBquart\fR [\fIfile\fR] [\fB-schrn\fR] [\fB-l\fR \fINUM\fR]
+.br
+\fBqc\fR [\fICOMMAND\fR]
+.br
+\fBqc\fR [\fB-\fR \fIARGS\fR]
+.P
+.SH OPTIONS
+.P
+\fBquart\fR has the following options:
+.P
+\fIfile\fR
+.RS 4
+File containing the schedule to parse.\&
+.P
+.RE
+\fB-c\fR
+.RS 4
+Print \fIcurrent\fR task.\&
+.P
+.RE
+\fB-n\fR
+.RS 4
+Print \fInext\fR task.\&
+.P
+.RE
+\fB-l\fR \fIquarters\fR
+.RS 4
+Print the task that you should do \fIquarters\fR quarters \fIlater\fR.\&
+.P
+.RE
+\fBqc\fR has the following options:
+.P
+Without any arguments it pretty prints the current schedule.\&
+.P
+\fB-\fR
+.RS 4
+Pass any argument that follows the `-` symbol to \fBquart\fR, along with
+the current schedule.\&
+.P
+.RE
+\fBinit, i\fR
+.RS 4
+Create the required directory structure under \fI$QUART_HOME\fR.\&
+.P
+.RE
+\fBnew, n\fR
+.RS 4
+Create a new schedule and archive the current one.\&
+.P
+.RE
+\fBremove, rm\fR
+.RS 4
+Remove current schedule while also archiving it.\&
+.P
+.RE
+\fBedit, e\fR
+.RS 4
+Run \fI$EDITOR\fR on the current schedule.\&
+.P
+.RE
+\fBcat, c\fR
+.RS 4
+Print current schedule file in \fIstdout\fR.\&
+.P
+.RE
+\fBhelp, h, -h\fR
+.RS 4
+Show help.\&
+.P
+.RE
+.SH DESCRIPTION
+.P
+\fBquart\fR parses a file in \fBquart\fR(5) format and prints the schedule according to
+the given options.\& \fBqc\fR is a bash wrapper around \fBquart\fR that automates the
+management of daily schedule files.\& It cat be customized by editing it.\& It is
+the recommended way of managing you quart schedules.\& Both commands provide
+usage information using the \fB-h\fR flag.\&
+.P
+.SH FILES/ENVIRONMENT
+.P
+\fIQUART_HOME\fR
+.RS 4
+\fBqc\fR uses this location for storing current and archived schedules.\&
+\fI~/.\&quart.\&d\fR by default.\&
+.P
+.RE
+\fIQUART_HOME/archive\fR
+.RS 4
+All the archived schedules end up here and are renamed.\&
+.P
+.RE
+\fIEDITOR\fR
+.RS 4
+Used when invoking the edit command.\&
+.P
+.RE
+.SH AUTHOR
+.P
+Anastasis Grammenos \ No newline at end of file
diff --git a/quart.1.scd b/quart.1.scd
new file mode 100644
index 0000000..2a9e08e
--- /dev/null
+++ b/quart.1.scd
@@ -0,0 +1,79 @@
+quart(1)
+
+; Tabs are required for indentation (<C-q tab> in emacs)
+
+# NAME
+
+quart - plan your day, a quarter at a time
+
+# SYNOPSIS
+
+*quart* [_file_] [*-schrn*] [*-l* _NUM_] ++
+*qc* [_COMMAND_] ++
+*qc* [*-* _ARGS_]
+
+# OPTIONS
+
+*quart* has the following options:
+
+_file_
+ File containing the schedule to parse.
+
+*-c*
+ Print _current_ task.
+
+*-n*
+ Print _next_ task.
+
+*-l* _quarters_
+ Print the task that you should do _quarters_ quarters _later_.
+
+*qc* has the following options:
+
+Without any arguments it pretty prints the current schedule.
+
+*-*
+ Pass any argument that follows the `-` symbol to *quart*, along with
+ the current schedule.
+
+*init, i*
+ Create the required directory structure under _$QUART_HOME_.
+
+*new, n*
+ Create a new schedule and archive the current one.
+
+*remove, rm*
+ Remove current schedule while also archiving it.
+
+*edit, e*
+ Run _$EDITOR_ on the current schedule.
+
+*cat, c*
+ Print current schedule file in _stdout_.
+
+*help, h, -h*
+ Show help.
+
+# DESCRIPTION
+
+*quart* parses a file in *quart*(5) format and prints the schedule according to
+the given options. *qc* is a bash wrapper around *quart* that automates the
+management of daily schedule files. It cat be customized by editing it. It is
+the recommended way of managing you quart schedules. Both commands provide
+usage information using the *-h* flag.
+
+# FILES/ENVIRONMENT
+
+_QUART_HOME_
+ *qc* uses this location for storing current and archived schedules.
+ _~/.quart.d_ by default.
+
+_QUART_HOME/archive_
+ All the archived schedules end up here and are renamed.
+
+_EDITOR_
+ Used when invoking the edit command.
+
+# AUTHOR
+
+Anastasis Grammenos \ No newline at end of file
diff --git a/quart.5 b/quart.5
new file mode 100644
index 0000000..b8bb2e2
--- /dev/null
+++ b/quart.5
@@ -0,0 +1,129 @@
+.\" Generated by scdoc 1.11.1
+.\" Complete documentation for this program is not available as a GNU info page
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.nh
+.ad l
+.\" Begin generated content:
+.TH "quart" "5" "2020-12-25"
+.P
+.P
+.SH NAME
+.P
+quart - document format for \fBquart\fR(1) schedules
+.P
+.SH SYNTAX
+.P
+\fBquart\fR files are inspired by emacs' org-mode.\& The header contains the time
+that the schedule begins and each line represents a quarter of time, or 15
+minures.\&
+.P
+.SS HEADER
+.P
+Before any task, every \fBquart\fR file must contain a header with the starting
+time of the schedule.\& It looks like this:
+.P
+.nf
+.RS 4
+#+START: 9:15
+.fi
+.RE
+.P
+It is case insenstive.\&
+.P
+.SS COMMENTS
+.P
+Every line beginning with `#` is ignored.\& Comments must start at the beginning
+of the line.\&
+.P
+.SS BODY
+.P
+The body of the document contains the tasks.\& Each task is a line starting with `* `
+(asterisk and space).\& The full definition of a task looks like this:
+.P
+.RS 4
+* [\fIREPETITION\fR] [\fIFLAG\fR] <\fBDESCRIPTION\fR>
+.P
+.RE
+\fIFLAG\fR and \fIREPETION\fR are optional.\& See the respective sections below.\&
+.P
+The description can be any arbitrary string.\&
+.P
+.SS FLAGS
+.P
+Each task can have a \fIflag\fR set to make it pop out.\& There are three flags avaliable:
+.P
+.TS
+c r l
+c r l
+c r l
+c r l.
+T{
+\fBChar\fR
+T} T{
+\fBFlag\fR
+T} T{
+\fBDescription\fR
+T}
+T{
+!
+T} T{
+\fIUrgent\fR
+T} T{
+Mark task as important.\&
+T}
+T{
+?\&
+T} T{
+\fIMaybe\fR
+T} T{
+Mark task as mayhap.\&
+T}
+T{
+@
+T} T{
+\fIDepends\fR
+T} T{
+Task that depends on someone else.\&
+T}
+.TE
+.sp 1
+Use them by adding the required \fIchar\fR after the `* ` in a task:
+.P
+.nf
+.RS 4
+#+start: 9:30
+* ! Meet with Knuth\&.
+.fi
+.RE
+.P
+If used with \fIrepetition\fR it must go \fBafter\fR it.\&
+.P
+.SS REPETITION
+.P
+Allows repetition of a task for as many quarters as needed in succession.\&
+.P
+Use them by adding a number right after the `* ` in a task:
+.P
+.nf
+.RS 4
+#+start: 11:30
+* 8 Boring 2h meeting\&.
+.fi
+.RE
+.P
+If used with \fIflags\fR it must go \fBbefore\fR any.\&
+.P
+.SS SPECIAL
+.P
+There are two special symbols the \fIDESCRIPTION\fR can contain:
+.P
+\fB=\fR
+.RS 4
+Repeat the previous task.\& It will only carry over the description
+so it can contain a different \fIflag\fR or \fIrepetition\fR number.\&
+.P
+.RE
+\fB-\fR
+.RS 4
+None, the most important thing.\& 15 minutes of doing nothing.\& \ No newline at end of file
diff --git a/quart.5.scd b/quart.5.scd
new file mode 100644
index 0000000..438dfad
--- /dev/null
+++ b/quart.5.scd
@@ -0,0 +1,90 @@
+quart(5)
+
+; Tabs are required for indentation (<C-q tab> in emacs)
+
+# NAME
+
+quart - document format for *quart*(1) schedules
+
+# SYNTAX
+
+*quart* files are inspired by emacs' org-mode. The header contains the time
+that the schedule begins and each line represents a quarter of time, or 15
+minures.
+
+## HEADER
+
+Before any task, every *quart* file must contain a header with the starting
+time of the schedule. It looks like this:
+
+```
+#+START: 9:15
+```
+
+It is case insenstive.
+
+## COMMENTS
+
+Every line beginning with `#` is ignored. Comments must start at the beginning
+of the line.
+
+## BODY
+
+The body of the document contains the tasks. Each task is a line starting with `\* `
+(asterisk and space). The full definition of a task looks like this:
+
+ \* [_REPETITION_] [_FLAG_] <*DESCRIPTION*>
+
+_FLAG_ and _REPETION_ are optional. See the respective sections below.
+
+The description can be any arbitrary string.
+
+## FLAGS
+
+Each task can have a _flag_ set to make it pop out. There are three flags avaliable:
+
+|- *Char*
+:] *Flag*
+:[ *Description*
+|- \!
+: _Urgent_
+:[ Mark task as important.
+|- ?
+: _Maybe_
+:[ Mark task as mayhap.
+|- @
+: _Depends_
+:[ Task that depends on someone else.
+
+Use them by adding the required _char_ after the `\* ` in a task:
+
+```
+#+start: 9:30
+\* ! Meet with Knuth.
+```
+
+If used with _repetition_ it must go *after* it.
+
+## REPETITION
+
+Allows repetition of a task for as many quarters as needed in succession.
+
+Use them by adding a number right after the `\* ` in a task:
+
+```
+#+start: 11:30
+* 8 Boring 2h meeting.
+```
+
+If used with _flags_ it must go *before* any.
+
+## SPECIAL
+
+There are two special symbols the _DESCRIPTION_ can contain:
+
+*=*
+ Repeat the previous task. It will only carry over the description
+ so it can contain a different _flag_ or _repetition_ number.
+
+*-*
+ None, the most important thing. 15 minutes of doing nothing. \ No newline at end of file
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);
+}
diff --git a/quart.i3blocks b/quart.i3blocks
new file mode 100755
index 0000000..bf0e1a1
--- /dev/null
+++ b/quart.i3blocks
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+case $BLOCK_BUTTON in
+ 1) notify-send "Current:" "$(qc - -c)"
+ ;;
+ 2) notify-send "Next:" "$(qc - -n)"
+ ;;
+ 3) notify-send "Remaining:" "$(qc - -r)"
+ ;;
+esac
+
+REM=$(qc - -r | wc -l)
+
+echo "<span color='#755a87'>Q:</span>\
+<span color='#6b8e23'>$REM ($(qc - -c | awk -F '[[:space:]][[:space:]]' '{print $3}'))</span>"
diff --git a/sample.org b/sample.org
new file mode 100644
index 0000000..185fcd4
--- /dev/null
+++ b/sample.org
@@ -0,0 +1,8 @@
+#+START: 14:15
+
+* ! Coffee
+* =
+* sysadmin emacs+tmux config
+* =
+* ssh-agent
+* =