Intial design complete
authoralex <[email protected]>
Sat, 20 Aug 2022 01:44:26 +0000 (18:44 -0700)
committeralex <[email protected]>
Thu, 1 Sep 2022 05:50:13 +0000 (22:50 -0700)
Full project completed.

Added the following sub-commands:
-add
-dismiss
-ls
-rm
-postpone
-prune

Added unit tests to verify functionality.

59 files changed:
.gitignore [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
configure.ac [new file with mode: 0644]
inc/add.h [new file with mode: 0644]
inc/args.h [new file with mode: 0644]
inc/copy.h [new file with mode: 0644]
inc/cut.h [new file with mode: 0644]
inc/defaults.h [new file with mode: 0644]
inc/event.h [new file with mode: 0644]
inc/file.h [new file with mode: 0644]
inc/ls.h [new file with mode: 0644]
inc/main.h [new file with mode: 0644]
inc/opt.h [new file with mode: 0644]
inc/postpone.h [new file with mode: 0644]
inc/prune.h [new file with mode: 0644]
inc/rm.h [new file with mode: 0644]
inc/seek.h [new file with mode: 0644]
inc/usage.h [new file with mode: 0644]
src/add.c [new file with mode: 0644]
src/args.c [new file with mode: 0644]
src/copy.c [new file with mode: 0644]
src/cut.c [new file with mode: 0644]
src/defaults.c [new file with mode: 0644]
src/event.c [new file with mode: 0644]
src/file/line.c [new file with mode: 0644]
src/file/mv.c [new file with mode: 0644]
src/file/open.c [new file with mode: 0644]
src/file/pipe.c [new file with mode: 0644]
src/file/tmp.c [new file with mode: 0644]
src/ls.c [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/opt/duration.c [new file with mode: 0644]
src/opt/env.c [new file with mode: 0644]
src/opt/file.c [new file with mode: 0644]
src/opt/filter.c [new file with mode: 0644]
src/opt/global.c [new file with mode: 0644]
src/opt/recur.c [new file with mode: 0644]
src/postpone.c [new file with mode: 0644]
src/prune.c [new file with mode: 0644]
src/rm.c [new file with mode: 0644]
src/seek.c [new file with mode: 0644]
src/usage.c [new file with mode: 0644]
test/unit/Makefile.am [new file with mode: 0644]
test/unit/add.tests.c [new file with mode: 0644]
test/unit/args.tests.c [new file with mode: 0644]
test/unit/copy.tests.c [new file with mode: 0644]
test/unit/cut.tests.c [new file with mode: 0644]
test/unit/dismiss.tests.c [new file with mode: 0644]
test/unit/event.tests.c [new file with mode: 0644]
test/unit/file.tests.c [new file with mode: 0644]
test/unit/ls.tests.c [new file with mode: 0644]
test/unit/opt.tests.c [new file with mode: 0644]
test/unit/postpone.tests.c [new file with mode: 0644]
test/unit/prune.tests.c [new file with mode: 0644]
test/unit/rm.tests.c [new file with mode: 0644]
test/unit/seek.tests.c [new file with mode: 0644]
test/unit/test_macros.h [new file with mode: 0644]
test/unit/test_utils.c [new file with mode: 0644]
test/unit/test_utils.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..a7921fc
--- /dev/null
@@ -0,0 +1,26 @@
+ev
+events
+
+# build objects
+*.o
+*.log
+*.trs
+*.tests
+
+# autoconf/automake
+.deps
+.dirstamp
+aclocal.m4
+autom4te.cache/
+autoscan.log
+build-aux/
+config.h
+config.h.in
+config.h.in~
+config.log
+config.status
+configure
+configure.scan
+Makefile
+Makefile.in
+stamp-h1
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..7b8e14c
--- /dev/null
@@ -0,0 +1,59 @@
+AM_CPPFLAGS = \
+       -Wall \
+       -Werror
+
+if ENABLE_DEBUG
+else
+AM_CPPFLAGS += \
+       -DNDEBUG
+endif
+
+bin_PROGRAMS = ev
+
+ev_SOURCES = \
+       src/add.c \
+       src/args.c \
+       src/copy.c \
+       src/cut.c \
+       src/defaults.c \
+       src/event.c \
+       src/file/line.c \
+       src/file/mv.c \
+       src/file/open.c \
+       src/file/pipe.c \
+       src/file/tmp.c \
+       src/ls.c \
+       src/main.c \
+       src/opt/duration.c \
+       src/opt/env.c \
+       src/opt/file.c \
+       src/opt/filter.c \
+       src/opt/global.c \
+       src/opt/recur.c \
+       src/postpone.c \
+       src/prune.c \
+       src/rm.c \
+       src/seek.c \
+       src/usage.c
+
+ev_SOURCES += \
+       inc/add.h \
+       inc/args.h \
+       inc/copy.h \
+       inc/cut.h \
+       inc/defaults.h \
+       inc/event.h \
+       inc/file.h \
+       inc/ls.h \
+       inc/main.h \
+       inc/opt.h \
+       inc/postpone.h \
+       inc/prune.h \
+       inc/rm.h \
+       inc/seek.h \
+       inc/usage.h
+
+SUBDIRS = . test/unit
+
+unit:
+       $(MAKE) -C test/unit check
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..1030934
--- /dev/null
@@ -0,0 +1,65 @@
+AC_PREREQ([2.69])
+AC_INIT([ev], [0.0.0])
+
+# Store build files not in main directory
+AC_CONFIG_AUX_DIR([build-aux])
+
+AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror])
+
+AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_HEADERS([inc/config.h])
+
+AC_ARG_ENABLE([debug],
+  [AS_HELP_STRING([--enable-debug],
+  [enable debugging])],
+  [enable_debug=$enableval],
+  [enable_debug=no])
+
+AC_ARG_ENABLE([memcheck],
+  [AS_HELP_STRING([--disable-memcheck],
+  [disable memcheck with valgrind (enabled by default)])],
+  [enable_memcheck=$enableval],
+  [enable_memcheck=yes])
+
+AC_PATH_PROG([VALGRIND], [valgrind])
+AM_CONDITIONAL([HAVE_VALGRIND], [test -n "$VALGRIND"])
+
+AC_MSG_CHECKING([if debugging])
+if test x$enable_debug != xno; then
+       AC_MSG_RESULT(yes)
+       CFLAGS="-ggdb3 -O0"
+else
+       AC_MSG_RESULT(no)
+fi
+
+AM_CONDITIONAL([ENABLE_DEBUG],[test x$enable_debug != xno])
+
+AC_MSG_CHECKING([if memcheck should be enabled])
+if test x$enable_memcheck != xno; then
+  AC_MSG_RESULT(yes)
+else
+  AC_MSG_RESULT(no)
+fi
+
+AM_CONDITIONAL([ENABLE_MEMCHECK],[test x$enable_memcheck = xyes])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_INSTALL
+
+# Checks for libraries.
+
+# Checks for header files.
+AC_CHECK_HEADERS([stdlib.h string.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_SIZE_T
+
+# Checks for library functions.
+AC_FUNC_MALLOC
+AC_CHECK_FUNCS([memset strdup strptime])
+
+AC_CONFIG_FILES([Makefile
+                test/unit/Makefile])
+
+AC_OUTPUT
diff --git a/inc/add.h b/inc/add.h
new file mode 100644 (file)
index 0000000..41148fd
--- /dev/null
+++ b/inc/add.h
@@ -0,0 +1,19 @@
+#ifndef __ADD_H_
+#define __ADD_H_
+
+#include<getopt.h>
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+#include<time.h>
+
+#include<copy.h>
+#include<event.h>
+#include<file.h>
+#include<opt.h>
+#include<usage.h>
+
+int add();
+int add_to_file(struct event*);
+
+#endif
diff --git a/inc/args.h b/inc/args.h
new file mode 100644 (file)
index 0000000..e518298
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef __ARGS_H_
+#define __ARGS_H_
+
+#include<getopt.h>
+
+#include<opt.h>
+#include<usage.h>
+
+enum sub_command {
+       SUB_COMMAND_INVALID_ARGS = -1,
+       SUB_COMMAND_ADD,
+       SUB_COMMAND_DISMISS,
+       SUB_COMMAND_LS,
+       SUB_COMMAND_POSTPONE,
+       SUB_COMMAND_PRUNE,
+       SUB_COMMAND_RM
+};
+
+int args(int,char**);
+
+#endif
diff --git a/inc/copy.h b/inc/copy.h
new file mode 100644 (file)
index 0000000..ef9a9df
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef __COPY_H_
+#define __COPY_H_
+
+#include<stdio.h>
+
+#include<event.h>
+#include<file.h>
+
+int copy(FILE*,FILE*,time_t);
+int copy_next_event(FILE*,FILE*);
+
+#endif
diff --git a/inc/cut.h b/inc/cut.h
new file mode 100644 (file)
index 0000000..371dac4
--- /dev/null
+++ b/inc/cut.h
@@ -0,0 +1,10 @@
+#ifndef __CUT_H_
+#define __CUT_H_
+
+#include<copy.h>
+#include<event.h>
+#include<file.h>
+
+int cut(struct event*, time_t, size_t);
+
+#endif
diff --git a/inc/defaults.h b/inc/defaults.h
new file mode 100644 (file)
index 0000000..21abbd8
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __DEFAULTS_H_
+#define __DEFAULTS_H_
+
+#include<stdlib.h>
+#include<string.h>
+
+#include<opt.h>
+
+#define EVENTS_FILE "events"
+
+int defaults();
+
+#endif
diff --git a/inc/event.h b/inc/event.h
new file mode 100644 (file)
index 0000000..ef64ed3
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef __EVENT_H_
+#define __EVENT_H_
+
+/* needed for strptime */
+#define _XOPEN_SOURCE
+#define _POSIX_C_SOURCE 200809L
+
+#include<assert.h>
+#include<errno.h>
+#include<stdlib.h>
+#include<stdio.h>
+#include<string.h>
+#include<time.h>
+
+#include<usage.h>
+
+#define EVENT_SERIALIZE_MAX_LENGTH 1024
+
+enum time_period {
+       TIME_PERIOD_NONE = 0,
+       TIME_PERIOD_SECOND,
+       TIME_PERIOD_MINUTE,
+       TIME_PERIOD_HOUR,
+       TIME_PERIOD_DAY,
+       TIME_PERIOD_WEEK,
+       TIME_PERIOD_MONTH,
+       TIME_PERIOD_YEAR
+};
+
+struct event_options {
+       unsigned int duration;
+       enum time_period duration_period;
+       
+       unsigned int recur;
+       enum time_period recur_period;
+};
+
+struct event {
+       struct event_options options;
+
+       char *name;
+       char *place;
+
+       struct tm datetime;
+};
+
+struct event_filter {
+       time_t start;
+       time_t end;
+};
+
+int event_date_compare(const struct event*,time_t);
+void event_free(const struct event*);
+void event_init(struct event*);
+int event_name_set(struct event*, const char*);
+enum time_period event_options_period(char);
+int event_parse(char*, size_t, struct event*);
+int event_place_set(struct event*, const char*);
+int event_serialize(char*, size_t, const struct event*);
+int event_time_set(struct event*, const char*);
+int event_within(struct event*, struct event_filter*);
+
+#endif
diff --git a/inc/file.h b/inc/file.h
new file mode 100644 (file)
index 0000000..b2bc20d
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __FILE_H_
+#define __FILE_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+
+#include<opt.h>
+
+ssize_t file_line_next(char*, size_t, FILE*);
+int file_move(const char*);
+FILE *file_open();
+int file_pipe(FILE*, FILE*);
+FILE *file_temp(char*);
+
+#endif
diff --git a/inc/ls.h b/inc/ls.h
new file mode 100644 (file)
index 0000000..c805335
--- /dev/null
+++ b/inc/ls.h
@@ -0,0 +1,14 @@
+#ifndef __LS_H_
+#define __LS_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+#include<time.h>
+
+#include<event.h>
+#include<file.h>
+#include<opt.h>
+
+int ls();
+
+#endif
diff --git a/inc/main.h b/inc/main.h
new file mode 100644 (file)
index 0000000..7cca03e
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef __MAIN_H_
+#define __MAIN_H_
+
+#include<stdlib.h>
+
+#include<add.h>
+#include<args.h>
+#include<defaults.h>
+#include<ls.h>
+#include<opt.h>
+#include<postpone.h>
+#include<prune.h>
+#include<rm.h>
+
+int main(int,char**);
+
+#endif
diff --git a/inc/opt.h b/inc/opt.h
new file mode 100644 (file)
index 0000000..16f6a3a
--- /dev/null
+++ b/inc/opt.h
@@ -0,0 +1,29 @@
+#ifndef __OPT_H_
+#define __OPT_H_
+
+#include<limits.h>
+#include<stdlib.h>
+#include<string.h>
+
+#include<event.h>
+
+struct options {
+       char *file;
+
+       struct event_options event_options;
+
+       struct event_filter event_filter;
+};
+
+extern struct options global_options;
+
+int opt_duration_set(const char*);
+int opt_event_filter_date_set(time_t*,const char*);
+int opt_event_filter_range_set(const char*, const char*);
+int opt_event_filter_set(const char*);
+int opt_file_set(const char*);
+void opt_global_init();
+int opt_recur_set(const char*);
+int opt_load_from_env();
+
+#endif
diff --git a/inc/postpone.h b/inc/postpone.h
new file mode 100644 (file)
index 0000000..daa5129
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __POSTPONE_H_
+#define __POSTPONE_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+
+#include<add.h>
+#include<cut.h>
+#include<event.h>
+#include<usage.h>
+
+int postpone();
+
+#endif
diff --git a/inc/prune.h b/inc/prune.h
new file mode 100644 (file)
index 0000000..f45aacc
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef __PRUNE_H_
+#define __PRUNE_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+
+#include<file.h>
+#include<seek.h>
+
+int prune();
+
+#endif
diff --git a/inc/rm.h b/inc/rm.h
new file mode 100644 (file)
index 0000000..d93856e
--- /dev/null
+++ b/inc/rm.h
@@ -0,0 +1,17 @@
+#ifndef __RM_H_
+#define __RM_H_
+
+#include<stdio.h>
+#include<stdlib.h>
+#include<time.h>
+
+#include<add.h>
+#include<cut.h>
+#include<event.h>
+#include<file.h>
+
+#define RM_FLAG_DISMISS 1
+
+int rm(int,char**,int);
+
+#endif
diff --git a/inc/seek.h b/inc/seek.h
new file mode 100644 (file)
index 0000000..cca9787
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef __SEEK_H_
+#define __SEEK_H_
+
+#include<event.h>
+#include<file.h>
+
+int seek(FILE*,time_t);
+
+#endif
diff --git a/inc/usage.h b/inc/usage.h
new file mode 100644 (file)
index 0000000..c05b501
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __USAGE_H_
+#define __USAGE_H_
+
+#include<stdio.h>
+
+void usage();
+
+#endif
diff --git a/src/add.c b/src/add.c
new file mode 100644 (file)
index 0000000..df98e35
--- /dev/null
+++ b/src/add.c
@@ -0,0 +1,140 @@
+#include<add.h>
+
+static int add_name_set(struct event*,const char*);
+static int add_place_set(struct event*,const char*);
+static int add_time_set(struct event*,const char*);
+
+int add(int argc, char **argv) {
+       struct event ev;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char timebuf[40];
+
+       event_init(&ev);
+
+       if(global_options.event_options.duration>0) {
+               ev.options.duration = global_options.event_options.duration;
+               ev.options.duration_period = global_options.event_options.duration_period;
+       }
+       
+       if(global_options.event_options.recur>0) {
+               ev.options.recur = global_options.event_options.recur;
+               ev.options.recur_period = global_options.event_options.recur_period;
+       }
+
+       switch(argc-optind) {
+               case 4:
+                       if(add_place_set(&ev,argv[optind+2])<0) { return EXIT_FAILURE; }
+               case 3:
+                       if(add_name_set(&ev,argv[optind+1])<0) { return EXIT_FAILURE; }
+                       if(add_time_set(&ev,argv[argc-1])<0) { return EXIT_FAILURE; }
+                       break;
+               default:
+                       fprintf(stderr,"invalid number of arguments\n");
+                       usage();
+                       return EXIT_FAILURE;
+       }
+       
+       strftime(timebuf,40,"%a, %d %b %Y %T %z",&(ev.datetime));
+
+       if(ev.place!=NULL) {
+               if(snprintf(
+                       buf,
+                       EVENT_SERIALIZE_MAX_LENGTH,
+                       "Added event '%s' at '%s' on %s\n",
+                       ev.name,
+                       ev.place,
+                       timebuf
+               )<0) { goto too_long; }
+       } else {
+               if(snprintf(
+                       buf,
+                       EVENT_SERIALIZE_MAX_LENGTH,
+                       "Added event '%s' on %s\n",
+                       ev.name,
+                       timebuf
+               )<0) { goto too_long; }
+       }
+       
+       if(add_to_file(&ev)<0) { return EXIT_FAILURE; }
+
+       fprintf(stdout,buf);
+
+       return EXIT_SUCCESS;
+too_long:
+       fprintf(stderr,"event too long\n");
+       return EXIT_FAILURE;
+}
+
+static int add_name_set(struct event *ev, const char *p) {
+       assert(ev!=NULL);
+
+       if(NULL==p) { return -1; }
+
+       ev->name = strdup(p);
+       if(NULL==ev->name) { return -1; }
+
+       return 1;
+}
+
+static int add_place_set(struct event *ev, const char *p) {
+       assert(ev!=NULL);
+
+       if(NULL==p) { return -1; }
+
+       ev->place = strdup(p);
+       if(NULL==ev->place) { return -1; }
+
+       return 1;
+}
+
+static int add_time_set(struct event *ev, const char *str) {
+       assert(ev!=NULL);
+
+       if(NULL==str) { return -1; }
+
+       if(event_time_set(ev,str)<0) {
+               fprintf(stderr,"invalid date format provided\n");
+               usage();
+               return -1; 
+       }
+
+       return 1;
+}
+
+int add_to_file(struct event *to_add) {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct tm tm;
+       time_t to_add_time;
+       FILE *src, *to;
+       ssize_t i;
+
+       assert(to_add!=NULL);
+
+       if(NULL==to_add->name) { return -1; }
+
+       char tempfile[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile);
+       if(NULL==to) { return -1; }
+
+       src = file_open();
+       if(NULL==src) { return -1; }
+
+       memcpy(&tm,&(to_add->datetime),sizeof(struct tm));
+       to_add_time = mktime(&tm);
+
+       if(copy(to,src,to_add_time)<0) { return -1; }
+
+       if(event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,to_add)<0) { return -1; }
+
+       i = strlen(buf);
+       if(fwrite(buf,1,i,to)!=i) { return -1; }
+
+       if(file_pipe(to,src)<0) { return -1; }
+
+       if(fclose(src)!=0) { return -1; }
+       if(fclose(to)!=0) { return -1; }
+
+       if(file_move(tempfile)<0) { return -1; }
+
+       return 1;
+}
diff --git a/src/args.c b/src/args.c
new file mode 100644 (file)
index 0000000..21232ab
--- /dev/null
@@ -0,0 +1,76 @@
+#include<args.h>
+
+struct option long_options[] = {
+       {"all", no_argument, 0, 'a'},
+       {"duration", required_argument, 0, 'd'},
+       {"end-date", required_argument, 0, 'e'},
+       {"file", required_argument, 0, 'f'},
+       {"interactive", no_argument, 0, 'i'},
+       {"overdue", no_argument, 0, 'o'},
+       {"recurring", required_argument, 0, 'r'},
+       {"start-date", required_argument, 0, 's'},
+       {"upcoming", required_argument, 0, 'u'},
+       {0,0,0,0}
+};
+
+enum sub_command args(int argc, char **argv) {
+       char c;
+
+       while(1) {
+               int option_index = 0;
+
+               if((c = getopt_long(argc,argv,"ad:e:f:or:s:u:",long_options,&option_index))==-1) { break; }
+
+               switch(c) {
+                       case 'a':
+                               if(opt_event_filter_set("all")<0) { goto fail; }
+                               break;
+                       case 'd':
+                               if(opt_duration_set(optarg)<0) { goto fail; }
+                               break;
+                       case 'e':
+                               if(opt_event_filter_date_set(
+                                       &(global_options.event_filter.end),
+                                               /* time_t *to_set */
+                                       optarg /* const char *date_string */
+                               )<0) { goto fail; }
+                               break;
+                       case 'f':
+                               if(opt_file_set(optarg)<0) { goto fail; }
+                               break;
+                       case 'o':
+                               if(opt_event_filter_set("overdue")<0) { goto fail; }
+                               break;
+                       case 'r':
+                               if(opt_recur_set(optarg)<0) { goto fail; }
+                               break;
+                       case 's':
+                               if(opt_event_filter_date_set(
+                                       &(global_options.event_filter.start),
+                                               /* time_t *to_set */
+                                       optarg /* const char *date_string */
+                                       )<0) { goto fail; }
+                               break;
+                       case 'u':
+                               if(opt_event_filter_range_set("upcoming",optarg)<0) { goto fail; }
+                               break;
+                       case '?':
+                       default:
+                               goto fail;
+               }
+       }
+
+       if(optind>=argc) { return SUB_COMMAND_LS; }
+
+       if(strcmp(argv[optind],"add")==0) { return SUB_COMMAND_ADD; }
+       if(strcmp(argv[optind],"dismiss")==0) { return SUB_COMMAND_DISMISS; }
+       if(strcmp(argv[optind],"ls")==0) { return SUB_COMMAND_LS; }
+       if(strcmp(argv[optind],"postpone")==0) { return SUB_COMMAND_POSTPONE; }
+       if(strcmp(argv[optind],"prune")==0) { return SUB_COMMAND_PRUNE; }
+       if(strcmp(argv[optind],"rm")==0) { return SUB_COMMAND_RM; }
+
+       goto fail;
+fail:
+       usage();
+       return SUB_COMMAND_INVALID_ARGS;
+}
diff --git a/src/copy.c b/src/copy.c
new file mode 100644 (file)
index 0000000..0c3edc1
--- /dev/null
@@ -0,0 +1,56 @@
+#include<copy.h>
+
+int copy(FILE *to, FILE *src, time_t until) {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+       ssize_t i;
+
+       if(NULL==to) { return -1; }
+       if(NULL==src) { return -1; }
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       while((i = file_line_next(buf,EVENT_SERIALIZE_MAX_LENGTH,src))>0) {
+
+               if(event_parse(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev)<0) { return -1; }
+               
+               if(event_date_compare(&ev,until)>0) {
+                       event_free(&ev);
+                       if(fseek(src,-i,SEEK_CUR)!=0) { return -1; }
+                       break;
+               }
+
+               if(event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev)<0) { return -1; }
+
+               if(fwrite(buf,1,i,to)!=i) { return -1; }
+       }
+
+       if(i<0) {
+               if(ferror(src)!=0) {
+                       fprintf(stderr,"failed to get line\n");
+                       return -1;
+               }
+       }
+
+       return 1;
+}
+
+int copy_next_event(FILE *to, FILE *src) {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       ssize_t i;
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       i = file_line_next(buf,EVENT_SERIALIZE_MAX_LENGTH,src);
+
+       if(i<0) {
+               if(ferror(src)!=0) {
+                       fprintf(stderr,"failed to get line");
+                       return -1;
+               }
+       }
+       
+       if(i>0) {
+               if(fwrite(buf,1,i,to)!=i) { return -1; }
+       }
+
+       return 1;
+}
diff --git a/src/cut.c b/src/cut.c
new file mode 100644 (file)
index 0000000..f6239ff
--- /dev/null
+++ b/src/cut.c
@@ -0,0 +1,57 @@
+#include<cut.h>
+
+static int extract(FILE*, struct event*);
+
+int cut(struct event *ev, time_t start, size_t offset) {
+       FILE *src, *to;
+
+       assert(ev!=NULL);
+
+       char tempfile[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile);
+       if(NULL==to) { return -1; }
+
+       src = file_open();
+       if(NULL==src) { return -1; }
+
+       if(copy(to,src,start)<0) { return -1; }
+
+       while(offset>0) {
+               if(copy_next_event(to,src)<0) { return -1; }
+               offset--;
+       }
+
+       if(extract(src,ev)<0) { return -1; }
+
+       if(file_pipe(to,src)<0) { return -1; }
+       
+       if(fclose(src)!=0) { return -1; }
+       if(fclose(to)!=0) { return -1; }
+       
+       if(file_move(tempfile)<0) { return -1; }
+
+       return 1;
+}
+
+static int extract(FILE *src, struct event *ev) {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       ssize_t i;
+
+       assert(ev!=NULL);
+
+       if(NULL==src) { return -1; }
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       
+       i = file_line_next(buf,EVENT_SERIALIZE_MAX_LENGTH,src);
+       if(i<0) {
+               if(ferror(src)!=0) {
+                       fprintf(stderr,"failed to get line");
+                       return -1;
+               }
+       }
+
+       if(event_parse(buf,EVENT_SERIALIZE_MAX_LENGTH,ev)<0) { return -1; }
+
+       return 1;
+}
diff --git a/src/defaults.c b/src/defaults.c
new file mode 100644 (file)
index 0000000..cd3d8f6
--- /dev/null
@@ -0,0 +1,32 @@
+#include<defaults.h>
+
+struct options global_options;
+
+static const char default_file[] = EVENTS_FILE;
+
+static time_t midnight();
+
+int defaults() {
+       opt_global_init();
+
+       if(opt_file_set(default_file)<0) { return -1; }
+
+       global_options.event_filter.start = 0;
+       global_options.event_filter.end = midnight();
+
+       return 1;
+}
+
+static time_t midnight() {
+       struct tm *now;
+       time_t now_timet;
+
+       now_timet = time(NULL);
+       now = localtime(&now_timet);
+
+       now->tm_sec = 59;
+       now->tm_min = 59;
+       now->tm_hour = 23;
+
+       return mktime(now);
+}
diff --git a/src/event.c b/src/event.c
new file mode 100644 (file)
index 0000000..236d4d1
--- /dev/null
@@ -0,0 +1,420 @@
+#include<event.h>
+
+static void event_options_init(struct event_options*);
+static char event_options_period_char(enum time_period period);
+static int event_parse_name(char*, size_t, struct event*);
+static int event_parse_options(char*, size_t, struct event*);
+static int event_parse_place(char*, size_t, struct event*);
+static int event_parse_time(char*, size_t, struct event*);
+static int event_serialize_name(char*, size_t, const struct event*);
+static int event_serialize_options(char*, size_t, const struct event*);
+static int event_serialize_place(char*, size_t, const struct event*);
+static int event_serialize_time(char*, size_t, const struct event*);
+
+int event_date_compare(const struct event *ev, time_t time) {
+       struct tm tm;
+       time_t event_time;
+       memcpy(&tm,&(ev->datetime),sizeof(struct tm));
+
+       event_time = mktime(&tm);
+
+       if(event_time>time) { return 1; }
+       if(event_time<time) { return -1; }
+       return 0;
+}
+
+void event_free(const struct event *p) {
+       if(p->name!=NULL) { free(p->name); }
+       if(p->place!=NULL) { free(p->place); }
+}
+
+void event_init(struct event *p) {
+       assert(p!=NULL);
+
+       event_options_init(&(p->options));
+
+       p->name = NULL;
+       p->place = NULL;
+       
+       memset(&(p->datetime),0,sizeof(struct tm));
+}
+
+static void event_options_init(struct event_options *p) {
+       assert(p!=NULL);
+
+       p->recur = 0;
+       p->recur_period = TIME_PERIOD_NONE;
+       
+       p->duration = 0;
+       p->duration_period = TIME_PERIOD_NONE;
+}
+
+enum time_period event_options_period(char period) {
+       switch(period) {
+               case 'Y':
+                       return TIME_PERIOD_YEAR;
+               case 'M':
+                       return TIME_PERIOD_MONTH;
+               case 'W':
+                       return TIME_PERIOD_WEEK;
+               case 'D':
+                       return TIME_PERIOD_DAY;
+               case 'h':
+                       return TIME_PERIOD_HOUR;
+               case 'm':
+                       return TIME_PERIOD_MINUTE;
+               case 's':
+                       return TIME_PERIOD_SECOND;
+               default:
+                       return TIME_PERIOD_NONE;
+       }
+}
+
+static char event_options_period_char(enum time_period period) {
+       switch(period) {
+               case TIME_PERIOD_SECOND:
+                       return 's';
+               case TIME_PERIOD_MINUTE:
+                       return 'm';
+               case TIME_PERIOD_HOUR:
+                       return 'h';
+               case TIME_PERIOD_DAY:
+                       return 'D';
+               case TIME_PERIOD_WEEK:
+                       return 'W';
+               case TIME_PERIOD_MONTH:
+                       return 'M';
+               case TIME_PERIOD_YEAR:
+                       return 'Y';
+               default:
+                       return '\0';
+       }
+}
+
+int event_name_set(struct event *ev, const char *name) {
+       size_t len;
+
+       if(ev->name!=NULL) { free(ev->name); }
+
+       ev->name = strdup(name);
+       if(ev->name==NULL) { return -1; }
+
+       len = strlen(ev->name);
+       len--;
+       if(ev->name[len]=='\n') {
+               ev->name[len] = '\0';
+       }
+
+       return 1;
+}
+
+int event_parse(char *buf, size_t buf_size, struct event *ev) {
+       event_init(ev);
+
+       if(event_parse_options(buf,buf_size,ev)<0) { return -1; }
+       if(event_parse_time(buf,buf_size,ev)<0) { return -1; }
+       if(event_parse_name(buf,buf_size,ev)<0) { return -1; }
+       if(event_parse_place(buf,buf_size,ev)<0) { return -1; }
+
+       return 1;
+}
+
+static int event_parse_name(char *buf, size_t buf_size, struct event *ev) {
+       const char escaped[] = "\"";
+       const char normal[] = ":";
+       const char *token;
+       char *p;
+
+       if(buf[0]=='"') {
+               token = escaped;
+       } else {
+               token = normal;
+       }
+
+       p = strtok(buf,token);
+       if(p!=NULL) {
+               if(event_name_set(ev,p)<0) { return -1; }
+       }
+
+       p = strtok(NULL,token);
+
+       /*
+        * fix string for if place is also escaped
+        */
+       if(token[0]!=':') {
+               if(p[1]=='\0') {
+                       p[1] = '"';
+               }
+               p++;
+       }
+
+       if(p!=NULL) {
+               memmove(buf,p,(buf_size - (p - buf)));
+       } else {
+               buf[0] = '\0';
+       }
+
+       return 1;
+}
+
+static int event_parse_options(char *buf, size_t buf_size, struct event *ev) {
+       char period;
+       char *options, *p;
+       size_t diff;
+
+       if(buf[0]!='[') { return 0; }
+
+       options = strtok(buf,"]");
+
+       diff = strlen(options)+1;
+
+       p = strtok(&(options[1]),",");
+       while(p!=NULL) {
+               if(memcmp(p,"duration",8)==0) {
+                       if(sscanf(
+                               p,
+                               "duration:%u%c",
+                               &(ev->options.duration),
+                               &period
+                               )!=2) {
+                               if(errno!=0) {
+                                       perror("sccanf");
+                                       return -1;
+                               }
+                       }
+                       ev->options.duration_period = event_options_period(period);
+               } else if(memcmp(p,"recur",5)==0) {
+                       if(sscanf(
+                               p,
+                               "recur:%u%c",
+                               &(ev->options.recur),
+                               &period
+                               )!=2) {
+                               if(errno!=0) {
+                                       perror("sccanf");
+                                       return -1;
+                               }
+                       }
+                       ev->options.recur_period = event_options_period(period);
+               } else { return -1; }
+
+               p = strtok(NULL,",");
+       }
+
+       memmove(buf,&(buf[diff]),(buf_size - diff));
+       return 1;
+}
+
+static int event_parse_place(char *buf, size_t buf_size, struct event *ev) {
+       const char escaped[] = "\"";
+       const char normal[] = ":";
+       const char *token;
+       char *p;
+
+       if(buf[0]=='"') {
+               token = escaped;
+       } else {
+               token = normal;
+       }
+
+       p = strtok(buf,token);
+       if(p!=NULL) {
+               if(event_place_set(ev,p)<0) { return -1; }
+       }
+
+       return 1;
+}
+
+static int event_parse_time(char *buf, size_t buf_size, struct event *ev) {
+       char *p;
+
+       if(buf[10]!=' ') {
+               buf[10] = '\0';
+               p = &(buf[11]);
+       } else if(buf[19]!=' ') {
+               buf[19] = '\0';
+               p = &(buf[20]);
+       } else {
+               buf[25] = '\0';
+               p = &(buf[26]);
+       }
+
+       if(event_time_set(ev,buf)<0) { return -1; }
+
+       memmove(buf,p,(buf_size - (p - buf)));
+
+       return 1;
+}
+
+int event_place_set(struct event *ev, const char *place) {
+       size_t len;
+
+       if(ev->place!=NULL) { free(ev->place); }
+
+       ev->place = strdup(place);
+       if(ev->place==NULL) { return -1; }
+
+       len = strlen(ev->place);
+       len--;
+       if(ev->place[len]=='\n') {
+               ev->place[len] = '\0';
+       }
+
+       return 1;
+}
+
+int event_serialize(char *buf, size_t buf_size, const struct event *ev) {
+       assert(buf!=NULL);
+
+       memset(buf,0,buf_size);
+
+       if(event_serialize_options(buf,buf_size,ev)<0) { return -1; }
+       if(event_serialize_time(buf,buf_size,ev)<0) { return -1; }
+       if(event_serialize_name(buf,buf_size,ev)<0) { return -1; }
+       if(event_serialize_place(buf,buf_size,ev)<0) { return -1; }
+
+       event_free(ev);
+
+       strcat(buf,"\n");
+
+       return 1;
+}
+
+static int event_serialize_name(char *buf, size_t buf_size, const struct event *ev) {
+       char *p;
+
+       if(NULL==ev->name) { return -1; }
+
+       if(strlen(buf)+strlen(ev->name)+2>buf_size) { return -1; }
+
+       strcat(buf,":");
+
+       p = strchr(ev->name,':');
+       if(p!=NULL) { strcat(buf,"\""); }
+
+       strcat(buf,ev->name);
+       
+       if(p!=NULL) { strcat(buf,"\""); }
+
+       return 1;
+}
+
+static int event_serialize_options(char *buf, size_t buf_size, const struct event *ev) {
+       size_t pos;
+
+       if(!((ev->options.duration>0)||(ev->options.recur>0))) { return 0; }
+
+       strcpy(buf,"[");
+       pos = 1;
+
+       if(ev->options.duration>0) {
+               if(sprintf(
+                       &(buf[pos]),
+                       "duration:%u%c",
+                       ev->options.duration,
+                       event_options_period_char(ev->options.duration_period)
+                       )<0) { return -1; }
+
+               if(ev->options.recur>0) {
+                       strcat(buf,",");
+               }
+
+               pos = strlen(buf);
+       }
+
+       if(ev->options.recur>0) {
+               if(sprintf(
+                       &(buf[pos]),
+                       "recur:%u%c",
+                       ev->options.recur,
+                       event_options_period_char(ev->options.recur_period)
+                       )<0) { return -1; }
+       }
+
+       strcat(buf,"]");
+
+       return 1;
+}
+
+static int event_serialize_place(char *buf, size_t buf_size, const struct event *ev) {
+       char *p;
+
+       assert(buf!=NULL);
+
+       if(NULL==ev->place) { return 0; }
+       
+       if(strlen(buf)+strlen(ev->place)+2>buf_size) { return -1; }
+
+       strcat(buf,":");
+       
+       p = strchr(ev->name,':');
+       if(p!=NULL) { strcat(buf,"\""); }
+
+       strcat(buf,ev->place);
+
+       if(p!=NULL) { strcat(buf,"\""); }
+
+       return 1;
+}
+
+static int event_serialize_time(char *buf, size_t buf_size, const struct event *ev) {
+       size_t len;
+
+       assert(buf!=NULL);
+
+       len = strlen(buf);
+       
+       if(ev->datetime.tm_sec==0 && ev->datetime.tm_min==0 && ev->datetime.tm_hour == 0) {
+               if(0==strftime(
+                       &(buf[len]), /* char *s */
+                       buf_size - len, /* size_t max */
+                       "%Y-%m-%d", /* const char *format */
+                       &(ev->datetime) /* const struct tm *tm */
+                       )) { return -1; }
+       } else {
+               if(0==strftime(
+                       &(buf[len]), /* char *s */
+                       buf_size - len, /* size_t max */
+                       "%Y-%m-%d %H:%M:%S %z", /* const char *format */
+                       &(ev->datetime) /* const struct tm *tm */
+                       )) { return -1; }
+       }
+
+       return 1;
+}
+
+int event_time_set(struct event *ev, const char *str) {
+       const char *p;
+
+       p = str;
+
+       memset(&(ev->datetime), 0, sizeof(struct tm));
+
+       /* minimum expected date format */
+       p = strptime(p,"%Y-%m-%d",&(ev->datetime));
+       if(NULL==p) { return -1; }
+       
+       /* attempt to get time information */
+       p = strptime(p,"%H:%M:%S",&(ev->datetime));
+
+       /* attempt to get timezone information */
+       if(p!=NULL) {
+               strptime(p,"%z",&(ev->datetime));
+       }
+
+       return 1;
+}
+
+int event_within(struct event *ev, struct event_filter *filter) {
+       struct tm tm;
+       time_t event_time;
+
+       memcpy(&tm,&(ev->datetime),sizeof(struct tm));
+
+       event_time = mktime(&tm);
+
+       if(filter->start>event_time) { return -1; }
+       if(filter->end<event_time) { return -1; }
+
+       return 1;
+}
diff --git a/src/file/line.c b/src/file/line.c
new file mode 100644 (file)
index 0000000..b842526
--- /dev/null
@@ -0,0 +1,5 @@
+#include<file.h>
+
+ssize_t file_line_next(char *buf, size_t n, FILE *stream) {
+       return getline(&buf,&n,stream);
+}
diff --git a/src/file/mv.c b/src/file/mv.c
new file mode 100644 (file)
index 0000000..688ab97
--- /dev/null
@@ -0,0 +1,10 @@
+#include<file.h>
+
+int file_move(const char *tempname) {
+       if(rename(tempname,global_options.file)==-1) {
+               perror("rename");
+               return -1;
+       }
+
+       return 1;
+}
diff --git a/src/file/open.c b/src/file/open.c
new file mode 100644 (file)
index 0000000..ecbf85e
--- /dev/null
@@ -0,0 +1,25 @@
+#include<file.h>
+
+static FILE *file_create_prompt();
+
+static FILE *file_create_prompt() {
+       fprintf(stderr,"%s doesn't exist... create? [Y/n] ",global_options.file);
+       if(fgetc(stdin)!='Y') { return NULL; }
+       return fopen(global_options.file,"w+");
+}
+
+FILE *file_open() {
+       FILE *fp;
+
+       fp = fopen(global_options.file,"r");
+       if(NULL==fp) {
+               if(errno!=ENOENT) {
+                       perror("fopen");
+                       return NULL;
+               } else {
+                       fp = file_create_prompt();
+               }
+       }
+
+       return fp;
+}
diff --git a/src/file/pipe.c b/src/file/pipe.c
new file mode 100644 (file)
index 0000000..1f6c439
--- /dev/null
@@ -0,0 +1,14 @@
+#include<file.h>
+
+int file_pipe(FILE *to, FILE *from) {
+       char buf[100];
+       size_t i;
+
+       while((i = fread(buf, 1, 100, from))>0) {
+               if(fwrite(buf,1,i,to)!=i) { return -1; }
+       }
+
+       if(feof(from)==0) { return -1; }
+
+       return 1;
+}
diff --git a/src/file/tmp.c b/src/file/tmp.c
new file mode 100644 (file)
index 0000000..ca4050e
--- /dev/null
@@ -0,0 +1,11 @@
+#include<file.h>
+
+FILE *file_temp(char *template) {
+       int fd = mkstemp(template);
+       if(fd<0) {
+               perror("mkstemp");
+               return NULL;
+       }
+
+       return fdopen(fd,"w");
+}
diff --git a/src/ls.c b/src/ls.c
new file mode 100644 (file)
index 0000000..ad07d9c
--- /dev/null
+++ b/src/ls.c
@@ -0,0 +1,59 @@
+#include<ls.h>
+
+static int event_print(struct event*);
+
+int ls() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+       ssize_t i;
+       FILE *fp;
+
+       fp = file_open();
+       if(fp==NULL) { return EXIT_FAILURE; }
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       while((i = file_line_next(buf,EVENT_SERIALIZE_MAX_LENGTH,fp))>0) {
+
+               if(event_parse(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev)<0) { goto close; }
+               
+               if(event_within(&ev,&(global_options.event_filter))>0) {
+                       if(event_print(&ev)<0) { goto close; }
+               }
+       
+               memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       }
+
+       if(fclose(fp)!=0) {
+               perror("fclose");
+               return EXIT_FAILURE;
+       }
+
+       return EXIT_SUCCESS;
+close:
+       fclose(fp);
+       return EXIT_FAILURE;
+}
+
+static int event_print(struct event *ev) {
+       char datebuf[20];
+       if(
+               (ev->datetime.tm_sec==0) &&
+               (ev->datetime.tm_min == 0) &&
+               (ev->datetime.tm_hour == 0)
+         ) {
+               if(strftime(datebuf,20,"%Y-%m-%d",&(ev->datetime))!=10) { return -1; }
+       } else {
+               if(strftime(datebuf,20,"%Y-%m-%d %H:%M:%S",&(ev->datetime))!=19) { return -1; }
+       }
+
+       if(fprintf(stdout,"%s\t",datebuf)<0) { return -1; }
+       if(fprintf(stdout,"%s",ev->name)<0) { return -1; }
+
+       if(ev->place!=NULL) {
+               if(fprintf(stdout," @ %s",ev->place)<0) { return -1; }
+       }
+
+       if(fprintf(stdout,"\n")<0) { return -1; }
+
+       return 1;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..52bf386
--- /dev/null
@@ -0,0 +1,28 @@
+#include<main.h>
+
+int main(int argc, char **argv) {
+       enum sub_command cmd;
+
+       if(defaults()<0) { return EXIT_FAILURE; }
+       if(opt_load_from_env()<0) { return EXIT_FAILURE; }
+
+       cmd = args(argc,argv);
+       if(SUB_COMMAND_INVALID_ARGS==cmd) { return EXIT_FAILURE; }
+
+       switch(cmd) {
+               case SUB_COMMAND_ADD:
+                       return add(argc,argv);
+               case SUB_COMMAND_DISMISS:
+                       return rm(argc,argv,RM_FLAG_DISMISS);
+               case SUB_COMMAND_LS:
+                       return ls();
+               case SUB_COMMAND_POSTPONE:
+                       return postpone(argc,argv);
+               case SUB_COMMAND_PRUNE:
+                       return prune();
+               case SUB_COMMAND_RM:
+                       return rm(argc,argv,0);
+               default:
+                       return EXIT_FAILURE;
+       }
+}
diff --git a/src/opt/duration.c b/src/opt/duration.c
new file mode 100644 (file)
index 0000000..82c882e
--- /dev/null
@@ -0,0 +1,25 @@
+#include<opt.h>
+
+int opt_duration_set(const char *p) {
+       unsigned int duration;
+       char c;
+       enum time_period period;
+
+       if(NULL==p) { return -1; }
+
+       if(sscanf(p,"%u%c",&duration,&c)!=2) {
+               fprintf(stderr,"invalid duration option value: %s\n",p);
+               return -1;
+       }
+
+       period = event_options_period(c);
+       if(TIME_PERIOD_NONE==period) {
+               fprintf(stderr,"invalid duration option value: %s\n",p);
+               return -1;
+       }
+
+       global_options.event_options.duration = duration;
+       global_options.event_options.duration_period = period;
+
+       return 1;
+}
diff --git a/src/opt/env.c b/src/opt/env.c
new file mode 100644 (file)
index 0000000..acf6298
--- /dev/null
@@ -0,0 +1,19 @@
+#include<opt.h>
+
+#define CHECK_ENV(x,f) { \
+       p = getenv("EVENTS_"x); \
+       if(p!=NULL) { \
+               if(f(p)<0) { return -1; } \
+       } \
+}
+
+int opt_load_from_env() {
+       char *p;
+
+       CHECK_ENV("DURATION",opt_duration_set);
+       CHECK_ENV("EVENT_FILTER",opt_event_filter_set);
+       CHECK_ENV("FILE",opt_file_set);
+       CHECK_ENV("RECUR",opt_recur_set);
+
+       return 1;
+}
diff --git a/src/opt/file.c b/src/opt/file.c
new file mode 100644 (file)
index 0000000..2e539db
--- /dev/null
@@ -0,0 +1,10 @@
+#include<opt.h>
+
+int opt_file_set(const char *p) {
+       if(global_options.file!=NULL) { free(global_options.file); }
+
+       global_options.file = strdup(p);
+       if(NULL==global_options.file) { return -1; }
+
+       return 1;
+}
diff --git a/src/opt/filter.c b/src/opt/filter.c
new file mode 100644 (file)
index 0000000..60f6d7b
--- /dev/null
@@ -0,0 +1,100 @@
+#include<opt.h>
+
+static int opt_event_filter_range_set_upcoming(const char*);
+
+int opt_event_filter_date_set(time_t *to_set, const char *date_string) {
+       struct event ev;
+
+       assert(to_set!=NULL);
+
+       /*
+        * This function uses the event_time_set(struct event *ev, const char *str)
+        * method for consistencies' sake.
+        */
+
+       if(event_time_set(&ev,date_string)<0) {
+               fprintf(stderr,"invalid date given: %s\n",date_string);
+               return -1;
+       }
+
+       (*to_set) = mktime(&(ev.datetime));
+
+       return 1;
+}
+
+int opt_event_filter_range_set(const char *type, const char *arg) {
+       if(strcmp(type,"upcoming")==0) {
+               if(opt_event_filter_range_set_upcoming(arg)<0) {
+                       fprintf(stderr,
+                               "invalid time period given as argument to --upcoming option\n"
+                       );
+                       return -1;
+               }
+               return 1;
+       }
+
+       fprintf(stderr,"should never get to here\n");
+       return -1;
+}
+
+static int opt_event_filter_range_set_upcoming(const char *arg) {
+       struct tm *now;
+       time_t now_timet;
+       unsigned int offset;
+       char period;
+
+       now_timet = time(NULL);
+       now = localtime(&now_timet);
+
+       if(sscanf(arg,"%u%c",&offset,&period)!=2) {
+               if(errno!=0) { perror("sscanf"); }
+               return -1;
+       }
+
+       switch(event_options_period(period)) {
+               case TIME_PERIOD_YEAR:
+                       now->tm_year += offset;
+                       break;
+               case TIME_PERIOD_MONTH:
+                       now->tm_mon += offset;
+                       break;
+               case TIME_PERIOD_WEEK:
+                       now->tm_mday += 7*offset;
+                       break;
+               case TIME_PERIOD_DAY:
+                       now->tm_mday += offset;
+                       break;
+               case TIME_PERIOD_HOUR:
+                       now->tm_hour += offset;
+                       break;
+               case TIME_PERIOD_MINUTE:
+                       now->tm_min += offset;
+                       break;
+               case TIME_PERIOD_SECOND:
+                       now->tm_sec += offset;
+                       break;
+               default:
+                       return -1;
+       }
+
+       global_options.event_filter.start = time(NULL);
+       global_options.event_filter.end = mktime(now);
+
+       return 1;
+}
+
+int opt_event_filter_set(const char *p) {
+       if(strcmp(p,"all")==0) {
+               global_options.event_filter.start = 0;
+               global_options.event_filter.end = INT_MAX;
+               return 1;
+       }
+
+       if(strcmp(p,"overdue")==0) {
+               global_options.event_filter.start = 0;
+               global_options.event_filter.end = time(NULL);
+               return 1;
+       }
+
+       return -1;
+}
diff --git a/src/opt/global.c b/src/opt/global.c
new file mode 100644 (file)
index 0000000..74bdd75
--- /dev/null
@@ -0,0 +1,14 @@
+#include<opt.h>
+
+void opt_global_init() {
+       global_options.file = NULL;
+
+       global_options.event_options.duration = 0;
+       global_options.event_options.duration_period = TIME_PERIOD_NONE;
+
+       global_options.event_options.recur = 0;
+       global_options.event_options.recur_period = TIME_PERIOD_NONE;
+       
+       global_options.event_filter.start = 0;
+       global_options.event_filter.end = 0;
+}
diff --git a/src/opt/recur.c b/src/opt/recur.c
new file mode 100644 (file)
index 0000000..d7a4938
--- /dev/null
@@ -0,0 +1,25 @@
+#include<opt.h>
+
+int opt_recur_set(const char *p) {
+       unsigned int recur;
+       char c;
+       enum time_period period;
+
+       if(NULL==p) { return -1; }
+
+       if(sscanf(p,"%u%c",&recur,&c)!=2) {
+               fprintf(stderr,"invalid recur option value: %s\n",p);
+               return -1;
+       }
+
+       period = event_options_period(c);
+       if(TIME_PERIOD_NONE==period) {
+               fprintf(stderr,"invalid recur option value: %s\n",p);
+               return -1;
+       }
+
+       global_options.event_options.recur = recur;
+       global_options.event_options.recur_period = period;
+
+       return 1;
+}
diff --git a/src/postpone.c b/src/postpone.c
new file mode 100644 (file)
index 0000000..263971b
--- /dev/null
@@ -0,0 +1,135 @@
+#include<postpone.h>
+
+static char *postpone_offset_string = NULL;
+
+static int handle_args(int,char**,unsigned long int*,time_t*);
+static int postpone_event(struct event*);
+
+static int handle_args(int argc, char **argv, unsigned long int *offset, time_t *start_time) {
+       unsigned long int i;
+       
+       assert(offset!=NULL);
+       assert(start_time!=NULL);
+
+       switch(argc-optind) {
+               case 4: /* date index period */
+                       if(opt_event_filter_date_set(start_time,argv[optind+1])<0) {
+                               goto invalid;
+                       }
+
+                       i = strtoul(argv[optind+2],NULL,0);
+                       if(i>UINT_MAX) { goto invalid; }
+                       (*offset) = i;
+
+                       postpone_offset_string = strdup(argv[argc-1]);
+                       if(NULL==postpone_offset_string) { goto invalid; }
+                       break;
+               case 3: /* date index */
+                       if(opt_event_filter_date_set(start_time,argv[optind+1])<0) {
+                               goto invalid;
+                       }
+                       
+                       i = strtoul(argv[argc-1],NULL,0);
+                       if(i>UINT_MAX) { goto invalid; }
+                       (*offset) = i;
+                       break;
+               case 2: /* index */
+                       (*start_time) = 0;
+
+                       i = strtoul(argv[argc-1],NULL,0);
+                       if(i>UINT_MAX) { goto invalid; }
+                       (*offset) = i;
+                       break;
+               default:
+                       goto invalid;
+       }
+
+       return 1;
+invalid:
+       fprintf(stderr,"invalid number of arguments\n");
+       usage();
+       return -1;
+}
+
+int postpone(int argc, char **argv) {
+       char timebuf[40];
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       time_t start_time;
+       size_t offset;
+       struct event ev;
+
+       if(handle_args(argc,argv,&offset,&start_time)<0) { goto fail; }
+
+       if(cut(&ev,start_time,offset)<0) { goto fail; }
+
+       if(postpone_event(&ev)<0) { goto fail; }
+       
+       strftime(timebuf,40,"%a, %d %b %Y %T %z",&(ev.datetime));
+
+       if(snprintf(
+               buf,
+               EVENT_SERIALIZE_MAX_LENGTH,
+               "postponed event '%s' until '%s'\n",
+               ev.name,
+               timebuf
+               )<0) { goto fail; }
+
+       if(add_to_file(&ev)<0) { goto fail; }
+
+       fprintf(stdout,buf);
+
+       return EXIT_SUCCESS;
+fail:
+       if(postpone_offset_string!=NULL) { free(postpone_offset_string); }
+       return EXIT_FAILURE;
+}
+
+int postpone_event(struct event *ev) {
+       unsigned int offset;
+       char period;
+
+       assert(ev!=NULL);
+
+       if(NULL==postpone_offset_string) {
+               ev->datetime.tm_mday += 1;
+               return 1;
+       }
+
+       if(sscanf(postpone_offset_string,"%u%c",&offset,&period)!=2) {
+               if(errno!=0) {
+                       perror("sscanf");
+                       return -1;
+               }
+       }
+
+       switch(event_options_period(period)) {
+               case TIME_PERIOD_YEAR:
+                       ev->datetime.tm_year += offset;
+                       break;
+               case TIME_PERIOD_MONTH:
+                       ev->datetime.tm_mon += offset;
+                       break;
+               case TIME_PERIOD_WEEK:
+                       ev->datetime.tm_mday += 7*offset;
+                       break;
+               case TIME_PERIOD_DAY:
+                       ev->datetime.tm_mday += offset;
+                       break;
+               case TIME_PERIOD_HOUR:
+                       ev->datetime.tm_hour += offset;
+                       break;
+               case TIME_PERIOD_MINUTE:
+                       ev->datetime.tm_min += offset;
+                       break;
+               case TIME_PERIOD_SECOND:
+                       ev->datetime.tm_sec += offset;
+                       break;
+               default:
+                       return -1;
+       }
+
+       free(postpone_offset_string);
+       postpone_offset_string = NULL;
+
+       return 1;
+}
diff --git a/src/prune.c b/src/prune.c
new file mode 100644 (file)
index 0000000..0c3bc44
--- /dev/null
@@ -0,0 +1,23 @@
+#include<prune.h>
+
+int prune() {
+       FILE *src, *to;
+
+       char tempfile[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile);
+       if(NULL==to) { return EXIT_FAILURE; }
+
+       src = file_open();
+       if(NULL==src) { return EXIT_FAILURE; }
+
+       if(seek(src,time(NULL))<0) { return EXIT_FAILURE; }
+
+       if(file_pipe(to,src)<0) { return EXIT_FAILURE; }
+
+       if(fclose(src)!=0) { return EXIT_FAILURE; }
+       if(fclose(to)!=0) { return EXIT_FAILURE; }
+       
+       if(file_move(tempfile)<0) { return EXIT_FAILURE; }
+
+       return EXIT_SUCCESS;
+}
diff --git a/src/rm.c b/src/rm.c
new file mode 100644 (file)
index 0000000..5bc2e7f
--- /dev/null
+++ b/src/rm.c
@@ -0,0 +1,107 @@
+#include<rm.h>
+
+static int check_need_readd(struct event*);
+static int handle_args(int,char**,unsigned long int*, time_t *);
+
+static int check_need_readd(struct event *ev) {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char timebuf[40];
+
+       assert(ev!=NULL);
+
+       if(ev->options.recur==0) { return 1; }
+
+       switch(ev->options.recur_period) {
+               case TIME_PERIOD_YEAR:
+                       ev->datetime.tm_year += ev->options.recur;
+                       break;
+               case TIME_PERIOD_MONTH:
+                       ev->datetime.tm_mon += ev->options.recur;
+                       break;
+               case TIME_PERIOD_WEEK:
+                       ev->datetime.tm_mday += 7*(ev->options.recur);
+                       break;
+               case TIME_PERIOD_DAY:
+                       ev->datetime.tm_mday += ev->options.recur;
+                       break;
+               case TIME_PERIOD_HOUR:
+                       ev->datetime.tm_hour += ev->options.recur;
+                       break;
+               case TIME_PERIOD_MINUTE:
+                       ev->datetime.tm_min += ev->options.recur;
+                       break;
+               case TIME_PERIOD_SECOND:
+                       ev->datetime.tm_sec += ev->options.recur;
+                       break;
+               default:
+                       return -1;
+       }
+
+       strftime(timebuf,40,"%a, %d %b %Y %T %z",&(ev->datetime));
+
+       if(snprintf(
+               buf,
+               EVENT_SERIALIZE_MAX_LENGTH,
+               "re-added event '%s' on %s\n",
+               ev->name,
+               timebuf
+               )<0) { return -1; }
+
+       if(add_to_file(ev)<0) { return -1; }
+
+       fprintf(stdout,buf);
+
+       return 1;
+}
+
+static int handle_args(int argc, char **argv, unsigned long int *offset, time_t *start_time) {
+       unsigned long i;
+
+       assert(offset!=NULL);
+       assert(start_time!=NULL);
+
+       (*start_time) = 0;
+
+       switch(argc-optind) {
+               case 3:
+                       if(opt_event_filter_date_set(start_time,argv[optind+1])<0) {
+                               goto invalid;
+                       }
+               case 2:
+                       i = strtoul(argv[argc-1],NULL,0);
+                       if(i>UINT_MAX) { goto invalid; }
+                       (*offset) = i;
+                       break;
+               default:
+                       goto invalid;
+       }
+
+       return 1;
+invalid:
+       fprintf(stderr,"invalid arguments\n");
+       usage();
+       return -1;
+}
+
+int rm(int argc, char **argv, int flags) {
+       char timebuf[40];
+       time_t start_time;
+       size_t offset;
+       struct event ev;
+
+       if(handle_args(argc,argv,&offset,&start_time)<0) { return EXIT_FAILURE; }
+
+       if(cut(&ev,start_time,offset)<0) { return EXIT_FAILURE; }
+
+       strftime(timebuf,40,"%a, %d %b %Y %T %z",&(ev.datetime));
+
+       fprintf(stderr,"removed event %s on %s\n",ev.name,timebuf);
+
+       if(RM_FLAG_DISMISS==flags) {
+               if(check_need_readd(&ev)<0) { return EXIT_FAILURE; }
+       } else {
+               event_free(&ev);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/src/seek.c b/src/seek.c
new file mode 100644 (file)
index 0000000..8378202
--- /dev/null
@@ -0,0 +1,32 @@
+#include<seek.h>
+
+int seek(FILE *fp, time_t time) {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+       ssize_t i;
+
+       if(NULL==fp) { return -1; }
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       while((i = file_line_next(buf,EVENT_SERIALIZE_MAX_LENGTH,fp))>0) {
+               if(event_parse(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev)<0) { return -1; }
+
+               if(event_date_compare(&ev,time)>=0) {
+                       event_free(&ev);
+                       if(fseek(fp,-i,SEEK_CUR)!=0) { return -1; }
+                       break;
+               }
+               
+               event_free(&ev);
+               memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       }
+
+       if(i<0) {
+               if(ferror(fp)!=0) {
+                       fprintf(stderr,"failed to get line");
+                       return -1;
+               }
+       }
+
+       return 1;
+}
diff --git a/src/usage.c b/src/usage.c
new file mode 100644 (file)
index 0000000..4b6c991
--- /dev/null
@@ -0,0 +1,21 @@
+#include<usage.h>
+
+void usage() {
+       fprintf(stderr,"Usage:\n");
+       fprintf(stderr,"\tev [options] [command = 'ls'] [arg1, arg2, ..., argn]\n");
+       fprintf(stderr,"\n");
+       fprintf(stderr,"Commands:\n");
+       fprintf(stderr,"\tadd\n");
+       fprintf(stderr,"\tdismiss\n");
+       fprintf(stderr,"\tls\n");
+       fprintf(stderr,"\tpostpone\n");
+       fprintf(stderr,"\tprune\n");
+       fprintf(stderr,"\trm\n");
+       fprintf(stderr,"\n");
+
+       fprintf(stderr,"Options:\n");
+       fprintf(stderr,"\n");
+       fprintf(stderr,"See `man ev` for more information\n");
+
+       return;
+}
diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am
new file mode 100644 (file)
index 0000000..50b3bc2
--- /dev/null
@@ -0,0 +1,153 @@
+AM_CPPFLAGS = \
+       -Wall \
+       -Werror
+
+EXTRA_DIST = \
+       test_macros.h \
+       test_utils.h
+
+if ENABLE_DEBUG
+else
+AM_CPPFLAGS += \
+       -DNDEBUG
+endif
+
+check_PROGRAMS = add.tests args.tests copy.tests cut.tests event.tests file.tests ls.tests opt.tests postpone.tests prune.tests rm.tests seek.tests
+TESTS = $(check_PROGRAMS)
+
+if ENABLE_MEMCHECK
+LOG_COMPILER = $(VALGRIND)
+AM_LOG_FLAGS = --leak-check=full -v --track-origins=yes --error-exitcode=1
+endif
+
+common_SOURCES = \
+       test_utils.c \
+       $(top_srcdir)/src/opt/global.c
+
+add_tests_SOURCES = \
+       $(common_SOURCES) \
+       add.tests.c \
+       $(top_srcdir)/src/copy.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/mv.c \
+       $(top_srcdir)/src/file/open.c \
+       $(top_srcdir)/src/file/pipe.c \
+       $(top_srcdir)/src/file/tmp.c \
+       $(top_srcdir)/src/usage.c
+
+add_tests_CPPFLAGS = $(AM_CPPFLAGS) \
+       -DADD_SRC_FILE="$(top_srcdir)/src/add.c"
+
+args_tests_SOURCES = \
+       $(common_SOURCES) \
+       args.tests.c \
+       $(top_srcdir)/src/args.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/opt/duration.c \
+       $(top_srcdir)/src/opt/file.c \
+       $(top_srcdir)/src/opt/filter.c \
+       $(top_srcdir)/src/opt/recur.c \
+       $(top_srcdir)/src/usage.c
+
+copy_tests_SOURCES = \
+       $(common_SOURCES) \
+       copy.tests.c \
+       $(top_srcdir)/src/copy.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/tmp.c
+
+cut_tests_SOURCES = \
+       $(common_SOURCES) \
+       cut.tests.c \
+       $(top_srcdir)/src/copy.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/open.c \
+       $(top_srcdir)/src/file/pipe.c \
+       $(top_srcdir)/src/file/mv.c \
+       $(top_srcdir)/src/file/tmp.c
+
+cut_tests_CPPFLAGS = $(AM_CPPFLAGS) \
+       -DCUT_SRC_FILE="$(top_srcdir)/src/cut.c"
+
+event_tests_SOURCES = \
+       event.tests.c \
+       $(top_srcdir)/src/event.c
+
+file_tests_SOURCES = \
+       $(common_SOURCES) \
+       file.tests.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/mv.c \
+       $(top_srcdir)/src/file/open.c \
+       $(top_srcdir)/src/file/pipe.c \
+       $(top_srcdir)/src/file/tmp.c
+
+ls_tests_SOURCES = \
+       $(common_SOURCES) \
+       ls.tests.c
+
+opt_tests_SOURCES = \
+       $(common_SOURCES) \
+       opt.tests.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/opt/duration.c \
+       $(top_srcdir)/src/opt/filter.c \
+       $(top_srcdir)/src/opt/recur.c
+
+postpone_tests_SOURCES = \
+       $(common_SOURCES) \
+       postpone.tests.c \
+       $(top_srcdir)/src/add.c \
+       $(top_srcdir)/src/copy.c \
+       $(top_srcdir)/src/cut.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/mv.c \
+       $(top_srcdir)/src/file/open.c \
+       $(top_srcdir)/src/file/pipe.c \
+       $(top_srcdir)/src/file/tmp.c \
+       $(top_srcdir)/src/opt/filter.c \
+       $(top_srcdir)/src/usage.c
+
+postpone_tests_CPPFLAGS = $(AM_CPPFLAGS) \
+       -DPOSTPONE_SRC_FILE="$(top_srcdir)/src/postpone.c"
+
+prune_tests_SOURCES = \
+       $(common_SOURCES) \
+       prune.tests.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/mv.c \
+       $(top_srcdir)/src/file/open.c \
+       $(top_srcdir)/src/file/pipe.c \
+       $(top_srcdir)/src/file/tmp.c \
+       $(top_srcdir)/src/prune.c \
+       $(top_srcdir)/src/seek.c
+
+rm_tests_SOURCES = \
+       $(common_SOURCES) \
+       rm.tests.c \
+       $(top_srcdir)/src/add.c \
+       $(top_srcdir)/src/copy.c \
+       $(top_srcdir)/src/cut.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/file/mv.c \
+       $(top_srcdir)/src/file/open.c \
+       $(top_srcdir)/src/file/pipe.c \
+       $(top_srcdir)/src/file/tmp.c \
+       $(top_srcdir)/src/opt/filter.c \
+       $(top_srcdir)/src/usage.c
+
+rm_tests_CPPFLAGS = $(AM_CPPFLAGS) \
+       -DRM_SRC_FILE="$(top_srcdir)/src/rm.c"
+
+seek_tests_SOURCES = \
+       $(common_SOURCES) \
+       seek.tests.c \
+       $(top_srcdir)/src/event.c \
+       $(top_srcdir)/src/file/line.c \
+       $(top_srcdir)/src/seek.c
diff --git a/test/unit/add.tests.c b/test/unit/add.tests.c
new file mode 100644 (file)
index 0000000..1d56e26
--- /dev/null
@@ -0,0 +1,182 @@
+#include<test_utils.h>
+
+#include<add.h>
+
+#include<unistd.h>
+
+#include INCLUDE(ADD_SRC_FILE)
+
+int main();
+static void add_name_set_basic_test();
+static void add_place_set_basic_test();
+static void add_time_set_basic_test();
+static void add_to_file_basic_test();
+static void add_to_file_extensive_test();
+
+int main() {
+       setup_env();
+
+       add_name_set_basic_test();
+       add_place_set_basic_test();
+       add_time_set_basic_test();
+       add_to_file_basic_test();
+       add_to_file_extensive_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void add_name_set_basic_test() {
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(-1==add_name_set(&ev,NULL));
+       
+       assert(1==add_name_set(&ev,"test"));
+
+       assert(strcmp(ev.name,"test")==0);
+
+       event_free(&ev);
+}
+
+static void add_place_set_basic_test() {
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(-1==add_place_set(&ev,NULL));
+
+       assert(1==add_place_set(&ev,"test"));
+
+       assert(strcmp(ev.place,"test")==0);
+
+       event_free(&ev);
+}
+
+static void add_time_set_basic_test() {
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(-1==add_time_set(&ev,NULL));
+
+       assert(-1==add_time_set(&ev,"test"));
+
+       assert(1==add_time_set(&ev,"2022-09-01"));
+
+       assert(mktime(&(ev.datetime))==1662019200);
+}
+
+static void add_to_file_basic_test() {
+       struct event ev;
+       char buf[1000];
+       FILE *fp;
+
+       event_init(&ev);
+
+       fp = fopen(global_options.file,"w");
+       assert(fp!=NULL);
+       assert(fclose(fp)==0);
+
+       assert(-1==add_to_file(&ev));
+
+       assert(1==add_name_set(&ev,"name"));
+       assert(1==add_time_set(&ev,"2022-08-01"));
+
+       assert(1==add_to_file(&ev));
+       
+       fp = fopen(global_options.file,"r");
+       assert(fp!=NULL);
+
+       memset(buf,0,1000);
+       assert(fread(buf,1,1000,fp)==16);
+       assert(strcmp(buf,"2022-08-01:name\n")==0);
+
+       assert(fclose(fp)==0);
+
+       assert(1==add_name_set(&ev,"event2"));
+       assert(1==add_time_set(&ev,"2022-09-01"));
+       
+       assert(1==add_to_file(&ev));
+
+       fp = fopen(global_options.file,"r");
+       assert(fp!=NULL);
+
+       memset(buf,0,1000);
+       assert(fread(buf,1,1000,fp)==34);
+       assert(strcmp(buf,"2022-08-01:name\n2022-09-01:event2\n")==0);
+
+       assert(fclose(fp)==0);
+
+       assert(1==add_name_set(&ev,"event3"));
+       assert(1==add_place_set(&ev,"place"));
+       assert(1==add_time_set(&ev,"2022-06-01 20:00:00"));
+       
+       assert(1==add_to_file(&ev));
+
+       fp = fopen(global_options.file,"r");
+       assert(fp!=NULL);
+
+       memset(buf,0,1000);
+       assert(fread(buf,1,1000,fp)==73);
+       assert(strcmp(buf,"2022-06-01 20:00:00 +0000:event3:place\n2022-08-01:name\n2022-09-01:event2\n")==0);
+
+       assert(fclose(fp)==0);
+
+       reset_env();
+}
+
+static void add_to_file_extensive_test() {
+       int used[12];
+       size_t index, rounds;
+       char date[11];
+       char name[10];
+       char buf[1000];
+       struct event ev;
+       FILE *fp;
+
+       rounds = 0;
+       while(rounds<100) {
+               memset(used,0,sizeof(int)*12);
+       
+               fp = fopen(global_options.file,"w");
+               assert(fp!=NULL);
+               assert(fclose(fp)==0);
+
+               for(size_t i=0;i<12;i++) {
+                       memset(date,0,11);
+                       memset(name,0,10);
+
+                       index = rand()%12;
+                       while(used[index]!=0) {
+                               index++;
+                               index %= 12;
+                       }
+                       used[index] = 1;
+
+                       assert(10==sprintf(date,"2000-%02lu-01",index+1));
+                       assert(((index>9)?7:6)==sprintf(name,"event%lu",index));
+       
+                       event_init(&ev);
+                       assert(1==add_name_set(&ev,name));
+                       assert(1==add_time_set(&ev,date));
+
+                       assert(1==add_to_file(&ev));
+               }
+       
+               fp = fopen(global_options.file,"r");
+               assert(fp!=NULL);
+       
+               memset(buf,0,1000);
+               assert(fread(buf,1,1000,fp)==218);
+               assert(strcmp(buf,"2000-01-01:event0\n2000-02-01:event1\n2000-03-01:event2\n2000-04-01:event3\n2000-05-01:event4\n2000-06-01:event5\n2000-07-01:event6\n2000-08-01:event7\n2000-09-01:event8\n2000-10-01:event9\n2000-11-01:event10\n2000-12-01:event11\n")==0);
+
+               assert(fclose(fp)==0);
+
+               reset_env();
+
+               rounds++;
+       }
+}
diff --git a/test/unit/args.tests.c b/test/unit/args.tests.c
new file mode 100644 (file)
index 0000000..a517266
--- /dev/null
@@ -0,0 +1,334 @@
+#include<test_utils.h>
+
+#include<args.h>
+
+int main();
+static void args_add_basic_test();
+static void args_basic_test();
+static void args_dismiss_basic_test();
+static void args_long_opts_basic_test();
+static void args_ls_all_basic_test();
+static void args_ls_basic_test();
+static void args_ls_end_date_basic_test();
+static void args_ls_overdue_basic_test();
+static void args_ls_start_date_basic_test();
+static void args_ls_upcoming_basic_test();
+static void args_order_test();
+static void args_postpone_basic_test();
+static void args_prune_basic_test();
+static void args_rm_basic_test();
+static void args_short_opts_basic_test();
+
+int main() {
+       setup_env();
+
+       args_basic_test();
+       args_order_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void args_add_basic_test() {
+       char *add_args[] = {
+               "ev",
+               "add",
+               NULL
+       };
+
+       assert(SUB_COMMAND_ADD==args(2,add_args));
+       optind = 0;
+}
+
+static void args_basic_test() {
+       assert(SUB_COMMAND_LS==args(0,NULL));
+
+       args_add_basic_test();
+       args_dismiss_basic_test();
+       args_ls_basic_test();
+       args_ls_all_basic_test();
+       args_ls_end_date_basic_test();
+       args_ls_overdue_basic_test();
+       args_ls_upcoming_basic_test();
+       args_ls_start_date_basic_test();
+       args_postpone_basic_test();
+       args_prune_basic_test();
+       args_rm_basic_test();
+
+       args_short_opts_basic_test();
+       args_long_opts_basic_test();
+}
+
+static void args_dismiss_basic_test() {
+       char *dismiss_args[] = {
+               "ev",
+               "dismiss",
+               NULL
+       };
+
+       assert(SUB_COMMAND_DISMISS==args(2,dismiss_args));
+       optind = 0;
+}
+
+static void args_long_opts_basic_test() {
+       char *long_opts_args[] = {
+               "ev",
+               "add",
+               "--duration",
+               "1W",
+               "--file",
+               "/tmp/events.test2",
+               "--recurring",
+               "1Y",
+               NULL
+       };
+       assert(SUB_COMMAND_ADD==args(8,long_opts_args));
+       assert(global_options.event_options.duration==1);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_WEEK);
+       assert(strcmp(global_options.file,"/tmp/events.test2")==0);
+       assert(global_options.event_options.recur==1);
+       assert(global_options.event_options.recur_period==TIME_PERIOD_YEAR);
+       optind = 0;
+
+       reset_env();
+}
+
+static void args_ls_all_basic_test() {
+       char *ls_all_args[] = {
+               "ev",
+               "ls",
+               "--all",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(3,ls_all_args));
+       assert(global_options.event_filter.start==0);
+       assert(global_options.event_filter.end==INT_MAX);
+       optind = 0;
+
+       reset_env();
+
+       char *ls_all_short_args[] = {
+               "ev",
+               "ls",
+               "-a",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(3,ls_all_short_args));
+       assert(global_options.event_filter.start==0);
+       assert(global_options.event_filter.end==INT_MAX);
+       optind = 0;
+
+       reset_env();
+}
+
+static void args_ls_basic_test() {
+       char *ls_args[] = {
+               "ev",
+               "ls",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(2,ls_args));
+       optind = 0;
+}
+
+static void args_ls_end_date_basic_test() {
+       char *ls_end_date_args[] = {
+               "ev",
+               "ls",
+               "--end-date",
+               "2004-01-01",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(4,ls_end_date_args));
+       assert(global_options.event_filter.start==0);
+       assert(global_options.event_filter.end==1072944000);
+       optind = 0;
+
+       reset_env();
+
+       char *ls_end_date_short_args[] = {
+               "ev",
+               "ls",
+               "-e",
+               "2005-05-01",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(4,ls_end_date_short_args));
+       assert(global_options.event_filter.start==0);
+       assert(global_options.event_filter.end==1114934400);
+       optind = 0;
+
+       reset_env();
+}
+
+static void args_ls_overdue_basic_test() {
+       char *ls_overdue_args[] = {
+               "ev",
+               "ls",
+               "--overdue",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(3,ls_overdue_args));
+       assert(global_options.event_filter.start==0);
+       assert((time(NULL) - global_options.event_filter.end)<1);
+       optind = 0;
+
+       reset_env();
+
+       char *ls_overdue_short_args[] = {
+               "ev",
+               "ls",
+               "-o",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(3,ls_overdue_short_args));
+       assert(global_options.event_filter.start==0);
+       assert((time(NULL) - global_options.event_filter.end)<1);
+       optind = 0;
+
+       reset_env();
+}
+
+static void args_ls_start_date_basic_test() {
+       char *ls_start_date_args[] = {
+               "ev",
+               "ls",
+               "--start-date",
+               "2001-01-01",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(4,ls_start_date_args));
+       assert(global_options.event_filter.start==978336000);
+       assert(global_options.event_filter.end==0);
+       optind = 0;
+
+       reset_env();
+
+       char *ls_start_date_short_args[] = {
+               "ev",
+               "ls",
+               "-s",
+               "2002-01-01",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(4,ls_start_date_short_args));
+       assert(global_options.event_filter.start==1009872000);
+       assert(global_options.event_filter.end==0);
+       optind = 0;
+
+       reset_env();
+}
+
+static void args_ls_upcoming_basic_test() {
+       char *ls_upcoming_args[] = {
+               "ev",
+               "ls",
+               "--upcoming",
+               "1W",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(4,ls_upcoming_args));
+       assert((time(NULL) - global_options.event_filter.start)<1);
+       assert((global_options.event_filter.end - global_options.event_filter.start)<(60*60*24*7+1));
+       optind = 0;
+
+       reset_env();
+
+       char *ls_upcoming_short_args[] = {
+               "ev",
+               "ls",
+               "-u",
+               "1D",
+               NULL
+       };
+       assert(SUB_COMMAND_LS==args(4,ls_upcoming_short_args));
+       assert((time(NULL) - global_options.event_filter.start)<1);
+       assert((global_options.event_filter.end - global_options.event_filter.start)<(60*60*24+1));
+       optind = 0;
+
+       reset_env();
+}
+
+static void args_order_test() {
+       char *normal[] = {
+               "ev",
+               "add",
+               "--duration",
+               "1W",
+               NULL
+       };
+       assert(SUB_COMMAND_ADD==args(4,normal));
+       assert(global_options.event_options.duration==1);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_WEEK);
+       optind = 0;
+
+       reset_env();
+
+       char *swapped[] = {
+               "ev",
+               "--duration",
+               "1W",
+               "add",
+               NULL
+       };
+       assert(SUB_COMMAND_ADD==args(4,swapped));
+       assert(global_options.event_options.duration==1);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_WEEK);
+       optind = 0;
+       optind = 0;
+       reset_env();
+}
+
+static void args_postpone_basic_test() {
+       char *postpone_args[] = {
+               "ev",
+               "postpone",
+               NULL
+       };
+       assert(SUB_COMMAND_POSTPONE==args(2,postpone_args));
+       optind = 0;
+}
+
+static void args_prune_basic_test() {
+       char *prune_args[] = {
+               "ev",
+               "prune",
+               NULL
+       };
+       assert(SUB_COMMAND_PRUNE==args(2,prune_args));
+       optind = 0;
+}
+
+static void args_rm_basic_test() {
+       char *rm_args[] = {
+               "ev",
+               "rm",
+               NULL
+       };
+       assert(SUB_COMMAND_RM==args(2,rm_args));
+       optind = 0;
+}
+
+static void args_short_opts_basic_test() {
+       char *short_opts_args[] = {
+               "ev",
+               "add",
+               "-d",
+               "1m",
+               "-f",
+               "/tmp/events.test3",
+               "-r",
+               "1D",
+               NULL
+       };
+       assert(SUB_COMMAND_ADD==args(8,short_opts_args));
+       assert(global_options.event_options.duration==1);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_MINUTE);
+       assert(strcmp(global_options.file,"/tmp/events.test3")==0);
+       assert(global_options.event_options.recur==1);
+       assert(global_options.event_options.recur_period==TIME_PERIOD_DAY);
+       optind = 0;
+
+       reset_env();
+}
diff --git a/test/unit/copy.tests.c b/test/unit/copy.tests.c
new file mode 100644 (file)
index 0000000..868bda2
--- /dev/null
@@ -0,0 +1,188 @@
+#include<test_utils.h>
+
+#include<copy.h>
+
+int main();
+static void copy_basic_test();
+static void copy_correctness_test();
+static void copy_next_event_basic_test();
+
+int main() {
+       setup_env();
+
+       copy_basic_test();
+       copy_correctness_test();
+       copy_next_event_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void copy_basic_test() {
+       FILE *src, *to;
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+       assert(fclose(src)==0);
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       char tempfile[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile);
+       assert(to!=NULL);
+
+       assert(-1==copy(NULL,NULL,0));
+       assert(-1==copy(to,NULL,0));
+
+       assert(1==copy(to,src,0));
+
+       reset_env();
+}
+
+static void copy_correctness_test() {
+       FILE *src, *to;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       time_t until;
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       strcpy(buf,"2022-01-01:test\n2022-02-01:test2\n2022-03-01:test3\n");
+       assert(fwrite(buf,1,strlen(buf),src)==50);
+
+       assert(fclose(src)==0);
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       char tempfile[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile);
+       assert(to!=NULL);
+
+       until = 1642986321; /* 2022, Jan 24thish */
+       assert(copy(to,src,until)==1);
+
+       assert(fclose(to)==0);
+       to = fopen(tempfile,"r");
+       assert(to!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,to)==16);
+       strcpy(buf,"2022-01-01:test\n");
+       assert(memcmp(buf,got,16)==0);
+
+       assert(fclose(to)==0);
+       assert(remove(tempfile)==0);
+
+       rewind(src);
+
+       char tempfile2[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile2);
+       assert(to!=NULL);
+       
+       until = 1645664721; /* 2022, Feb 24thish */
+       assert(copy(to,src,until)==1);
+       
+       assert(fclose(to)==0);
+       to = fopen(tempfile2,"r");
+       assert(to!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,to)==33);
+       strcpy(buf,"2022-01-01:test\n2022-02-01:test2\n");
+       assert(memcmp(buf,got,33)==0);
+       
+       assert(fclose(to)==0);
+       assert(remove(tempfile2)==0);
+
+       rewind(src);
+
+       char tempfile3[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile3);
+       assert(to!=NULL);
+       
+       until = INT_MAX; /* END OF TIME */
+       assert(copy(to,src,until)==1);
+
+       assert(fclose(to)==0);
+       to = fopen(tempfile3,"r");
+       assert(to!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,to)==50);
+       strcpy(buf,"2022-01-01:test\n2022-02-01:test2\n2022-03-01:test3\n");
+       assert(memcmp(buf,got,50)==0);
+
+       assert(fclose(src)==0);
+       assert(fclose(to)==0);
+       assert(remove(tempfile3)==0);
+
+       reset_env();
+}
+
+static void copy_next_event_basic_test() {
+       FILE *src, *to;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+       
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+       
+       strcpy(buf,"2022-01-01:test\n2022-02-01:test2\n2022-03-01:test3\n");
+       assert(fwrite(buf,1,strlen(buf),src)==50);
+
+       assert(fclose(src)==0);
+       
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+       
+       char tempfile[] = "/tmp/XXXXXX";
+       to = file_temp(tempfile);
+       assert(to!=NULL);
+
+       assert(copy_next_event(to,src)==1);
+
+       assert(fclose(to)==0);
+       to = fopen(tempfile,"r");
+       assert(to!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,to)==16);
+       assert(strcmp(got,"2022-01-01:test\n")==0);
+
+       assert(fclose(to)==0);
+       to = fopen(tempfile,"a");
+       assert(to!=NULL);
+
+       assert(copy_next_event(to,src)==1);
+
+       assert(fclose(to)==0);
+       to = fopen(tempfile,"r");
+       assert(to!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,to)==33);
+       assert(strcmp(got,"2022-01-01:test\n2022-02-01:test2\n")==0);
+
+       assert(fclose(to)==0);
+       to = fopen(tempfile,"a");
+       assert(to!=NULL);
+
+       assert(copy_next_event(to,src)==1);
+       
+       assert(fclose(to)==0);
+       to = fopen(tempfile,"r");
+       assert(to!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,to)==50);
+       assert(memcmp(buf,got,50)==0);
+       
+       assert(fclose(to)==0);
+       assert(fclose(src)==0);
+
+       reset_env();
+}
diff --git a/test/unit/cut.tests.c b/test/unit/cut.tests.c
new file mode 100644 (file)
index 0000000..5a1569b
--- /dev/null
@@ -0,0 +1,190 @@
+#include<test_utils.h>
+
+#include<cut.h>
+
+#include INCLUDE(CUT_SRC_FILE)
+
+int main();
+static void cut_basic_test();
+static void cut_correctness_test();
+static void cut_offset_correctness_test();
+static void extract_basic_test();
+
+int main() {
+       setup_env();
+
+       cut_basic_test();
+       cut_correctness_test();
+       cut_offset_correctness_test();
+       extract_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void cut_basic_test() {
+       struct event ev;
+       FILE *src;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       strcpy(buf,"2012-01-01:test\n2012-02-01:test2\n2012-03-01:test3\n");
+       assert(fwrite(buf,1,strlen(buf),src)==50);
+
+       assert(fclose(src)==0);
+
+       assert(1==cut(&ev,0,0));
+       assert(strcmp(ev.name,"test")==0);
+       assert(mktime(&(ev.datetime))==1325404800);
+       event_free(&ev);
+       
+       assert(1==cut(&ev,0,0));
+       assert(strcmp(ev.name,"test2")==0);
+       assert(mktime(&(ev.datetime))==1328083200);
+       event_free(&ev);
+
+       assert(1==cut(&ev,0,0));
+       assert(strcmp(ev.name,"test3")==0);
+       assert(mktime(&(ev.datetime))==1330588800);
+       event_free(&ev);
+
+       reset_env();
+}
+
+static void cut_correctness_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+       FILE *src;
+       size_t index, rounds;
+       time_t start;
+       int used[12];
+
+       const unsigned int start_time = 1292392480;
+       const unsigned int chunk_size = (1323928480 - 1292392480)/12;
+
+       rounds = 0;
+       while(rounds<100) {
+               src = fopen(global_options.file,"w");
+               assert(src!=NULL);
+
+               memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+               strcpy(buf,"2011-01-01:test1\n2011-02-01:test2\n2011-03-01:test3\n2011-04-01:test4\n2011-05-01:test5\n2011-06-01:test6\n2011-07-01:test7\n2011-08-01:test8\n2011-09-01:test9\n2011-10-01:test10\n2011-11-01:test11\n2011-12-01:test12\n");
+               assert(fwrite(buf,1,strlen(buf),src)==207);
+
+               assert(fclose(src)==0);
+
+               memset(used,0,sizeof(int)*12);
+               for(size_t i=0;i<12;i++) {
+                       index = rand()%12;
+
+                       while(used[index]!=0) {
+                               index++;
+                               index %= 12;
+                       }
+                       used[index] = 1;
+
+                       start = start_time + (index*chunk_size);
+                       assert(1==cut(&ev,start,0));
+
+                       sprintf(buf,"test%lu",index+1);
+                       assert(strcmp(ev.name,buf)==0);
+
+                       event_free(&ev);
+               }
+
+               reset_env();
+
+               rounds++;
+       }
+}
+
+static void cut_offset_correctness_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+       FILE *src;
+       size_t index, rounds;
+       int used[12];
+       int expected[12];
+
+       rounds = 0;
+       while(rounds<100) {
+               src = fopen(global_options.file,"w");
+               assert(src!=NULL);
+
+               memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+               strcpy(buf,"2009-01-01:test1\n2009-02-01:test2\n2009-03-01:test3\n2009-04-01:test4\n2009-05-01:test5\n2009-06-01:test6\n2009-07-01:test7\n2009-08-01:test8\n2009-09-01:test9\n2009-10-01:test10\n2009-11-01:test11\n2009-12-01:test12\n");
+               assert(fwrite(buf,1,strlen(buf),src)==207);
+
+               assert(fclose(src)==0);
+
+               memset(used,0,sizeof(int)*12);
+               for(size_t i=0;i<12;i++) {
+                       expected[i] = 1;
+                       index = rand()%(12-i);
+
+                       assert(1==cut(&ev,0,index));
+
+                       if(strcmp(ev.name,"test1")==0) { used[0] = 1; }
+                       if(strcmp(ev.name,"test2")==0) { used[1] = 1; }
+                       if(strcmp(ev.name,"test3")==0) { used[2] = 1; }
+                       if(strcmp(ev.name,"test4")==0) { used[3] = 1; }
+                       if(strcmp(ev.name,"test5")==0) { used[4] = 1; }
+                       if(strcmp(ev.name,"test6")==0) { used[5] = 1; }
+                       if(strcmp(ev.name,"test7")==0) { used[6] = 1; }
+                       if(strcmp(ev.name,"test8")==0) { used[7] = 1; }
+                       if(strcmp(ev.name,"test9")==0) { used[8] = 1; }
+                       if(strcmp(ev.name,"test10")==0) { used[9] = 1; }
+                       if(strcmp(ev.name,"test11")==0) { used[10] = 1; }
+                       if(strcmp(ev.name,"test12")==0) { used[11] = 1; }
+
+                       event_free(&ev);
+               }
+
+               assert(memcmp(used,expected,sizeof(int)*12)==0);
+
+               reset_env();
+
+               rounds++;
+       }
+}
+
+static void extract_basic_test() {
+       FILE *src;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       strcpy(buf,"2017-01-01:test1\n2017-02-01:test2\n2017-03-01:test3\n");
+       assert(fwrite(buf,1,strlen(buf),src)==51);
+       
+       assert(fclose(src)==0);
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       assert(extract(NULL,&ev)==-1);
+
+       assert(extract(src,&ev)==1);
+
+       assert(strcmp(ev.name,"test1")==0);
+       assert(mktime(&(ev.datetime))==1483257600);
+
+       event_free(&ev);
+       
+       assert(fclose(src)==0);
+
+       reset_env();
+}
diff --git a/test/unit/dismiss.tests.c b/test/unit/dismiss.tests.c
new file mode 100644 (file)
index 0000000..08bdc14
--- /dev/null
@@ -0,0 +1,20 @@
+#include<test_utils.h>
+
+#include<rm.h>
+
+int main();
+static void dismiss_basic_test();
+
+int main() {
+       setup_env();
+
+       dismiss_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void dismiss_basic_test() {
+       assert(0);
+}
diff --git a/test/unit/event.tests.c b/test/unit/event.tests.c
new file mode 100644 (file)
index 0000000..3ea0f35
--- /dev/null
@@ -0,0 +1,380 @@
+#include<event.h>
+
+int main();
+static void event_date_compare_basic_test();
+static void event_init_basic_test();
+static void event_name_set_basic_test();
+static void event_parse_basic_test();
+static void event_place_set_basic_test();
+static void event_serialize_basic_test();
+static void event_serialize_parse_exactness_test();
+static void event_time_set_basic_test();
+
+int main() {
+       event_init_basic_test();
+
+       event_date_compare_basic_test();
+       event_name_set_basic_test();
+       event_parse_basic_test();
+       event_place_set_basic_test();
+       event_serialize_basic_test();
+       event_serialize_parse_exactness_test();
+       event_time_set_basic_test();
+
+       return EXIT_SUCCESS;
+}
+
+static void event_date_compare_basic_test() {
+       char timebuf[40];
+       struct event ev;
+       time_t timet;
+       struct tm *now, got;
+
+       event_init(&ev);
+
+       timet = time(NULL);
+       now = localtime(&timet);
+       
+       assert(strftime(timebuf,40,"%Y-%m-%d %H:%M:%S %z",now)==25);
+       assert(1==event_time_set(&ev,timebuf));
+
+       /* daylight savings time set here for some reason.
+        * resetting timet to be time set in ev...
+        */
+       memcpy(&got,&(ev.datetime),sizeof(struct tm));
+       timet = mktime(&got);
+
+       timet -= 1000;
+       assert(1==event_date_compare(&ev,timet));
+
+       timet += 2000;
+       assert(-1==event_date_compare(&ev,timet));
+
+       timet -= 1000;
+       assert(0==event_date_compare(&ev,timet));
+
+       event_free(&ev);
+}
+
+static void event_init_basic_test() {
+       struct event ev;
+       struct tm dummy;
+
+       event_init(&ev);
+
+       assert(ev.name==NULL);
+       assert(ev.place==NULL);
+
+       assert(ev.options.duration==0);
+       assert(ev.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev.options.recur==0);
+       assert(ev.options.recur_period==TIME_PERIOD_NONE);
+
+       memset(&dummy, 0, sizeof(struct tm));
+       assert(memcmp(&(ev.datetime),&dummy,sizeof(struct tm))==0);
+
+       event_free(&ev);
+}
+
+static void event_name_set_basic_test() {
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(ev.name==NULL);
+       
+       assert(event_name_set(&ev,"test name")==1);
+       assert(strcmp(ev.name,"test name")==0);
+       
+       assert(event_name_set(&ev,"test name2")==1);
+       assert(strcmp(ev.name,"test name2")==0);
+
+       event_free(&ev);
+}
+
+static void event_parse_basic_test() {
+       struct event ev1, ev2, ev3, ev4, ev5, ev6, ev7, ev8, ev9;
+
+       event_init(&ev1);
+       char ex1[] = "2022-09-01:test event:test place";
+       assert(event_parse(ex1,sizeof(ex1),&ev1)==1);
+       assert(ev1.options.duration==0);
+       assert(ev1.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev1.options.recur==0);
+       assert(ev1.options.recur_period==TIME_PERIOD_NONE);
+       assert(strcmp(ev1.name,"test event")==0);
+       assert(strcmp(ev1.place,"test place")==0);
+       assert(mktime(&(ev1.datetime))==1662019200);
+
+       event_init(&ev2);
+       char ex2[] = "2022-09-02:\"test: event2\":\"test: place2\"";
+       assert(event_parse(ex2,sizeof(ex2),&ev2)==1);
+       assert(ev2.options.duration==0);
+       assert(ev2.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev2.options.recur==0);
+       assert(ev2.options.recur_period==TIME_PERIOD_NONE);
+       assert(strcmp(ev2.name,"test: event2")==0);
+       assert(strcmp(ev2.place,"test: place2")==0);
+       assert(mktime(&(ev2.datetime))==1662105600);
+       
+       event_init(&ev3);
+       char ex3[] = "2022-09-03 09:30:00:test event3:test place3";
+       assert(event_parse(ex3,sizeof(ex3),&ev3)==1);
+       assert(ev3.options.duration==0);
+       assert(ev3.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev3.options.recur==0);
+       assert(ev3.options.recur_period==TIME_PERIOD_NONE);
+       assert(strcmp(ev3.name,"test event3")==0);
+       assert(strcmp(ev3.place,"test place3")==0);
+       assert(mktime(&(ev3.datetime))==1662226200);
+
+       event_init(&ev4);
+       char ex4[] = "2022-09-04 09:30:00 -0700:test event4:test place4";
+       assert(event_parse(ex4,sizeof(ex4),&ev4)==1);
+       assert(ev4.options.duration==0);
+       assert(ev4.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev4.options.recur==0);
+       assert(ev4.options.recur_period==TIME_PERIOD_NONE);
+       assert(strcmp(ev4.name,"test event4")==0);
+       assert(strcmp(ev4.place,"test place4")==0);
+       assert(mktime(&(ev4.datetime))==1662312600);
+       
+       event_init(&ev5);
+       char ex5[] = "[recur:1M]2022-08-04:test event5:test place5";
+       assert(event_parse(ex5,sizeof(ex5),&ev5)==1);
+       assert(ev5.options.duration==0);
+       assert(ev5.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev5.options.recur==1);
+       assert(ev5.options.recur_period==TIME_PERIOD_MONTH);
+       assert(strcmp(ev5.name,"test event5")==0);
+       assert(strcmp(ev5.place,"test place5")==0);
+       assert(mktime(&(ev5.datetime))==1659600000);
+
+       event_init(&ev6);
+       char ex6[] = "[duration:1W]2022-06-05 09:30:00 -0700:test event6:test place6";
+       assert(event_parse(ex6,sizeof(ex6),&ev6)==1);
+       assert(ev6.options.duration==1);
+       assert(ev6.options.duration_period==TIME_PERIOD_WEEK);
+       assert(ev6.options.recur==0);
+       assert(ev6.options.recur_period==TIME_PERIOD_NONE);
+       assert(strcmp(ev6.name,"test event6")==0);
+       assert(strcmp(ev6.place,"test place6")==0);
+       assert(mktime(&(ev6.datetime))==1654450200);
+
+       event_init(&ev7);
+       char ex7[] = "[recur:1W,duration:30m]2022-10-01 09:30:00 -0700:test event7:test place7";
+       assert(event_parse(ex7,sizeof(ex7),&ev7)==1);
+       assert(ev7.options.duration==30);
+       assert(ev7.options.duration_period==TIME_PERIOD_MINUTE);
+       assert(ev7.options.recur==1);
+       assert(ev7.options.recur_period==TIME_PERIOD_WEEK);
+       assert(strcmp(ev7.name,"test event7")==0);
+       assert(strcmp(ev7.place,"test place7")==0);
+       assert(mktime(&(ev7.datetime))==1664645400);
+
+       event_init(&ev8);
+       char ex8[] = "[duration:30m,recur:1W]2022-10-01 09:30:00 -0700:test event8:test place8";
+       assert(event_parse(ex8,sizeof(ex8),&ev8)==1);
+       assert(ev8.options.duration==30);
+       assert(ev8.options.duration_period==TIME_PERIOD_MINUTE);
+       assert(ev8.options.recur==1);
+       assert(ev8.options.recur_period==TIME_PERIOD_WEEK);
+       assert(strcmp(ev8.name,"test event8")==0);
+       assert(strcmp(ev8.place,"test place8")==0);
+       assert(mktime(&(ev8.datetime))==1664645400);
+
+       event_init(&ev9);
+       char ex9[] = "2022-08-01:test";
+       assert(event_parse(ex9,sizeof(ex9),&ev9)==1);
+       assert(ev9.options.duration==0);
+       assert(ev9.options.duration_period==TIME_PERIOD_NONE);
+       assert(ev9.options.recur==0);
+       assert(ev9.options.recur_period==TIME_PERIOD_NONE);
+       assert(strcmp(ev9.name,"test")==0);
+       assert(NULL==ev9.place);
+       assert(mktime(&(ev9.datetime))==1659340800);
+
+       event_free(&ev1);
+       event_free(&ev2);
+       event_free(&ev3);
+       event_free(&ev4);
+       event_free(&ev5);
+       event_free(&ev6);
+       event_free(&ev7);
+       event_free(&ev8);
+       event_free(&ev9);
+}
+
+static void event_place_set_basic_test() {
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(ev.place==NULL);
+       
+       assert(event_place_set(&ev,"test place")==1);
+       assert(strcmp(ev.place,"test place")==0);
+       
+       assert(event_place_set(&ev,"test name2")==1);
+       assert(strcmp(ev.place,"test name2")==0);
+
+       event_free(&ev);
+}
+
+static void event_serialize_basic_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(1==event_time_set(&ev,"2022-07-01 08:01:15 -0100"));
+       assert(-1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+
+       assert(1==event_name_set(&ev,"event"));
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+       assert(strcmp(buf,"2022-07-01 08:01:15 -0100:event\n")==0);
+       
+       event_init(&ev);
+       assert(1==event_time_set(&ev,"2022-07-01 08:01:15 -0100"));
+       assert(1==event_name_set(&ev,"event"));
+       assert(1==event_place_set(&ev,"place"));
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+       assert(strcmp(buf,"2022-07-01 08:01:15 -0100:event:place\n")==0);
+       
+       event_init(&ev);
+       assert(1==event_time_set(&ev,"2022-07-01 08:01:15 -0100"));
+       assert(1==event_name_set(&ev,"event: what?"));
+       assert(1==event_place_set(&ev,"place: here"));
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+       assert(strcmp(buf,"2022-07-01 08:01:15 -0100:\"event: what?\":\"place: here\"\n")==0);
+
+       event_init(&ev);
+       assert(1==event_time_set(&ev,"2022-06-21"));
+       assert(1==event_name_set(&ev,"event"));
+       assert(1==event_place_set(&ev,"place"));
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+       assert(strcmp(buf,"2022-06-21:event:place\n")==0);
+
+       event_init(&ev);
+       assert(1==event_time_set(&ev,"2022-06-21"));
+       assert(1==event_name_set(&ev,"event"));
+       assert(1==event_place_set(&ev,"place"));
+       ev.options.duration = 30;
+       ev.options.duration_period = TIME_PERIOD_DAY;
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+       assert(strcmp(buf,"[duration:30D]2022-06-21:event:place\n")==0);
+       
+       event_init(&ev);
+       assert(1==event_time_set(&ev,"2022-06-21"));
+       assert(1==event_name_set(&ev,"event"));
+       assert(1==event_place_set(&ev,"place"));
+       ev.options.duration = 30;
+       ev.options.duration_period = TIME_PERIOD_DAY;
+       ev.options.recur = 1;
+       ev.options.recur_period = TIME_PERIOD_YEAR;
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+       assert(strcmp(buf,"[duration:30D,recur:1Y]2022-06-21:event:place\n")==0);
+}
+
+static void event_serialize_parse_exactness_test() {
+       struct event ev;
+
+       const char expected[] = "[duration:30D,recur:1Y]2000-01-01:event:test palaaec\n";
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       event_init(&ev);
+
+       assert(53==strlen(expected));
+       strcpy(buf,expected);
+
+       assert(1==event_parse(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       assert(1==event_serialize(buf,EVENT_SERIALIZE_MAX_LENGTH,&ev));
+
+       assert(memcmp(expected,buf,53)==0);
+}
+
+static void event_time_set_basic_test() {
+       struct event ev;
+
+       event_init(&ev);
+
+       assert(-1==event_time_set(&ev,"nota  date"));
+
+       assert(-1==event_time_set(&ev,"99"));
+       assert(-1==event_time_set(&ev,"99-02"));
+
+       assert(1==event_time_set(&ev,"1999-02-28"));
+       assert(ev.datetime.tm_sec==0);
+       assert(ev.datetime.tm_min==0);
+       assert(ev.datetime.tm_hour==0);
+       assert(ev.datetime.tm_mday==28);
+       assert(ev.datetime.tm_mon==1);
+       assert(ev.datetime.tm_year==99);
+       assert(ev.datetime.tm_wday==0);
+       assert(ev.datetime.tm_yday==58);
+       assert(ev.datetime.tm_isdst==0);
+       assert(ev.datetime.__tm_gmtoff==0);
+
+       assert(1==event_time_set(&ev,"1997-01-28 not a time"));
+       assert(ev.datetime.tm_sec==0);
+       assert(ev.datetime.tm_min==0);
+       assert(ev.datetime.tm_hour==0);
+       assert(ev.datetime.tm_mday==28);
+       assert(ev.datetime.tm_mon==0);
+       assert(ev.datetime.tm_year==97);
+       assert(ev.datetime.tm_wday==2);
+       assert(ev.datetime.tm_yday==27);
+       assert(ev.datetime.tm_isdst==0);
+       assert(ev.datetime.__tm_gmtoff==0);
+
+       assert(1==event_time_set(&ev,"2002-05-24 121212"));
+       assert(ev.datetime.tm_sec==0);
+       assert(ev.datetime.tm_min==0);
+       assert(ev.datetime.tm_hour==12);
+       assert(ev.datetime.tm_mday==24);
+       assert(ev.datetime.tm_mon==4);
+       assert(ev.datetime.tm_year==102);
+       assert(ev.datetime.tm_wday==5);
+       assert(ev.datetime.tm_yday==143);
+       assert(ev.datetime.tm_isdst==0);
+       assert(ev.datetime.__tm_gmtoff==0);
+       
+       assert(1==event_time_set(&ev,"2012-12-22 12:1212"));
+       assert(ev.datetime.tm_sec==0);
+       assert(ev.datetime.tm_min==12);
+       assert(ev.datetime.tm_hour==12);
+       assert(ev.datetime.tm_mday==22);
+       assert(ev.datetime.tm_mon==11);
+       assert(ev.datetime.tm_year==112);
+       assert(ev.datetime.tm_wday==6);
+       assert(ev.datetime.tm_yday==356);
+       assert(ev.datetime.tm_isdst==0);
+       assert(ev.datetime.__tm_gmtoff==0);
+       
+       assert(1==event_time_set(&ev,"2022-01-24 09:19:55"));
+       assert(ev.datetime.tm_sec==55);
+       assert(ev.datetime.tm_min==19);
+       assert(ev.datetime.tm_hour==9);
+       assert(ev.datetime.tm_mday==24);
+       assert(ev.datetime.tm_mon==0);
+       assert(ev.datetime.tm_year==122);
+       assert(ev.datetime.tm_wday==1);
+       assert(ev.datetime.tm_yday==23);
+       assert(ev.datetime.tm_isdst==0);
+       assert(ev.datetime.__tm_gmtoff==0);
+       
+       assert(1==event_time_set(&ev,"2009-01-03 13:25:25 -0800"));
+       assert(ev.datetime.tm_sec==25);
+       assert(ev.datetime.tm_min==25);
+       assert(ev.datetime.tm_hour==13);
+       assert(ev.datetime.tm_mday==3);
+       assert(ev.datetime.tm_mon==0);
+       assert(ev.datetime.tm_year==109);
+       assert(ev.datetime.tm_wday==6);
+       assert(ev.datetime.tm_yday==2);
+       assert(ev.datetime.tm_isdst==0);
+       assert(ev.datetime.__tm_gmtoff==-28800);
+}
diff --git a/test/unit/file.tests.c b/test/unit/file.tests.c
new file mode 100644 (file)
index 0000000..aafb687
--- /dev/null
@@ -0,0 +1,198 @@
+#include<test_utils.h>
+
+#include<file.h>
+
+int main();
+static void file_line_next_basic_test();
+static void file_move_basic_test();
+static void file_open_basic_test();
+static void file_pipe_basic_test();
+static void file_temp_basic_test();
+
+int main() {
+       setup_env();
+
+       file_temp_basic_test();
+
+       file_line_next_basic_test();
+       file_move_basic_test();
+       file_pipe_basic_test();
+       
+       /* must be last because of freopen of stdin */
+       file_open_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void file_open_basic_test() {
+       FILE *fp;
+
+       char tempfile[] = "/tmp/XXXXXX";
+       fp = file_temp(tempfile);
+       assert(fp!=NULL);
+       fclose(fp);
+
+       stdin = freopen(tempfile,"r+",stdin);
+       assert(stdin!=NULL);
+
+       assert(fputc('n',stdin)=='n');
+       rewind(stdin);
+       assert(NULL==file_open());
+
+       rewind(stdin);
+       assert(fputc('l',stdin)=='l');
+       rewind(stdin);
+       assert(NULL==file_open());
+       
+       rewind(stdin);
+       assert(fputc('y',stdin)=='y');
+       rewind(stdin);
+       assert(NULL==file_open());
+       
+       rewind(stdin);
+       assert(fputc('Y',stdin)=='Y');
+       rewind(stdin);
+       fp = file_open();
+       assert(fp!=NULL);
+
+       fclose(fp);
+
+       reset_env();
+}
+
+static void file_line_next_basic_test() {
+       FILE *fp;
+       char buf[100];
+       size_t i;
+
+       char tempfile[] = "/tmp/XXXXXX";
+
+       fp = file_temp(tempfile);
+       assert(fp!=NULL);
+
+       strcpy(buf,"this is a test\nline number 2\nline 3????\\n what?");
+       i = strlen(buf);
+
+       assert(fwrite(buf,1,i,fp)==i);
+
+       assert(fclose(fp)==0);
+
+       fp = fopen(tempfile,"r");
+       assert(fp!=NULL);
+
+       memset(buf, 0, 100);
+
+       assert(file_line_next(buf,100,fp)==15);
+       assert(strcmp(buf,"this is a test\n")==0);
+       
+       assert(file_line_next(buf,100,fp)==14);
+       assert(strcmp(buf,"line number 2\n")==0);
+       
+       assert(file_line_next(buf,100,fp)==18);
+       assert(strcmp(buf,"line 3????\\n what?")==0);
+
+       assert(fclose(fp)==0);
+
+       assert(remove(tempfile)==0);
+}
+
+static void file_move_basic_test() {
+       FILE *fp;
+       char buf[100];
+       char got[100];
+       size_t i;
+
+       char template[] = "/tmp/XXXXXX";
+
+       fp = file_temp(template);
+       assert(fp!=NULL);
+
+       memset(buf,0,100);
+
+       char test_string[] = "asldkfjasoildfjoisajdfoiasjdfiojasoidfjoisadjfioasdjfoajsdoifja";
+
+       strcpy(buf,test_string);
+       i = strlen(buf);
+       assert(i==fwrite(buf,1,i,fp));
+
+       assert(fclose(fp)==0);
+
+       assert(1==file_move(template));
+
+       assert(fopen(template,"r")==NULL);
+
+       fp = fopen(global_options.file,"r");
+       assert(fp!=NULL);
+
+       memset(got,0,100);
+       assert(i==fread(got,1,100,fp));
+
+       assert(memcmp(buf,got,i)==0);
+
+       assert(fclose(fp)==0);
+
+       reset_env();
+}
+
+static void file_pipe_basic_test() {
+       FILE *fp1, *fp2;
+       char buf[100];
+       char got[100];
+       size_t i;
+
+       char file1[] = "/tmp/XXXXXX";
+       char file2[] = "/tmp/XXXXXX";
+
+       fp1 = file_temp(file1);
+       assert(fp1!=NULL);
+       
+       fp2 = file_temp(file2);
+       assert(fp2!=NULL);
+
+       memset(buf, 0, 100);
+       memset(got, 0, 100);
+
+       strcpy(buf,"alksdjflaksjfdlkasjdfkljasdlkfjaskldfjlkasdjfklasjdfkljaslkdfjasd");
+
+       i = strlen(buf);
+       assert(i==fwrite(buf,1,i,fp2));
+
+       assert(fclose(fp2)==0);
+       fp2 = fopen(file2,"r");
+       assert(fp2!=NULL);
+
+       assert(fseek(fp2,20,SEEK_SET)==0);
+
+       assert(file_pipe(fp1,fp2)==1);
+       
+       assert(fclose(fp1)==0);
+       fp1 = fopen(file1,"r");
+       assert(fp1!=NULL);
+
+       assert((i-20)==fread(got,1,i,fp1));
+
+       assert(feof(fp1)!=0);
+
+       assert(memcmp(&(buf[20]),got,(i-20))==0);
+
+       assert(fclose(fp1)==0);
+       assert(fclose(fp2)==0);
+
+       assert(remove(file1)==0);
+       assert(remove(file2)==0);
+}
+
+static void file_temp_basic_test() {
+       FILE *fp;
+
+       char template[] = "/tmp/XXXXXX";
+
+       fp = file_temp(template);
+       assert(fp!=NULL);
+
+       assert(fclose(fp)==0);
+
+       assert(remove(template)==0);
+}
diff --git a/test/unit/ls.tests.c b/test/unit/ls.tests.c
new file mode 100644 (file)
index 0000000..ec2b828
--- /dev/null
@@ -0,0 +1,9 @@
+#include<test_utils.h>
+
+#include<ls.h>
+
+int main();
+
+int main() {
+       return EXIT_SUCCESS;
+}
diff --git a/test/unit/opt.tests.c b/test/unit/opt.tests.c
new file mode 100644 (file)
index 0000000..24e8110
--- /dev/null
@@ -0,0 +1,113 @@
+#include<test_utils.h>
+
+#include<opt.h>
+
+int main();
+static void opt_duration_set_basic_test();
+static void opt_event_filter_date_set_basic_test();
+static void opt_event_filter_range_set_basic_test();
+static void opt_event_filter_set_basic_test();
+static void opt_recur_set_basic_test();
+
+int main() {
+       setup_env();
+
+       opt_duration_set_basic_test();
+       opt_event_filter_date_set_basic_test();
+       opt_event_filter_range_set_basic_test();
+       opt_event_filter_set_basic_test();
+       opt_recur_set_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void opt_duration_set_basic_test() {
+       assert(-1==opt_duration_set(NULL));
+
+       assert(-1==opt_duration_set("notdsoifjaoisdjfoiasdj"));
+       assert(global_options.event_options.duration==0);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_NONE);
+
+       assert(-1==opt_duration_set("1u"));
+       assert(global_options.event_options.duration==0);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_NONE);
+
+       assert(1==opt_duration_set("1Y"));
+       assert(global_options.event_options.duration==1);
+       assert(global_options.event_options.duration_period==TIME_PERIOD_YEAR);
+
+       reset_env();
+}
+
+static void opt_event_filter_date_set_basic_test() {
+       assert(-1==opt_event_filter_date_set(&(global_options.event_filter.start),"notadate"));
+       
+       assert(1==opt_event_filter_date_set(&(global_options.event_filter.start),"2009-01-03"));
+       assert(global_options.event_filter.start==1230969600);
+
+       assert(1==opt_event_filter_date_set(&(global_options.event_filter.end),"2022-08-22"));
+       assert(global_options.event_filter.end==1661155200);
+
+       reset_env();
+}
+
+static void opt_event_filter_range_set_basic_test() {
+       assert(-1==opt_event_filter_range_set("not expected","1W"));
+
+       assert(-1==opt_event_filter_range_set("upcoming","notvalidformat"));
+
+       assert(1==opt_event_filter_range_set("upcoming","1s"));
+       assert((global_options.event_filter.end - global_options.event_filter.start)<2);
+
+       assert(1==opt_event_filter_range_set("upcoming","1m"));
+       assert((global_options.event_filter.end - global_options.event_filter.start)<(60+1));
+       
+       assert(1==opt_event_filter_range_set("upcoming","1h"));
+       assert((global_options.event_filter.end - global_options.event_filter.start)<(60*60+1));
+
+       assert(1==opt_event_filter_range_set("upcoming","1D"));
+       assert((global_options.event_filter.end - global_options.event_filter.start)<(60*60*24+1));
+
+       assert(1==opt_event_filter_range_set("upcoming","1W"));
+       assert((global_options.event_filter.end - global_options.event_filter.start)<(60*60*24*7+1));
+
+       /* month and year and left out since they're not consistent */
+       assert(1==opt_event_filter_range_set("upcoming","1M"));
+       assert(1==opt_event_filter_range_set("upcoming","1Y"));
+
+       reset_env();
+}
+
+static void opt_event_filter_set_basic_test() {
+       assert(-1==opt_event_filter_set("not expected"));
+
+       assert(1==opt_event_filter_set("all"));
+       assert(global_options.event_filter.start==0);
+       assert(global_options.event_filter.end==INT_MAX);
+
+       assert(1==opt_event_filter_set("overdue"));
+       assert(global_options.event_filter.start==0);
+       assert((time(NULL) - global_options.event_filter.end)<1);
+
+       reset_env();
+}
+
+static void opt_recur_set_basic_test() {
+       assert(-1==opt_recur_set(NULL));
+
+       assert(-1==opt_recur_set("notdsoifjaoisdjfoiasdj"));
+       assert(global_options.event_options.recur==0);
+       assert(global_options.event_options.recur_period==TIME_PERIOD_NONE);
+
+       assert(-1==opt_recur_set("1u"));
+       assert(global_options.event_options.recur==0);
+       assert(global_options.event_options.recur_period==TIME_PERIOD_NONE);
+
+       assert(1==opt_recur_set("1Y"));
+       assert(global_options.event_options.recur==1);
+       assert(global_options.event_options.recur_period==TIME_PERIOD_YEAR);
+
+       reset_env();
+}
diff --git a/test/unit/postpone.tests.c b/test/unit/postpone.tests.c
new file mode 100644 (file)
index 0000000..f54d95e
--- /dev/null
@@ -0,0 +1,185 @@
+#include<test_utils.h>
+
+#include<postpone.h>
+
+#include INCLUDE(POSTPONE_SRC_FILE)
+
+int optind;
+
+int main();
+static void handle_args_basic_test();
+static void postpone_basic_test();
+static void postpone_event_basic_test();
+
+int main() {
+       setup_env();
+
+       handle_args_basic_test();
+       postpone_event_basic_test();
+
+       postpone_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void handle_args_basic_test() {
+       unsigned long int offset;
+       time_t start_time;
+
+       char *args[] = {
+               "ev",
+               "postpone",
+               "0",
+               NULL
+       };
+
+       optind = 1;
+       assert(handle_args(1,args,&offset,&start_time)==-1);
+
+       assert(handle_args(2,args,&offset,&start_time)==-1);
+
+       assert(handle_args(3,args,&offset,&start_time)==1);
+       assert(offset==0);
+       assert(start_time==0);
+
+       char *args_with_date[] = {
+               "ev",
+               "postpone",
+               "2020-03-25",
+               "1",
+               NULL
+       };
+
+       assert(handle_args(4,args_with_date,&offset,&start_time)==1);
+       assert(offset==1);
+       assert(start_time==1585123200);
+
+       char *args_with_date_and_postpone[] = {
+               "ev",
+               "postpone",
+               "2020-03-25",
+               "2",
+               "2W",
+               NULL
+       };
+
+       assert(handle_args(5,args_with_date_and_postpone,&offset,&start_time)==1);
+       assert(offset==2);
+       assert(start_time==1585123200);
+
+       assert(strcmp(postpone_offset_string,"2W")==0);
+
+       free(postpone_offset_string);
+       postpone_offset_string = NULL;
+}
+
+static void postpone_basic_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       FILE *src;
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       strcpy(buf,"2022-08-01:test\n");
+       assert(fwrite(buf,1,strlen(buf),src)==16);
+
+       assert(fclose(src)==0);
+
+       char *args[] = {
+               "ev",
+               "postpone",
+               "0",
+               NULL
+       };
+
+       assert(EXIT_SUCCESS==postpone(3,args));
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==16);
+       assert(memcmp(got,"2022-08-02:test\n",16)==0);
+
+       assert(fclose(src)==0);
+
+       reset_env();
+}
+
+static void postpone_event_basic_test() {
+       struct event ev;
+       struct tm expected;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+
+       event_init(&ev);
+       
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       assert(event_name_set(&ev,"test")==1);
+       assert(event_time_set(&ev,"2020-01-01")==1);
+
+       postpone_offset_string = NULL;
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1577952000);
+       assert(strftime(buf,EVENT_SERIALIZE_MAX_LENGTH,"%Y-%m-%d",&(ev.datetime))==10);
+       assert(strcmp(buf,"2020-01-02")==0);
+
+       postpone_offset_string = strdup("1W");
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1578556800);
+       assert(strftime(buf,EVENT_SERIALIZE_MAX_LENGTH,"%Y-%m-%d",&(ev.datetime))==10);
+       assert(strcmp(buf,"2020-01-09")==0);
+
+       free(postpone_offset_string);
+       postpone_offset_string = strdup("1M");
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1581235200);
+       assert(strftime(buf,EVENT_SERIALIZE_MAX_LENGTH,"%Y-%m-%d",&(ev.datetime))==10);
+       assert(strcmp(buf,"2020-02-09")==0);
+
+       free(postpone_offset_string);
+       postpone_offset_string = strdup("1Y");
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1612857600);
+       assert(strftime(buf,EVENT_SERIALIZE_MAX_LENGTH,"%Y-%m-%d",&(ev.datetime))==10);
+       assert(strcmp(buf,"2021-02-09")==0);
+
+       free(postpone_offset_string);
+       postpone_offset_string = strdup("1h");
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1612861200);
+
+       free(postpone_offset_string);
+       postpone_offset_string = strdup("1m");
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1612861260);
+
+       free(postpone_offset_string);
+       postpone_offset_string = strdup("1s");
+       assert(postpone_event(&ev)==1);
+
+       memcpy(&expected,&(ev.datetime),sizeof(struct tm));
+       assert(mktime(&expected)==1612861261);
+
+       free(postpone_offset_string);
+       postpone_offset_string = NULL;
+
+       event_free(&ev);
+}
diff --git a/test/unit/prune.tests.c b/test/unit/prune.tests.c
new file mode 100644 (file)
index 0000000..9d91c58
--- /dev/null
@@ -0,0 +1,71 @@
+#include<test_utils.h>
+
+#include<prune.h>
+
+int main();
+static void prune_basic_test();
+
+int main() {
+       setup_env();
+
+       prune_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void prune_basic_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       char timebuf[40];       
+       struct tm curr, *now;
+       time_t now_timet;
+       FILE *src;
+       
+       now_timet = time(NULL);
+       now = localtime(&now_timet);
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(timebuf,0,40);
+
+       memcpy(&curr,now,sizeof(struct tm));
+       curr.tm_year -= 1;
+
+       assert(strftime(timebuf,20,"%Y-%m-%d",&curr)==10);
+       for(size_t i=0;i<10;i++) {
+               memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+               snprintf(buf,EVENT_SERIALIZE_MAX_LENGTH,"%s:test%lu\n",timebuf,i);
+               
+               assert(fwrite(buf,1,strlen(buf),src)==17);
+       }
+
+       curr.tm_year += 2;
+       assert(strftime(timebuf,20,"%Y-%m-%d",&curr)==10);
+       for(size_t i=0;i<10;i++) {
+               memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+               snprintf(buf,EVENT_SERIALIZE_MAX_LENGTH,"%s:test%lu\n",timebuf,i);
+               
+               assert(fwrite(buf,1,strlen(buf),src)==17);
+       }
+
+       assert(fclose(src)==0);
+
+       assert(prune()==EXIT_SUCCESS);
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==170);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       sprintf(buf,"%s:test0\n%s:test1\n%s:test2\n%s:test3\n%s:test4\n%s:test5\n%s:test6\n%s:test7\n%s:test8\n%s:test9\n",timebuf,timebuf,timebuf,timebuf,timebuf,timebuf,timebuf,timebuf,timebuf,timebuf);
+       assert(memcmp(got,buf,170)==0);
+
+       assert(fclose(src)==0);
+
+       reset_env();
+}
diff --git a/test/unit/rm.tests.c b/test/unit/rm.tests.c
new file mode 100644 (file)
index 0000000..1ceae1b
--- /dev/null
@@ -0,0 +1,169 @@
+#include<test_utils.h>
+
+#include<rm.h>
+
+#include INCLUDE(RM_SRC_FILE)
+
+int main();
+static void check_need_readd_basic_test();
+static void handle_args_basic_test();
+static void rm_basic_test();
+static void rm_dismiss_basic_test();
+
+int main() {
+       setup_env();
+
+       handle_args_basic_test();
+
+       rm_basic_test();
+       check_need_readd_basic_test();
+       rm_dismiss_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void check_need_readd_basic_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       struct event ev;
+       FILE *src;
+
+       event_init(&ev);
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       assert(fclose(src)==0);
+
+       assert(event_name_set(&ev,"test")==1);
+       assert(event_time_set(&ev,"2000-01-01")==1);
+
+       assert(1==check_need_readd(&ev));
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+       
+       assert(fread(buf,1,EVENT_SERIALIZE_MAX_LENGTH,src)==0);
+       assert(feof(src)!=0);
+       assert(fclose(src)==0);
+
+       ev.options.recur = 1;
+       ev.options.recur_period = TIME_PERIOD_YEAR;
+       
+       assert(1==check_need_readd(&ev));
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       assert(fread(buf,1,EVENT_SERIALIZE_MAX_LENGTH,src)==26);
+       assert(memcmp(buf,"[recur:1Y]2001-01-01:test\n",26)==0);
+
+       assert(fclose(src)==0);
+
+       reset_env();
+}
+
+static void handle_args_basic_test() {
+       unsigned long int offset;
+       time_t start_time;
+
+       char *args[] = {
+               "ev",
+               "rm",
+               "1",
+               NULL
+       };
+
+       assert(-1==handle_args(2,args,&offset,&start_time));
+       assert(1==handle_args(3,args,&offset,&start_time));
+       assert(offset==1);
+       assert(start_time==0);
+
+       char *args_with_date[] = {
+               "ev",
+               "rm",
+               "2022-09-01",
+               "2",
+               NULL
+       };
+       
+       assert(1==handle_args(4,args_with_date,&offset,&start_time));
+       assert(offset==2);
+       assert(start_time==1662019200);
+}
+
+static void rm_basic_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       FILE *src;
+
+       char *args[] = {
+               "ev",
+               "rm",
+               "0",
+               NULL
+       };
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       strcpy(buf,"2005-06-01:test1\n2006-07-02:test2\n2008-07-09:test3\n");
+       assert(fwrite(buf,1,strlen(buf),src)==51);
+       assert(fclose(src)==0);
+
+       assert(EXIT_FAILURE==rm(2,args,0));
+
+       assert(EXIT_SUCCESS==rm(3,args,0));
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==34);
+       assert(memcmp(got,"2006-07-02:test2\n2008-07-09:test3\n",34)==0);
+
+       assert(fclose(src)==0);
+
+       reset_env();
+}
+
+static void rm_dismiss_basic_test() {
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+       FILE *src;
+
+       char *args[] = {
+               "ev",
+               "dismiss",
+               "0",
+               NULL
+       };
+       
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+       
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+       
+       strcpy(buf,"1984-05-10:test1\n1988-11-12:test2\n1992-02-15:test3\n");
+       assert(fwrite(buf,1,strlen(buf),src)==51);
+       assert(fclose(src)==0);
+       
+       assert(EXIT_SUCCESS==rm(3,args,0));
+       
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+       
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==34);
+       assert(memcmp(got,"1988-11-12:test2\n1992-02-15:test3\n",34)==0);
+       
+       assert(fclose(src)==0);
+
+       reset_env();
+}
diff --git a/test/unit/seek.tests.c b/test/unit/seek.tests.c
new file mode 100644 (file)
index 0000000..9f5f610
--- /dev/null
@@ -0,0 +1,68 @@
+#include<test_utils.h>
+
+#include<seek.h>
+
+int main();
+static void seek_basic_test();
+
+int main() {
+       setup_env();
+
+       seek_basic_test();
+
+       clean_env();
+
+       return EXIT_SUCCESS;
+}
+
+static void seek_basic_test() {
+       FILE *src;
+       char buf[EVENT_SERIALIZE_MAX_LENGTH];
+       char got[EVENT_SERIALIZE_MAX_LENGTH];
+
+       src = fopen(global_options.file,"w");
+       assert(src!=NULL);
+
+       memset(buf,0,EVENT_SERIALIZE_MAX_LENGTH);
+       memset(got,0,EVENT_SERIALIZE_MAX_LENGTH);
+
+       strcpy(buf,"2008-02-15:test1\n2008-05-01:test2\n2008-10-01:test3\n");
+
+       assert(fwrite(buf,1,strlen(buf),src)==51);
+       assert(fclose(src)==0);
+
+       src = fopen(global_options.file,"r");
+       assert(src!=NULL);
+
+       assert(-1==seek(NULL,0));
+       
+       assert(1==seek(src,0));
+
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==51);
+       assert(strcmp(got,"2008-02-15:test1\n2008-05-01:test2\n2008-10-01:test3\n")==0);
+
+       rewind(src);
+
+       assert(1==seek(src,1199201413));
+       
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==51);
+       assert(memcmp(got,"2008-02-15:test1\n2008-05-01:test2\n2008-10-01:test3\n",51)==0);
+
+       rewind(src);
+
+       assert(1==seek(src,1204385413));
+       
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==34);
+       assert(memcmp(got,"2008-05-01:test2\n2008-10-01:test3\n",34)==0);
+
+       rewind(src);
+       
+       assert(1==seek(src,1214926213));
+       
+       assert(fread(got,1,EVENT_SERIALIZE_MAX_LENGTH,src)==17);
+       assert(memcmp(got,"2008-10-01:test3\n",17)==0);
+
+       assert(fclose(src)==0);
+
+       reset_env();
+}
diff --git a/test/unit/test_macros.h b/test/unit/test_macros.h
new file mode 100644 (file)
index 0000000..d3c0bee
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __TEST_MACROS_H_
+#define __TEST_MACROS_H_
+
+#define __include(x) #x
+#define _include(x) __include(x)
+#define INCLUDE(x) _include(x)
+
+#endif
diff --git a/test/unit/test_utils.c b/test/unit/test_utils.c
new file mode 100644 (file)
index 0000000..883e906
--- /dev/null
@@ -0,0 +1,31 @@
+#include<test_utils.h>
+
+struct options global_options;
+
+static const char default_file[] = "/tmp/events.test";
+
+void clean_env() {
+       reset_env();
+
+       free(global_options.file);
+}
+
+void reset_env() {
+       if(access(default_file, F_OK)==0) {
+               assert(remove(default_file)==0);
+       }
+
+       if(global_options.file!=NULL) { free(global_options.file); }
+
+       opt_global_init();
+
+       global_options.file = strdup(default_file);
+       assert(global_options.file!=NULL);
+}
+
+void setup_env() {
+       srand(time(NULL));
+
+       global_options.file = strdup(default_file);
+       assert(global_options.file!=NULL);
+}
diff --git a/test/unit/test_utils.h b/test/unit/test_utils.h
new file mode 100644 (file)
index 0000000..c15d177
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __TEST_UTILS_H_
+#define __TEST_UTILS_H_
+
+#include<unistd.h>
+
+#include<test_macros.h>
+
+#include<opt.h>
+
+void clean_env();
+void reset_env();
+void setup_env();
+
+#endif