summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAriadna Vigo <arivigodr@gmail.com>2020-06-19 16:03:23 +0200
committerAriadna Vigo <arivigodr@gmail.com>2020-06-19 16:03:23 +0200
commit68a2b8706a41273b27dc3cfefbafd2c19dba7645 (patch)
treed632a5e09b8b0f360d478e46c46fb194995f9d6c
Initial commit
-rw-r--r--.gitignore3
-rw-r--r--LICENSE12
-rw-r--r--Makefile37
-rw-r--r--README.md91
-rw-r--r--arg.h49
-rw-r--r--config.mk17
-rw-r--r--cras.c204
-rw-r--r--tasklst.c120
-rw-r--r--tasklst.h26
9 files changed, 559 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bfc7cd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+config.h
+cras
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f02ce34
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,12 @@
+Copyright 2020 Eugenio M. Vigo <emvigo@gmail.com>
+
+Cras is licensed under the Apache License, Version 2.0 (the "License"); you may
+ not use these files except in compliance with the License. You may obtain a
+copy of the License at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License. \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0868099
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+# See LICENSE file for copyright and license details.
+
+.POSIX:
+
+include config.mk
+
+SRC = cras.c tasklst.c
+OBJ = ${SRC:%.c=%.o}
+
+all: options cras
+
+options:
+ @echo Build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.mk
+
+cras: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ rm -f cras ${OBJ}
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f cras ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/cras
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/cras
+
+.PHONY: all options clean install uninstall
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7546dea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+# Cras - The Anti-Procrastination Tool
+
+Cras is an unapologetic daily task planner and manager for your terminal and WM
+ status bar. It holds your tasks only for a limited amount of time (24 hours,
+by default) and doesn't allow you to edit the task list after set up, except
+for marking a task as done.
+
+## Build
+Cras doesn't require any external dependencies.
+
+Build by using:
+
+```
+$ make
+```
+
+## Usage
+
+### Set up your task list
+To start using Cras, you first need to set up your task list. This is done by
+using the -s command line option and entering a short description of your task
+in a new line. End your list hitting EOF (Ctrl+D) in a *blank line*.
+
+```
+$ cras -s
+First task
+Second task
+Third task
+```
+
+You may also pipe in a text file if you so prefer.
+
+```
+$ cras -s < mytasklist
+```
+
+### Printing out your current list
+To print out your current list, you may use either of two options: a long,
+detailed output, and a short summary (ideal for status bars). The long-form
+output is read just by running Cras without any further options:
+
+```
+$ cras
+Tasks due for: Sat Jun 20 15:57:28 2020
+
+#01 [TODO] Write README.md
+#02 [TODO] Set up git repo for Cras
+#03 [TODO] Succeed in life
+
+3/0/3 to do/done/total
+```
+
+The short-form output is shown by using the -o option:
+
+```
+$ cras -o
+3/0/3 to do/done/total
+```
+
+### Marking a task as done
+When you've completed a task, use -t and the task number (as shown by the
+long-form output) to mark it as done.
+
+```
+$ cras -t 2
+$ cras
+Tasks due for: Sat Jun 20 15:57:28 2020
+
+#01 [TODO] Write README.md
+#02 [DONE] Set up git repo for Cras
+#03 [TODO] Succeed in life
+
+2/1/3 to do/done/total
+```
+
+If you need to mark a task again as pending, use -T.
+
+## Install
+You may install Cras by running the following command as root:
+
+```
+# make install
+```
+
+This will install the binary under $PREFIX/bin, as defined by your environment,
+ or /usr/local/bin by default. The Makefile supports the $DESTDIR variable as
+well.
+
+## License
+Cras is licensed under the Apache Public License version 2.0. See LICENSE
+ file for copyright and license details.
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..e94e02b
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,49 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..affaddb
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,17 @@
+# Cras version
+VERSION = 0.0.0
+
+# Customize below to your needs
+
+# Paths
+PREFIX = /usr/local
+
+# Flags
+CPPFLAGS = -DVERSION=\"${VERSION}\" -D_POSIX_C_SOURCE=200809L
+#CFLAGS = -g -std=c99 -Wpedantic -Wall -Wextra ${CPPFLAGS}
+CFLAGS = -std=c99 -Wpedantic -Wall -Wextra ${CPPFLAGS}
+#LDFLAGS = -static
+
+# Compiler and linker
+CC = cc
+
diff --git a/cras.c b/cras.c
new file mode 100644
index 0000000..86f8ec5
--- /dev/null
+++ b/cras.c
@@ -0,0 +1,204 @@
+/* See LICENSE file for copyright and license details. */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static char *argv0; /* Required here by arg.h */
+#include "arg.h"
+#include "tasklst.h"
+
+enum {
+ SHORT_OUTPUT,
+ LONG_OUTPUT
+};
+
+static void die(const char *fmt, ...);
+static void print_short_output(TaskLst tasks);
+static void print_output(TaskLst tasks);
+static void read_crasfile(TaskLst *tasks, const char *crasfile);
+static void write_crasfile(const char *crasfile, TaskLst tasks);
+static void read_user_input(TaskLst *tasks, FILE *fp);
+
+static void usage(void);
+static void set_tasks_mode(const char *crasfile);
+static void output_mode(const char *crasfile, int mode);
+static void mark_tasks_mode(const char *crasfile, const char *id, int mode);
+
+/*
+ * crasfile_path defined here just for testing. It will be moved later to
+ * config.(def.)h
+ */
+static char crasfile_path[] = "crasfile";
+
+static void die(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+
+ va_end(ap);
+
+ exit(1);
+}
+
+static void
+print_short_output(TaskLst tasks)
+{
+ printf("%d/%d/%d to do/done/total\n", tasklst_tasks_todo(tasks),
+ tasklst_tasks_done(tasks), tasklst_tasks_total(tasks));
+}
+
+static void
+print_output(TaskLst tasks)
+{
+ int i;
+
+ printf("Tasks due for: %s\n", ctime(&tasks.expiry));
+
+ for(i = 0; i < TASK_LST_MAX_NUM; ++i) {
+ if (tasks.status[i] != TASK_VOID)
+ printf("#%02d [%s] %s\n", i + 1,
+ (tasks.status[i] == TASK_TODO) ? "TODO" : "DONE",
+ tasks.tdesc[i]);
+ }
+
+ if (i > 0)
+ putchar('\n');
+
+ print_short_output(tasks);
+}
+
+static void
+read_crasfile(TaskLst *tasks, const char *crasfile)
+{
+ int read_stat;
+ FILE *fp;
+
+ if ((fp = fopen(crasfile, "r")) == NULL)
+ die("Could not read from %s: %s", crasfile, strerror(errno));
+
+ read_stat = tasklst_read_from_file(tasks, fp);
+ fclose(fp);
+
+ if (read_stat < 0)
+ die("Parsing error: task file corrupted.");
+
+ if (tasklst_expired(*tasks) == 0)
+ die("Current task file expired: Set a new one!");
+}
+
+static void
+write_crasfile(const char *crasfile, TaskLst tasks)
+{
+ FILE *fp;
+
+ if ((fp = fopen(crasfile, "w")) == NULL)
+ die("Could not write to %s: %s", crasfile, strerror(errno));
+
+ tasklst_write_to_file(fp, tasks);
+ fclose(fp);
+}
+
+static void
+read_user_input(TaskLst *tasks, FILE *fp)
+{
+ char linebuf[TASK_LST_DESC_MAX_SIZE];
+ int i;
+
+ for (i = 0; i < TASK_LST_MAX_NUM; ++i) {
+ fgets(linebuf, TASK_LST_DESC_MAX_SIZE, fp);
+ if (feof(fp) != 0)
+ break;
+
+ linebuf[strlen(linebuf) - 1] = '\0';
+ strncpy(tasks->tdesc[i], linebuf, TASK_LST_DESC_MAX_SIZE);
+ tasks->status[i] = TASK_TODO;
+ }
+}
+
+static void
+usage(void)
+{
+ die("[STUB: Usage info]");
+}
+
+static void
+set_tasks_mode(const char *crasfile)
+{
+ TaskLst tasks;
+
+ tasklst_init(&tasks);
+ read_user_input(&tasks, stdin); /* Only stdin for now */
+
+ tasklst_set_expiration(&tasks);
+ write_crasfile(crasfile, tasks);
+}
+
+static void
+output_mode(const char *crasfile, int mode)
+{
+ TaskLst tasks;
+
+ tasklst_init(&tasks);
+ read_crasfile(&tasks, crasfile);
+
+ if (mode == SHORT_OUTPUT)
+ print_short_output(tasks);
+ else
+ print_output(tasks);
+}
+
+static void
+mark_tasks_mode(const char *crasfile, const char *id, int mode)
+{
+ int tasknum;
+ char *endptr;
+ TaskLst tasks;
+
+ tasknum = strtol(id, &endptr, 10);
+ if (endptr[0] != '\0')
+ die("%s not a number", id);
+
+ tasklst_init(&tasks);
+ read_crasfile(&tasks, crasfile);
+
+ if (tasknum <= tasklst_tasks_total(tasks))
+ tasks.status[tasknum - 1] = mode;
+ else
+ die("Task #%d does not exist.", tasknum);
+
+ write_crasfile(crasfile, tasks);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 's':
+ set_tasks_mode(crasfile_path);
+ return 0;
+ case 'o':
+ output_mode(crasfile_path, SHORT_OUTPUT);
+ return 0;
+ case 't':
+ mark_tasks_mode(crasfile_path, EARGF(usage()),
+ TASK_DONE);
+ return 0;
+ case 'T':
+ mark_tasks_mode(crasfile_path, EARGF(usage()),
+ TASK_TODO);
+ return 0;
+ default:
+ usage(); /* usage() dies, so nothing else needed. */
+ } ARGEND;
+
+ /* Default behavior: long-form output */
+ output_mode(crasfile_path, LONG_OUTPUT);
+ return 0;
+}
diff --git a/tasklst.c b/tasklst.c
new file mode 100644
index 0000000..84d674e
--- /dev/null
+++ b/tasklst.c
@@ -0,0 +1,120 @@
+/* See LICENSE file for copyright and license details. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "tasklst.h"
+
+static int tasklst_tasks_status(TaskLst tasks, int status);
+
+static int
+tasklst_tasks_status(TaskLst tasks, int status)
+{
+ int i, total;
+
+ for (i = 0, total = 0; i < TASK_LST_MAX_NUM; ++i) {
+ if (tasks.status[i] == status)
+ ++total;
+ }
+
+ return total;
+}
+
+void
+tasklst_init(TaskLst *tasks)
+{
+ int i;
+
+ tasks->expiry = 0;
+
+ for (i = 0; i < TASK_LST_MAX_NUM; ++i) {
+ memset(&tasks->status[i], TASK_VOID, sizeof(int));
+ memset(tasks->tdesc[i], 0, TASK_LST_DESC_MAX_SIZE);
+ }
+}
+
+void
+tasklst_set_expiration(TaskLst *tasks)
+{
+ tasks->expiry = time(NULL) + TASK_LST_EXPIRY;
+}
+
+int
+tasklst_expired(TaskLst tasks)
+{
+ return ((time(NULL) - tasks.expiry) > TASK_LST_EXPIRY) ? 0 : -1;
+}
+
+int
+tasklst_tasks_total(TaskLst tasks)
+{
+ return tasklst_tasks_todo(tasks) + tasklst_tasks_done(tasks);
+}
+
+int
+tasklst_tasks_todo(TaskLst tasks)
+{
+ return tasklst_tasks_status(tasks, TASK_TODO);
+}
+
+int
+tasklst_tasks_done(TaskLst tasks)
+{
+ return tasklst_tasks_status(tasks, TASK_DONE);
+}
+
+int
+tasklst_read_from_file(TaskLst *tasks, FILE *fp)
+{
+ int i, stat_buf;
+ char *ptr, *endptr;
+ char linebuf[TASK_LST_DESC_MAX_SIZE];
+
+ fgets(linebuf, sizeof(linebuf), fp);
+ tasks->expiry = strtoul(linebuf, &endptr, 10);
+ if (endptr[0] != '\n')
+ return -1;
+
+ for (i = 0; i < TASK_LST_MAX_NUM && feof(fp) == 0; ++i) {
+ if (fgets(linebuf, sizeof(linebuf), fp) == NULL)
+ break;
+
+ ptr = strtok(linebuf, "\t");
+ if (ptr == NULL)
+ return -1;
+
+ stat_buf = strtol(ptr, &endptr, 10);
+ if (endptr[0] != '\0')
+ return -1;
+
+ if (stat_buf == TASK_VOID)
+ break;
+ else
+ tasks->status[i] = stat_buf;
+
+ ptr = strtok(NULL, "\n");
+ if (ptr == NULL)
+ return -1;
+
+ strncpy(tasks->tdesc[i], ptr, TASK_LST_DESC_MAX_SIZE);
+ }
+
+ return 0;
+}
+
+void
+tasklst_write_to_file(FILE *fp, TaskLst tasks)
+{
+ int i;
+
+ fprintf(fp, "%zu\n", tasks.expiry);
+
+ for (i = 0; i < TASK_LST_MAX_NUM; ++i) {
+ if (tasks.status[i] == TASK_VOID)
+ break;
+
+ fprintf(fp, "%d\t%s\n", tasks.status[i], tasks.tdesc[i]);
+ }
+}
diff --git a/tasklst.h b/tasklst.h
new file mode 100644
index 0000000..4e75614
--- /dev/null
+++ b/tasklst.h
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+
+#define TASK_LST_MAX_NUM 11
+#define TASK_LST_DESC_MAX_SIZE 64
+#define TASK_LST_EXPIRY 86400 /* 86400 secs = 24 hrs */
+
+enum {
+ TASK_VOID,
+ TASK_TODO,
+ TASK_DONE
+};
+
+typedef struct {
+ time_t expiry;
+ int status[TASK_LST_MAX_NUM];
+ char tdesc[TASK_LST_MAX_NUM][TASK_LST_DESC_MAX_SIZE];
+} TaskLst;
+
+void tasklst_init(TaskLst *tasks);
+void tasklst_set_expiration(TaskLst *tasks);
+int tasklst_expired(TaskLst tasks);
+int tasklst_tasks_total(TaskLst tasks);
+int tasklst_tasks_todo(TaskLst tasks);
+int tasklst_tasks_done(TaskLst tasks);
+int tasklst_read_from_file(TaskLst *tasks, FILE *fp);
+void tasklst_write_to_file(FILE *fp, TaskLst tasks); \ No newline at end of file