diff options
author | Ariadna Vigo <arivigodr@gmail.com> | 2020-06-19 16:03:23 +0200 |
---|---|---|
committer | Ariadna Vigo <arivigodr@gmail.com> | 2020-06-19 16:03:23 +0200 |
commit | 68a2b8706a41273b27dc3cfefbafd2c19dba7645 (patch) | |
tree | d632a5e09b8b0f360d478e46c46fb194995f9d6c |
Initial commit
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | LICENSE | 12 | ||||
-rw-r--r-- | Makefile | 37 | ||||
-rw-r--r-- | README.md | 91 | ||||
-rw-r--r-- | arg.h | 49 | ||||
-rw-r--r-- | config.mk | 17 | ||||
-rw-r--r-- | cras.c | 204 | ||||
-rw-r--r-- | tasklst.c | 120 | ||||
-rw-r--r-- | tasklst.h | 26 |
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 @@ -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. @@ -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 + @@ -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 |