diff --git a/.gitignore b/.gitignore
index c61760acc..e17fbd40d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-Makefile.in
aclocal.m4
autom4te.cache
auto.h*
@@ -11,3 +10,4 @@ stamp-h1
Makefile
configure
config.log
+www.xls
diff --git a/AUTHORS b/AUTHORS
index 027de8b31..bab7cef3e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -5,6 +5,9 @@ Contributing Authors:
Damian Glenny
Andy Lester
H. İbrahim Güngör
+ Stefan Dorn
+ Michael Greb
+ Benjamin Tegarden
With thanks to:
Eugene Kramer
@@ -18,4 +21,8 @@ With thanks to:
Vincent Fleuranceau
T. Charles Yun
ArchiMark
+ Carlos Yoder
+ Russell Friesenhahn
+ Paolo Marsi
+ Eric Farris
diff --git a/ChangeLog b/ChangeLog
index 61c2fde84..794dc591a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,51 @@
------ current release ---------------------------
-1.4.3 (11/1/2008)
+1.5.0 (3/15/2009)
+ + Removed deprecated TUTORIAL file.
+ + Removed "showage" configuration variable.
+ + "task stop" can now remove the start time from a started task.
+ + "task ghistory" now displays a differently aligned graph, allowing
+ easier comparison by month of tasks added versus completed and deleted.
+ + "task version" command now reports unrecognized configuration variables,
+ which may be spelling mistakes or deprecated variables.
+ + "configure --enable-debug" now supported to suppress compiler optimization
+ to allow debugging.
+ + Allow lower case priorities, and automatically upper case them.
+ + Added support for "due" configuration variable which defines the number
+ of days in the future when a task is considered due.
+ + Added support for custom reports, comprised of a set of column names and
+ sort order, with optional filtering in the configuration file. This
+ means user-defined reports can be written, and the reports currently
+ in the configuration file can be renamed. Several of task's built in
+ reports have been converted to user-defined reports.
+ + New online documentation for custom reports.
+ + New algorithm for determining when the "nag" message is displayed.
+ + Fixed bug where task hangs with a certain combination of recurring tasks
+ and shadow files.
+ + Fixed bug with the task sort algorithm, which led to an unstable sequence
+ when there were only a handful of tasks.
+ + Performance enhanced by eliminating unnecessary sorting.
+ + Task now has a large (and growing) test suite and bug regression tests
+ to help ensure higher quality releases.
+ + Fixed bug that caused performance hit during table rendering.
+ + Fixed bug that concatenated a modified description without spaces.
+ + Added new column 'recur' that displays the recurrence period of any
+ recurring tasks. This column can be added to any custom report.
+ + Added support for "color.recurring" configuration variable which
+ specifies the color of recurring tasks.
+ + Added support for "locking" configuration variable that controls whether
+ file locking is used.
+ + Task export feature now includes recurrence information, removes nested
+ quotes, and limits output to pending tasks.
+ + Task no longer includes deleted tasks in the summary report (thanks to
+ Benjamin Tegarden).
+ + Fixed bug that prevented the summary report from properly reporting
+ recently completed tasks.
+
+------ old releases ------------------------------
+
+1.4.3 (11/1/2008) 8639e9260646c8c9224e0fc47e5d2443b46eecfc
+ Fixed misleading task count at bottom on "info" report.
+ Added support for a shadow file that contains a plain text task report,
with the "shadow.file" and "shadow.command" configuration variables
@@ -10,13 +54,12 @@
+ Task now displays a message whenever a shadow file is updated, if the
"shadow.notify" configuration variable is set "on"
+ Bug: adding a task with a \n, \r or \f in it now fails properly
- + Removed "task usage" command.
+ + Removed "usage" command, and support for "command.logging" configuration
+ variable.
+ Added documentation for Shadow files.
+ Added documentation for task filters.
------- old releases ------------------------------
-
-1.4.2 (9/18/2008)
+1.4.2 (9/18/2008) e7304e86ce9bb80978c7055fd2a9e999619a6fb8
+ "task undo" can now retract a "task done" command, provided no reports
have been run (and therefore TDB::gc run)
+ Task now correctly sorts on entire strings, instead of just the first
@@ -39,13 +82,13 @@
+ Bug: Source now properly includes in order to build clean
using gcc 4.3 (thanks to H. İbrahim Güngör)
-1.4.1 (7/18/2008)
+1.4.1 (7/18/2008) e080c3168c6064628ab85b21bd859d9875a3a9a7
+ Bug: Descriptions can not be altered with "task 123 New description"
+ Tweak: For "task calendar" month names are now centered over the month
+ Removed TUTORIAL file contents in favor of online version
+ Provided Mac .pkg binary
-1.4.0 (7/10/2008)
+1.4.0 (7/10/2008) 60b7d15a1d22e064acf0974c5d7eabbb57dd8071
+ New recurring tasks feature
+ "task undelete" can now undelete erroneously deleted tasks, provided no
reports have been run (and therefore TDB::gc run)
@@ -63,7 +106,7 @@
+ Bug: Adding a blank priority resulted in an assigned garbage value
+ Bug: Fixed parsing of date "07/08/2008" when using dateformat "m/d/Y"
-1.3.1 (6/21/2008)
+1.3.1 (6/21/2008) 3a6de7d9402f2609a773a73b16eff97b14a32869
+ New configuration variable, "defaultwidth" that determines the width
of tables when ncurses support is not available
+ Bug: "showage" configuration variable should apply to all reports, not
@@ -74,7 +117,7 @@
+ Bug: Task now will recreate a missing ~/.taskrc file, OR a missing
~/.task directory
-1.3.0 (6/18/2008)
+1.3.0 (6/18/2008) 6673e408a223af98c38779c20b08524042c0edfa
+ "task calendar" now displays multiple months per line, adjustable by the
"monthsperline" configuration variable. Feature added by Damian Glenny
+ "task export" can now filter tasks like the reports
@@ -89,7 +132,7 @@
days gets added to the entry date of task 2..n
+ Bug: Fixed bug whereby "1 wks" was being improperly pluralized
-1.2.0 (6/13/2008)
+1.2.0 (6/13/2008) c393d47cdfe7e197a31e94f4bb764474fa05ad8d
+ Bug: "dateformat" configuration variable used to display dates, but
not parse them
+ "task list x" now performs a caseless comparison between "x" and the
@@ -99,7 +142,7 @@
"list" and "next" reports
+ Improved TUTORIAL
-1.1.0 (6/7/2008)
+1.1.0 (6/7/2008) 73286e86628725b346db2a25fbcd4bd68efb9b3a
+ "blanklines" configuration to stop displaying unnecessary white
space and thus work better on small-screen devices
+ "dateformat" configuration now determines how dates are formatted
@@ -107,11 +150,11 @@
+ http://www.beckingham.net/task.html home page set up
+ Added tags to the "task long" report
-1.0.1 (6/4/2008)
+1.0.1 (6/4/2008) d216d401217027d93581808fc8944ab7d6b85fb0
+ Bug: UUID generator not properly terminating string.
+ Bug: srandom/srand not called prior to UUID generation.
-1.0.0 (6/3/2008)
+1.0.0 (6/3/2008) f3de5c07118c597091a05c7d7fe8bdeae95474c1
+ New movie made, uploaded
+ Bug: assertion fails on mobile for t v
+ Bug: configure.ac does not properly determine ncurses availability
@@ -124,19 +167,19 @@
+ Added rules for colorization by tag, project and keyword
+ Added legend to "task calendar"
-0.9.9 (5/27/2008)
+0.9.9 (5/27/2008) 2ecf50032226c91b406f247417a063dc17c8e324
+ Autoconf/automake behaving properly.
+ Clean build on OS X 10.5.
+ Clean build on Ubuntu 8.0.
+ Clean build on Fedora Core 8.
+ Clean build on Fedora Core 9.
-0.9.8 (5/25/2008)
+0.9.8 (5/25/2008) 18fd59a1edb20e5c68d086a97fae5fa9f6bb348a
+ Added "task color" command.
+ Removed unnecessary files.
+ Completed documentation.
-0.9.7 (5/24/2008)
+0.9.7 (5/24/2008) 25dc4150947a3e612c8118838d04b3bbe68441f7
+ Migrated old compiler flags into Makefile.am
+ Added ncurses endwin function check to configure.ac
+ Set up structure for AUTHORS file.
@@ -177,7 +220,7 @@
+ Added more missing files.
+ Added all source code.
+ Generic OSS files added.
- + Initial commit.
+ + Initial commit on Github.
0.9.3 (4/6/2008)
+ Added "task completed" command.
@@ -188,7 +231,7 @@
+ "task" duplicated to "task_rel" for preparation of a fork.
0.9.1 (4/1/2008)
- + Blank attributes read are longer be written out.
+ + Blank attributes read are no longer written out.
+ Completed "task export" command.
+ Added configuration values to "task version" command.
+ Consolidated header files, removed unnecessary ones.
@@ -216,10 +259,12 @@
+ File locking
+ retain deleted tasks
+ "task info ID" report showing all metadata
- + File format v2
+ + File format v2, including UUID
[Development hiatus while planning for T, TDB API, new features and the future
-of the project. Seeded to two testers for feedback, suggestions.]
+of the project. Seeded to two testers for feedback, suggestions. Development
+deliberately stopped to allow extended use of task, allowing command logging and
+regular usage to determine which features were needed or unnecessary.]
0.6.0 Reports (12/27/2006)
+ "task history"
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 000000000..4e4244870
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,595 @@
+# Makefile.in generated by automake 1.10 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = .
+DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
+ $(srcdir)/Makefile.in $(srcdir)/auto.h.in \
+ $(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \
+ depcomp install-sh missing
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = auto.h
+CONFIG_CLEAN_FILES =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+ html-recursive info-recursive install-data-recursive \
+ install-dvi-recursive install-exec-recursive \
+ install-html-recursive install-info-recursive \
+ install-pdf-recursive install-ps-recursive install-recursive \
+ installcheck-recursive installdirs-recursive pdf-recursive \
+ ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+ { test ! -d $(distdir) \
+ || { find $(distdir) -type d ! -perm -200 -exec chmod u+w {} ';' \
+ && rm -fr $(distdir); }; }
+DIST_ARCHIVES = $(distdir).tar.gz
+GZIP_ENV = --best
+distuninstallcheck_listfiles = find . -type f -print
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = src
+EXTRA_DIST = DEVELOPERS
+all: auto.h
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+am--refresh:
+ @:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ echo ' cd $(srcdir) && $(AUTOMAKE) --gnu '; \
+ cd $(srcdir) && $(AUTOMAKE) --gnu \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ echo ' $(SHELL) ./config.status'; \
+ $(SHELL) ./config.status;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ $(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+
+auto.h: stamp-h1
+ @if test ! -f $@; then \
+ rm -f stamp-h1; \
+ $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \
+ else :; fi
+
+stamp-h1: $(srcdir)/auto.h.in $(top_builddir)/config.status
+ @rm -f stamp-h1
+ cd $(top_builddir) && $(SHELL) ./config.status auto.h
+$(srcdir)/auto.h.in: $(am__configure_deps)
+ cd $(top_srcdir) && $(AUTOHEADER)
+ rm -f stamp-h1
+ touch $@
+
+distclean-hdr:
+ -rm -f auto.h stamp-h1
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+# (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+ @failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+ @failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ rev=''; for subdir in $$list; do \
+ if test "$$subdir" = "."; then :; else \
+ rev="$$subdir $$rev"; \
+ fi; \
+ done; \
+ rev="$$rev ."; \
+ target=`echo $@ | sed s/-recursive//`; \
+ for subdir in $$rev; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done && test -z "$$fail"
+tags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+ done
+ctags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+ done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) auto.h.in $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ list='$(SOURCES) $(HEADERS) auto.h.in $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) auto.h.in $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) auto.h.in $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ $(am__remove_distdir)
+ test -d $(distdir) || mkdir $(distdir)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+ list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ distdir=`$(am__cd) $(distdir) && pwd`; \
+ top_distdir=`$(am__cd) $(top_distdir) && pwd`; \
+ (cd $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$top_distdir" \
+ distdir="$$distdir/$$subdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+ -find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \
+ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+ || chmod -R a+r $(distdir)
+dist-gzip: distdir
+ tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+ $(am__remove_distdir)
+
+dist-bzip2: distdir
+ tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
+ $(am__remove_distdir)
+
+dist-tarZ: distdir
+ tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+ $(am__remove_distdir)
+
+dist-shar: distdir
+ shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
+ $(am__remove_distdir)
+
+dist-zip: distdir
+ -rm -f $(distdir).zip
+ zip -rq $(distdir).zip $(distdir)
+ $(am__remove_distdir)
+
+dist dist-all: distdir
+ tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+ $(am__remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration. Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+ case '$(DIST_ARCHIVES)' in \
+ *.tar.gz*) \
+ GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\
+ *.tar.bz2*) \
+ bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\
+ *.tar.Z*) \
+ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+ *.shar.gz*) \
+ GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\
+ *.zip*) \
+ unzip $(distdir).zip ;;\
+ esac
+ chmod -R a-w $(distdir); chmod a+w $(distdir)
+ mkdir $(distdir)/_build
+ mkdir $(distdir)/_inst
+ chmod a-w $(distdir)
+ dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+ && cd $(distdir)/_build \
+ && ../configure --srcdir=.. --prefix="$$dc_install_base" \
+ $(DISTCHECK_CONFIGURE_FLAGS) \
+ && $(MAKE) $(AM_MAKEFLAGS) \
+ && $(MAKE) $(AM_MAKEFLAGS) dvi \
+ && $(MAKE) $(AM_MAKEFLAGS) check \
+ && $(MAKE) $(AM_MAKEFLAGS) install \
+ && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+ && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+ distuninstallcheck \
+ && chmod -R a-w "$$dc_install_base" \
+ && ({ \
+ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+ } || { rm -rf "$$dc_destdir"; exit 1; }) \
+ && rm -rf "$$dc_destdir" \
+ && $(MAKE) $(AM_MAKEFLAGS) dist \
+ && rm -rf $(DIST_ARCHIVES) \
+ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck
+ $(am__remove_distdir)
+ @(echo "$(distdir) archives ready for distribution: "; \
+ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+ @cd $(distuninstallcheck_dir) \
+ && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \
+ || { echo "ERROR: files left after uninstall:" ; \
+ if test -n "$(DESTDIR)"; then \
+ echo " (check DESTDIR support)"; \
+ fi ; \
+ $(distuninstallcheck_listfiles) ; \
+ exit 1; } >&2
+distcleancheck: distclean
+ @if test '$(srcdir)' = . ; then \
+ echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+ exit 1 ; \
+ fi
+ @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+ || { echo "ERROR: files left in build directory after distclean:" ; \
+ $(distcleancheck_listfiles) ; \
+ exit 1; } >&2
+check-am: all-am
+check: check-recursive
+all-am: Makefile auto.h
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-hdr distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-info: install-info-recursive
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-ps: install-ps-recursive
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+ -rm -rf $(top_srcdir)/autom4te.cache
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \
+ install-strip
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+ all all-am am--refresh check check-am clean clean-generic \
+ ctags ctags-recursive dist dist-all dist-bzip2 dist-gzip \
+ dist-shar dist-tarZ dist-zip distcheck distclean \
+ distclean-generic distclean-hdr distclean-tags distcleancheck \
+ distdir distuninstallcheck dvi dvi-am html html-am info \
+ info-am install install-am install-data install-data-am \
+ install-dvi install-dvi-am install-exec install-exec-am \
+ install-html install-html-am install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
+ pdf-am ps ps-am tags tags-recursive uninstall uninstall-am
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/NEWS b/NEWS
index e936a4cb7..8d2345ad2 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-Welcome to Task 1.4.3.
+Welcome to Task 1.5.0.
Task has been built and tested on the following configurations:
@@ -6,8 +6,10 @@ Task has been built and tested on the following configurations:
- OS X 10.5 Leopard
- Fedora Core 8
- Fedora Core 9
+ - Fedora Core 10
+ - Ubuntu 7 Feisty Fawn
- Ubuntu 8 Hardy Heron
- - Ubuntu 9 Feisty Fawn
+ - Ubunto 8.10 Intrepid Ibex
- Solaris 10
- Cygwin 1.5.25-14
diff --git a/TUTORIAL b/TUTORIAL
deleted file mode 100644
index e389b623a..000000000
--- a/TUTORIAL
+++ /dev/null
@@ -1,6 +0,0 @@
-
-This TUTORIAL file has been deprecated. It is superseded by a richer and more
-extensive online version that can be found at:
-
- http://www.beckingham.net/task.html
-
diff --git a/configure.ac b/configure.ac
index 0c1c977b3..4ae1d6760 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,36 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
-AC_INIT(task, 1.4.3, bugs@beckingham.net)
+AC_INIT(task, 1.5.0, bugs@beckingham.net)
+
+CFLAGS="${CFLAGS=}"
+CXXFLAGS="${CXXFLAGS=}"
+# this macro is used to get the arguments supplied
+# to the configure script (./configure --enable-debug)
+# Check if we have enable debug support.
+AC_MSG_CHECKING(whether to enable debugging)
+debug_default="yes"
+AC_ARG_ENABLE(debug, [ --enable-debug=[no/yes] turn on debugging
+ [default=$debug_default]],, enable_debug=$debug_default)
+# Yes, shell scripts can be used
+if test "$enable_debug" = "yes"; then
+ CXXFLAGS="$CFLAGS -Wall -pedantic -ggdb3 -DDEBUG"
+ AC_MSG_RESULT(yes)
+else
+ CXXFLAGS="$CFLAGS -O3"
+ AC_MSG_RESULT(no)
+fi
+
+# Check for OS.
+OS=`uname|sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+if test "$OS" = "sunos"; then
+ AC_MSG_NOTICE([OS Solaris detected])
+ AC_DEFINE([SOLARIS], [], [Compiling on Solaris])
+else
+ AC_MSG_NOTICE([OS Non-Solaris detected])
+ AC_DEFINE([LINUX], [], [Compiling on Non-Solaris])
+fi
+
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/task.cpp])
AC_CONFIG_HEADER([auto.h])
@@ -12,6 +41,8 @@ AC_PROG_CXX
AC_PROG_CC
AC_LANG(C++)
+AC_SUBST(CFLAGS)
+
# Checks for libraries.
AC_CHECK_LIB(ncurses,initscr)
AC_CHECK_LIB(ncurses,endwin)
@@ -33,10 +64,11 @@ AC_STRUCT_TM
AC_FUNC_MKTIME
AC_FUNC_SELECT_ARGTYPES
AC_CHECK_FUNCS([select])
-AC_CHECK_FUNC(flock, [AC_DEFINE([HAVE_FLOCK], [1], [Found flock])])
+#AC_CHECK_FUNC(flock, [AC_DEFINE([HAVE_FLOCK], [1], [Found flock])])
AC_CHECK_FUNC(uuid_unparse_lower, [AC_DEFINE([HAVE_UUID], [1], [Found uuid_unparse_lower])])
AC_CHECK_FUNC(random, [AC_DEFINE([HAVE_RANDOM], [1], [Found random])])
AC_CHECK_FUNC(srandom, [AC_DEFINE([HAVE_SRANDOM], [1], [Found srandom])])
AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT
+
diff --git a/grammar.bnf b/grammar.bnf
new file mode 100644
index 000000000..2905420f4
--- /dev/null
+++ b/grammar.bnf
@@ -0,0 +1,40 @@
+
+# This is a full BNF grammar for the task command line. It is intended that a
+# future release of task will incorporate a complete lexer/parser implementing
+# this grammar, which will allow for more sophisticated command lines, for
+# example:
+#
+# task delete 1 2 4-7
+# task add pri:H pro:X -- pro pri 1 ///
+#
+
+command ::= simple_command
+ | filter_command filter?
+ | id_command
+ | "export" file
+ |
+ | ;
+
+simple_command ::= "version" | "help" | "projects" | "tags" | "next" | "stats"
+ | "color" ;
+
+filter_command ::= "summary" | "history" | "calendar" | "active" | "overdue"
+ | "oldest" | "newest" | "add" | "list" | "long" | "ls"
+ | "completed" ;
+
+id_command ::= "delete" | "undelete" | "info" | "start" | "end" | "done"
+ | "undo" ;
+
+filter ::= filter_part+ ;
+
+filter_part ::= tag_add | tag_remove | attribute | word ;
+
+tag_add ::= "+" word ;
+tag_remove ::= "-" word ;
+attribute ::= word ":" word ;
+word ::=
+file ::=
+id ::= digit+ ;
+digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
+substitution ::= "/" word+ "/" word* "/" ;
+
diff --git a/grammar.txt b/grammar.txt
deleted file mode 100644
index aad7d610c..000000000
--- a/grammar.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-
-This is a full BNF grammar for the task command line. It is intended that a
-future release of task will incorporate a complete lexer/parser implementing
-this grammar.
-
-
-command:
- VERSION
- | HELP
- | PROJECTS
- | TAGS
- | SUMMARY
- | HISTORY
- | NEXT
- | CALENDAR
- | ACTIVE
- | OVERDUE
- | STATS
- | USAGE
- | OLDEST
- | NEWEST
- | EXPORT
- | COLOR
- | DELETE
- | UNDELETE
- | INFO
- | START
- | DONE
- | ADD [] [] []
- | LIST [] [] []
- | LONG [] [] []
- | LS [] [] []
- | COMPLETED [] [] []
- | [] [] []
- |
-
-id:
- \d+
- | \d{8}-\d{4}-\d{4}-\d{12}
-
-tags:
- +
- | -
-
-tag:
- \w+
-
-attrs:
-
- |
-
-attr:
- :
-
-name:
- \w+
-
-value:
- .+
-
-substitution:
- / / /
-
-pattern:
- .+
-
-file:
- ?
-
diff --git a/html/30second.html b/html/30second.html
index 6cde1c6c2..e40d6814b 100644
--- a/html/30second.html
+++ b/html/30second.html
@@ -80,7 +80,7 @@ No matches
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/advanced.html b/html/advanced.html
index 03a87d057..d3450e56b 100644
--- a/html/advanced.html
+++ b/html/advanced.html
@@ -158,6 +158,11 @@ ID Project Pri Due Active Age Description
"task start ..." command was run, as shown above.
+ % task stop <id>
+
+ Marks a task as inactive, by removing the start time.
+
+
% task overdue
Simply lists all the task that have a due date that is past, in
@@ -393,7 +398,7 @@ on_white on_bright_white
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/color.html b/html/color.html
index 2d939a2e6..32e18fd71 100644
--- a/html/color.html
+++ b/html/color.html
@@ -77,7 +77,7 @@ on_white on_bright_white
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/config.html b/html/config.html
index bbdb16b73..bfe97b9ac 100644
--- a/html/config.html
+++ b/html/config.html
@@ -171,28 +171,12 @@
- showage
-
- May be "yes" or "no". Determines whether the "Age"
- column appears on the "list" and "next" reports.
-
-
monthsperline
Determines how many months the "task calendar" command
- renders across the screen. Defaults to 1.
-
-
- oldest
-
- Determines how many tasks the "task oldest" command displays.
- Defaults to 10.
-
-
- newest
-
- Determines how many tasks the "task newest" command displays.
- Defaults to 10.
+ renders across the screen. Defaults to however many will
+ fit. If more months that will fit are specified, task will
+ only show as many that will fit.
defaultwidth
@@ -201,6 +185,14 @@
Defaults to 80.
+ due
+
+
+ This is the number of days into the future that define when a
+ task is considered due, and is colored accordingly.
+ Defaults to 7.
+
+
color
May be "on" or "off". Determines whether task uses color.
@@ -216,7 +208,8 @@
color.pri.L
color.pri.none
color.active
- color.tagged
+ color.tagged
+ color.recurring
These are the coloration rules. They correspond to a particular
@@ -322,13 +315,38 @@ ID Project Pri Description
whenever the shadow file is updated by some task command.
+ locking
+
+
+ Determines whether task uses file locking when accessing the pending.data
+ and completed.data files. Default to "on". Solaris users who store
+ the task data files on an NFS mount may need to set locking to "off".
+
+
+
+ Note that setting this value to "off" is dangerous. It means that
+ another program may write to the task.pending file when task is
+ attempting to do the same.
+
+
+
+
+ Note that the command:
+
+
+ task version
+
+
+ will display the configuration variables found in the .taskrc file,
+ and will warn you of any variables that are not recognized.
+
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/custom.html b/html/custom.html
new file mode 100644
index 000000000..1ee499a35
--- /dev/null
+++ b/html/custom.html
@@ -0,0 +1,164 @@
+
+
+
+ Custom Reports
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Custom Reports
+
+
+ Task allows you to customize reports, to a limited degree.
+ The "list", "long", "ls", "oldest" and "newest" reports are
+ all now custom reports, whereas in previous releases of task
+ they were not mutable. This means they can be modified,
+ renamed, or deleted.
+
+
+
+ More importantly, you can define your own. Here are the
+ three necessary items in the .taskrc file that define a new
+ report:
+
+
+
report.mine.description=Just the essentials
+report.mine.columns=id,project,priority,description
+report.mine.sort=priority-,project+
+
+
+ This defines a report, called "mine", that has four columns:
+ id, project, priority and description. It will be sorted on
+ two columns: by descending priority then ascending project.
+ The description that shows up in the task command usage page
+ is "Just the essentials". Because this report is called
+ "mine", it can be run with the command:
+
+
+
% task mine
+
+
+ An optional filter can also be specified like this:
+
+
+
report.mine.filter=priority:H +bug
+
+
+ This adds a filter so that only tasks with priority "H" and
+ with the "bug" tag are included in the report. This filter
+ definition is optional.
+
+
+
+ An optional limit can also be specified, which limits the
+ number of tasks shown in the report. If a limit is not
+ specified, then the number of tasks is not limited.
+
+
+
report.mine.limit=10
+
+
+ Here is a list of all the possible columns that may be included
+ in a report:
+
+
+
+ id
+ uuid
+ project
+ priority
+ entry
+ start
+ due
+ age
+ active
+ tags
+ recur
+ description
+
+
+
+ Custom reports will show up in the task command line usage.
+
+
+
+
+
+
+
+
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html/date.html b/html/date.html
index 9c02cd399..a06f291db 100644
--- a/html/date.html
+++ b/html/date.html
@@ -110,7 +110,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/filter.html b/html/filter.html
index 4f353bc44..04e3090b6 100644
--- a/html/filter.html
+++ b/html/filter.html
@@ -82,7 +82,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/links.html b/html/links.html
index c2739c586..77dc5a15b 100644
--- a/html/links.html
+++ b/html/links.html
@@ -37,6 +37,32 @@
Task links from around the web...
+
+ February 2009, Todo.txt CLI Manages Your Tasks from the Command Line
+
+
+ Gina Trapani generously mentions task in an article about the newly updated, todo.sh 2.0.
+
+
+
+
+ February, 2009, My command line based task management tools
+
+
+ Richard Querin talks about his task management tools.
+ Richard generously provides the Debian packages for task.
+
+
+
+
+ February, 2009, Common Apps
+
+
+ Archlinux.org mentions task on a page which is
+ a point of reference for people looking for software to fill a particular need.
+
+
+
November 2008, Task repository on GitHub
@@ -137,7 +163,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/recur.html b/html/recur.html
index 978c94cef..fd81b6aca 100644
--- a/html/recur.html
+++ b/html/recur.html
@@ -155,7 +155,7 @@ recurrences of this same task? (y/n) y
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/setup.html b/html/setup.html
index 7030b3cbc..b6ae3d485 100644
--- a/html/setup.html
+++ b/html/setup.html
@@ -90,7 +90,7 @@ Done.
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/shadow.html b/html/shadow.html
index 4b0248b1f..e59d36fd2 100644
--- a/html/shadow.html
+++ b/html/shadow.html
@@ -43,7 +43,7 @@
This means there is always a current version of the task
report kept in a text file. Products such as
- Samurize ,
+ Samurize ,
MkConsole ,
or
GeekTool
@@ -78,7 +78,7 @@ shadow.notify=on
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/shell.html b/html/shell.html
index ad33441b4..cb3873dcf 100644
--- a/html/shell.html
+++ b/html/shell.html
@@ -80,7 +80,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/simple.html b/html/simple.html
index 9e7f0f88c..860421d6a 100644
--- a/html/simple.html
+++ b/html/simple.html
@@ -305,7 +305,7 @@ ID Project Pri Due Active Age Description
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/task.html b/html/task.html
index 6358d5fec..184f155d0 100644
--- a/html/task.html
+++ b/html/task.html
@@ -57,6 +57,7 @@
Old Versions
Filters
Shadow Files
+ Custom Reports
@@ -70,61 +71,118 @@
- Get the Latest Release
+ Get the Latest Stable Release
-
New in version 1.4.3 (11/10/2008)
+
New in version 1.5.0 (3/15/2009)
- Fixed misleading task count at bottom of "info" report.
- Added support for a shadow file that contains a plain text task report,
- with the "shadow.file" and "shadow.command" configuration variables.
- The shadow file is automatically updated whenever the task database
- changes. Useful for integrating with "Samurize".
- Task now displays a message whenever a shadow file is updated, if the
- "shadow.notify" configuration variable is set "on".
- Fixed bug whereby adding a task with a \n, \r or \f did not fail properly.
- Removed "task usage" command.
- Added documentation for Shadow files.
- Added documentation for task filters.
+ Removed deprecated TUTORIAL file.
+ Removed support for the "showage" configuration variable.
+ "task stop" can remove the start time from a started task.
+ "task ghistory" now displays a differently aligned graph, allowing
+ easier comparison by month of tasks added versus completed and deleted.
+ "task version" command now reports unrecognized configuration variables,
+ which may be spelling mistakes or deprecated variables.
+ "configure --enable-debug" now supported to suppress compiler optimization
+ to allow debugging.
+ Allow lower case priorities, and automatically upper case them.
+ Added support for "due" configuration variable which defines the number
+ of days in the future when a task is considered due.
+ Added support for custom reports, comprised of a set of column names and
+ sort order, with optional filtering in the configuration file. This
+ means user-defined reports can be written, and the reports currently
+ in the configuration file can be renamed. Several of task's built in
+ reports have been converted to user-defined reports.
+ New online documentation for custom reports.
+ New algorithm for determining when the "nag" message is displayed.
+ Fixed bug where task hangs with a certain combination of recurring tasks
+ and shadow files.
+ Fixed bug with the task sort algorithm, which led to an unstable sequence
+ when there were only a handful of tasks.
+ Performance enhanced by eliminating unnecessary sorting.
+ Task now has a large (and growing) test suite and bug regression tests
+ to help ensure higher quality releases.
+ Fixed bug that caused large performance hit during table rendering.
+ Fixed bug that concatenated a modified description without spaces.
+ Added new column 'recur' that displays the recurrence period of any
+ recurring tasks. This column can be added to any custom report.
+ Added support for "color.recurring" configuration variable which
+ specifies the color of recurring tasks.
+ Added support for "locking" configuration variable that controls whether
+ file locking is used.
+ Task export feature now includes recurrence information, removes nested
+ quotes, and limits output to pending tasks.
+ Task no longer includes deleted tasks in the summary report (thanks to
+ Benjamin Tegarden).
+ Fixed bug that prevented the summary report from properly reporting
+ recently completed tasks.
(Find out what was new in prior versions )
-
+
Troubleshooting
Task has been built from source and tested in the following environments:
-
- OS X 10.4 Tiger
- OS X 10.5 Leopard
- Fedora Core 8
- Fedora Core 9
- Ubuntu 7 Feisty Fawn
- Ubuntu 8 Hardy Heron
- Solaris 10
- Cygwin 1.5.25-14
-
+
+
+ OS X 10.4 Tiger
+ OS X 10.5 Leopard
+ Fedora Core 8
+ Fedora Core 9
+ Fedora Core 10
+ Ubuntu 7 Feisty Fawn
+ Ubuntu 8 Hardy Heron
+ Ubuntu 8.10 Intrepid Ibex
+ Solaris 10
+ Cygwin 1.5.25-14
+
+
If you have difficulties building task, have found a bug, have a
@@ -143,7 +201,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/troubleshooting.html b/html/troubleshooting.html
index 5e3147631..f175d56d2 100644
--- a/html/troubleshooting.html
+++ b/html/troubleshooting.html
@@ -60,32 +60,6 @@
-
- How to get rid of the "Age" column
-
-
- The "Age" column that shows up on several reports is proving
- to be unpopular. In task 1.2.0 and later, here is how to
- remove it from the reports - make sure you have the line:
-
-
-
showage=no
-
-
- in your ~/.taskrc file.
-
- Note that the "task long" report does not obey this setting
- in versions prior to 1.3.1.
-
-
-
- The "showage" setting is supported in task 1.2.0 or later.
-
- The "task long" report supports this setting in versions 1.3.1
- or later.
-
-
-
How do I build task under Cygwin?
@@ -113,7 +87,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/usage.html b/html/usage.html
index 451c49653..840eb9b15 100644
--- a/html/usage.html
+++ b/html/usage.html
@@ -34,17 +34,16 @@
-
task add [tags] [attrs] desc...
- task list [tags] [attrs] desc...
- task long [tags] [attrs] desc...
- task ls [tags] [attrs] desc...
+ Usage: task
+ task add [tags] [attrs] desc...
task completed [tags] [attrs] desc...
- task ID [tags] [attrs] ["desc..."]
+ task ID [tags] [attrs] [desc...]
task ID /from/to/
task delete ID
task undelete ID
task info ID
task start ID
+ task stop ID
task done ID
task undo ID
task projects
@@ -56,12 +55,18 @@
task calendar
task active
task overdue
- task oldest
- task newest
task stats
task export
task color
task version
+ task help
+ task list [tags] [attrs] desc...
+ task long [tags] [attrs] desc...
+ task ls [tags] [attrs] desc...
+ task newest [tags] [attrs] desc...
+ task oldest [tags] [attrs] desc...
+
+See http://www.beckingham.net/task.html for the latest releases and a full tutorial.
ID is the numeric identifier displayed by the 'task list' command
@@ -73,8 +78,11 @@ Attributes are:
project: Project name
priority: Priority
due: Due date
+ recur: Recurrence frequency
+ until: Recurrence end date
fg: Foreground color
bg: Background color
+ rc: Alternate .taskrc file
Any command or attribute name may be abbreviated if still unique:
task list project:Home
@@ -85,14 +93,14 @@ Some task descriptions need to be escaped because of the shell:
task add escaped \' quote
Many characters have special meaning to the shell, including:
- $ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~
+ $ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/html/versions.html b/html/versions.html
index 347ed039a..60403f682 100644
--- a/html/versions.html
+++ b/html/versions.html
@@ -36,6 +36,32 @@
+
+
New in version 1.4.3 (11/1/2008)
+
task-1.4.3.tar.gz
+
+ Mac OS X 10.5 (Leopard) Intel-only:
+
task-1.4.3.pkg
+
+ Debian package:
task_1.4.3-1_i386.deb
+ (Thanks to
Richard Querin )
+
+
+
+ Fixed misleading task count at bottom of "info" report.
+ Added support for a shadow file that contains a plain text task report,
+ with the "shadow.file" and "shadow.command" configuration variables.
+ The shadow file is automatically updated whenever the task database
+ changes. Useful for integrating with "Samurize".
+ Task now displays a message whenever a shadow file is updated, if the
+ "shadow.notify" configuration variable is set "on".
+ Fixed bug whereby adding a task with a \n, \r or \f did not fail properly.
+ Removed "task usage" command.
+ Added documentation for Shadow files.
+ Added documentation for task filters.
+
+
+
New in version 1.4.2 (9/18/2008)
task-1.4.2.tar.gz
@@ -224,7 +250,7 @@
- Copyright 2006-2008, P. Beckingham. All rights reserved.
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
diff --git a/ideas.txt b/ideas.txt
deleted file mode 100644
index 09cea7499..000000000
--- a/ideas.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-Real Parsing
- define grammar for command line
- implement flex/bison parser
- new grammar includes:
- - task delete
[ ...]
- - task done [ ...]
-
-User-Defined Reports
- report.large=id,uuid,project,priority,entry,start,due,active,tags,description
- report.long=id,project,priority,entry,start,due,tags,description
- report.list=id,project,priority,due,active,description
- report.ls=id,project,priority,description
-
- Sorting is always: due+ priority- project+
-
- ID UUID Project Priority Entry Start Due Active Tags Description
-
-Test Suite
- - allow .taskrc override
- - debug=on to cause all cout to be csv
- - regression tests for every bug, command, feature
-
diff --git a/script.txt b/script.txt
index e7d1913e6..db62d92a9 100644
--- a/script.txt
+++ b/script.txt
@@ -3,18 +3,18 @@
task add do laundry Let's add some tasks
I need to do laundry
-task add project:garage order dumpster Oh yeah, the dumpster
+task add project:garage order dumpster Oh yeah, I need to order the dumpster
-task add +phone tell mom i loveher Must call Mom (that "phone" there is a tag - they are
- useful for searching, categorizing)
+task add +phone tell mom i loveher Must call Mom (that "phone" there is a tag - they can
+ be useful for searching and categorizing)
task add +phone pro:garage schedule
goodwill pickup
-task ad +email pro:garage ask Tom if Notice I can abbreviating commands
+task ad +email pro:garage ask Tom if Notice I can abbreviate commands
he wants that old bkie
task ls Let's see what we've got
- I spelled bike wrong
+ Oh, I spelled bike wrong
task 5 /bkie/bike/
task ls That's better
@@ -97,6 +97,10 @@ task summary Summary shows progress on all projec
task history History shows general activity - how many added,
completed etc, by month
+task ghistory This report shows a histogram of tasks that were
+ added (in red), completed (in green) and deleted
+ (in yellow), all by month.
+
And that's it. There are more commands than this
covered in the online documentation, but this should give
the basic idea.
diff --git a/src/Config.cpp b/src/Config.cpp
index 03ca730dd..c2cb928fc 100644
--- a/src/Config.cpp
+++ b/src/Config.cpp
@@ -1,7 +1,7 @@
///////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -36,8 +36,35 @@
#include "Config.h"
////////////////////////////////////////////////////////////////////////////////
+// These are default (but overridable) reports. These entries are necessary
+// because these three reports were converted from hard-coded reports to custom
+// reports, and therefore need these config file entries. However, users are
+// already used to seeing these five reports, but do not have these entries.
+// The choice was a) make users edit their .taskrc files, b) write a .taskrc
+// upgrade program to make the change, or c) this.
Config::Config ()
{
+ (*this)["report.long.description"] = "Lists all task, all data, matching the specified criteria";
+ (*this)["report.long.columns"] = "id,project,priority,entry,start,due,recur,age,tags,description";
+ (*this)["report.long.sort"] = "due+,priority-,project+";
+
+ (*this)["report.list.description"] = "Lists all tasks matching the specified criteria";
+ (*this)["report.list.columns"] = "id,project,priority,due,active,age,description";
+ (*this)["report.list.sort"] = "due+,priority-,project+";
+
+ (*this)["report.ls.description"] = "Minimal listing of all tasks matching the specified criteria";
+ (*this)["report.ls.columns"] = "id,project,priority,description";
+ (*this)["report.ls.sort"] = "priority-,project+";
+
+ (*this)["report.newest.description"] = "Shows the newest tasks";
+ (*this)["report.newest.columns"] = "id,project,priority,due,active,age,description";
+ (*this)["report.newest.sort"] = "id-";
+ (*this)["report.newest.limit"] = "10";
+
+ (*this)["report.oldest.description"] = "Shows the oldest tasks";
+ (*this)["report.oldest.columns"] = "id,project,priority,due,active,age,description";
+ (*this)["report.oldest.sort"] = "id+";
+ (*this)["report.oldest.limit"] = "10";
}
////////////////////////////////////////////////////////////////////////////////
@@ -47,9 +74,9 @@ Config::Config (const std::string& file)
}
////////////////////////////////////////////////////////////////////////////////
-// Read the Configuration filee and populate the *this map. The file format
-// is simply lines with name=value pairs. Whitespace between name, = and value
-// is not tolerated, but blank lines and comments starting with # are allowed.
+// Read the Configuration file and populate the *this map. The file format is
+// simply lines with name=value pairs. Whitespace between name, = and value is
+// not tolerated, but blank lines and comments starting with # are allowed.
bool Config::load (const std::string& file)
{
std::ifstream in;
@@ -115,25 +142,60 @@ void Config::createDefault (const std::string& home)
fprintf (out, "confirmation=yes\n");
fprintf (out, "next=2\n");
fprintf (out, "dateformat=m/d/Y\n");
- fprintf (out, "showage=yes\n");
- fprintf (out, "monthsperline=1\n");
+ fprintf (out, "#monthsperline=2\n");
fprintf (out, "curses=on\n");
fprintf (out, "color=on\n");
+ fprintf (out, "due=7\n");
+ fprintf (out, "nag=You have higher priority tasks.\n");
+ fprintf (out, "locking=on\n");
fprintf (out, "color.overdue=bold_red\n");
- fprintf (out, "#color.due=on_bright_yellow\n");
- fprintf (out, "#color.pri.H=on_red\n");
+ fprintf (out, "color.due=bold_yellow\n");
+ fprintf (out, "color.pri.H=bold\n");
fprintf (out, "#color.pri.M=on_yellow\n");
fprintf (out, "#color.pri.L=on_green\n");
+ fprintf (out, "#color.pri.none=white on_blue\n");
fprintf (out, "color.active=bold_cyan\n");
fprintf (out, "color.tagged=yellow\n");
fprintf (out, "#color.tag.bug=yellow\n");
- fprintf (out, "#color.project.home=on_green\n");
+ fprintf (out, "#color.project.garden=on_green\n");
fprintf (out, "#color.keyword.car=on_blue\n");
+ fprintf (out, "#color.recurring=on_red\n");
fprintf (out, "#shadow.file=%s/shadow.txt\n", dataDir.c_str ());
fprintf (out, "#shadow.command=list\n");
fprintf (out, "#shadow.notify=on\n");
- fprintf (out, "#default.command=list\n");
+ fprintf (out, "#default.project=foo\n");
+ fprintf (out, "#default.priority=M\n");
+ fprintf (out, "default.command=list\n");
+
+ // Custom reports.
+ fprintf (out, "# Fields: id,uuid,project,priority,entry,start,due,recur,age,active,tags,description\n");
+ fprintf (out, "# Description: This report is ...\n");
+ fprintf (out, "# Sort: due+,priority-,project+\n");
+ fprintf (out, "# Filter: pro:x pri:H +bug\n");
+ fprintf (out, "# Limit: 10\n");
+
+ fprintf (out, "report.long.description=Lists all task, all data, matching the specified criteria\n");
+ fprintf (out, "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n");
+ fprintf (out, "report.long.sort=due+,priority-,project+\n");
+
+ fprintf (out, "report.list.description=Lists all tasks matching the specified criteria\n");
+ fprintf (out, "report.list.columns=id,project,priority,due,active,age,description\n");
+ fprintf (out, "report.list.sort=due+,priority-,project+\n");
+
+ fprintf (out, "report.ls.description=Minimal listing of all tasks matching the specified criteria\n");
+ fprintf (out, "report.ls.columns=id,project,priority,description\n");
+ fprintf (out, "report.ls.sort=priority-,project+\n");
+
+ fprintf (out, "report.newest.description=Shows the newest tasks\n");
+ fprintf (out, "report.newest.columns=id,project,priority,due,active,age,description\n");
+ fprintf (out, "report.newest.sort=id-\n");
+ fprintf (out, "report.newest.limit=10\n");
+
+ fprintf (out, "report.oldest.description=Shows the oldest tasks\n");
+ fprintf (out, "report.oldest.columns=id,project,priority,due,active,age,description\n");
+ fprintf (out, "report.oldest.sort=id+\n");
+ fprintf (out, "report.oldest.limit=10\n");
fclose (out);
@@ -192,11 +254,13 @@ bool Config::get (const std::string& key, bool default_value)
{
std::string value = lowerCase ((*this)[key]);
- if (value == "t" ||
- value == "true" ||
- value == "1" ||
- value == "yes" ||
- value == "on")
+ if (value == "t" ||
+ value == "true" ||
+ value == "1" ||
+ value == "yes" ||
+ value == "on" ||
+ value == "enable" ||
+ value == "enabled")
return true;
return false;
diff --git a/src/Config.h b/src/Config.h
index 4488ec468..0286ea950 100644
--- a/src/Config.h
+++ b/src/Config.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/Date.cpp b/src/Date.cpp
index 67ac7e7c7..995011345 100644
--- a/src/Date.cpp
+++ b/src/Date.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/Date.h b/src/Date.h
index ad3f659e0..e8538435d 100644
--- a/src/Date.h
+++ b/src/Date.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/Grid.cpp b/src/Grid.cpp
index faaa4e90f..59e366b32 100644
--- a/src/Grid.cpp
+++ b/src/Grid.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -60,6 +60,7 @@
////////////////////////////////////////////////////////////////////////////////
#include
+#include
#include
////////////////////////////////////////////////////////////////////////////////
@@ -325,7 +326,7 @@ Grid::Cell::operator int () const
case CELL_INT: return mInt;
case CELL_FLOAT: return (int) mFloat;
case CELL_DOUBLE: return (int) mDouble;
- case CELL_STRING: return mString.length ();
+ case CELL_STRING: return ::atoi (mString.c_str ());
}
return 0;
@@ -340,7 +341,7 @@ Grid::Cell::operator float () const
case CELL_INT: return (float) mInt;
case CELL_FLOAT: return mFloat;
case CELL_DOUBLE: return (float) mDouble;
- case CELL_STRING: return (float) mString.length ();
+ case CELL_STRING: return (float) ::atof (mString.c_str ());
}
return 0.0;
@@ -355,7 +356,7 @@ Grid::Cell::operator double () const
case CELL_INT: return (double) mInt;
case CELL_FLOAT: return (double) mFloat;
case CELL_DOUBLE: return mDouble;
- case CELL_STRING: return (double) mString.length ();
+ case CELL_STRING: return (double) ::atof (mString.c_str ());
}
return 0.0;
diff --git a/src/Grid.h b/src/Grid.h
index cf9ff8ecb..e1315e8c3 100644
--- a/src/Grid.h
+++ b/src/Grid.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/Makefile.am b/src/Makefile.am
index eae651e2b..8203dbb14 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,2 @@
bin_PROGRAMS = task
-task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h
-AM_CPPFLAGS = -Wall -pedantic -ggdb3 -fno-rtti
+task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h
diff --git a/src/Makefile.in b/src/Makefile.in
index 120185c41..ae8c519e5 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -44,9 +44,10 @@ am__installdirs = "$(DESTDIR)$(bindir)"
binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
PROGRAMS = $(bin_PROGRAMS)
am_task_OBJECTS = Config.$(OBJEXT) Date.$(OBJEXT) T.$(OBJEXT) \
- TDB.$(OBJEXT) Table.$(OBJEXT) Grid.$(OBJEXT) color.$(OBJEXT) \
- parse.$(OBJEXT) task.$(OBJEXT) command.$(OBJEXT) \
- report.$(OBJEXT) util.$(OBJEXT) text.$(OBJEXT) rules.$(OBJEXT)
+ TDB.$(OBJEXT) Table.$(OBJEXT) Grid.$(OBJEXT) Timer.$(OBJEXT) \
+ color.$(OBJEXT) parse.$(OBJEXT) task.$(OBJEXT) \
+ command.$(OBJEXT) report.$(OBJEXT) util.$(OBJEXT) \
+ text.$(OBJEXT) rules.$(OBJEXT)
task_OBJECTS = $(am_task_OBJECTS)
task_LDADD = $(LDADD)
DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@
@@ -154,8 +155,7 @@ sysconfdir = @sysconfdir@
target_alias = @target_alias@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
-task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h
-AM_CPPFLAGS = -Wall -pedantic -ggdb3 -fno-rtti
+task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h
all: all-am
.SUFFIXES:
@@ -228,6 +228,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/T.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TDB.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Table.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Timer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@
diff --git a/src/T.cpp b/src/T.cpp
index e243c1db6..f490d8681 100644
--- a/src/T.cpp
+++ b/src/T.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -328,6 +328,11 @@ const std::string T::composeCSV ()
line += value;
line += ",";
+ value = mAttributes["recur"];
+ if (value != "")
+ line += value;
+ line += ",";
+
value = mAttributes["end"];
if (value != "")
line += value;
@@ -353,7 +358,11 @@ const std::string T::composeCSV ()
line += "'" + value + "'";
line += ",";
- line += "'" + mDescription + "'\n";
+ // Convert single quotes to double quotes, because single quotes are used to
+ // delimit the values that need it.
+ std::string clean = mDescription;
+ std::replace (clean.begin (), clean.end (), '\'', '"');
+ line += "'" + clean + "'\n";
return line;
}
@@ -468,7 +477,7 @@ void T::parse (const std::string& line)
break;
default:
- throw std::string ();
+ throw std::string ("Unrecognized task file format.");
break;
}
}
diff --git a/src/T.h b/src/T.h
index e26d25b5f..fc126ae34 100644
--- a/src/T.h
+++ b/src/T.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/TDB.cpp b/src/TDB.cpp
index e531f10a1..0ab4ed10a 100644
--- a/src/TDB.cpp
+++ b/src/TDB.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -38,6 +38,7 @@ TDB::TDB ()
: mPendingFile ("")
, mCompletedFile ("")
, mId (1)
+, mNoLock (false)
{
}
@@ -289,11 +290,10 @@ bool TDB::modifyT (const T& t)
////////////////////////////////////////////////////////////////////////////////
bool TDB::lock (FILE* file) const
{
-#ifdef HAVE_FLOCK
+ if (mNoLock)
+ return true;
+
return flock (fileno (file), LOCK_EX) ? false : true;
-#else
- return true;
-#endif
}
////////////////////////////////////////////////////////////////////////////////
@@ -303,18 +303,16 @@ bool TDB::overwritePending (std::vector & all)
FILE* out;
if ((out = fopen (mPendingFile.c_str (), "w")))
{
-#ifdef HAVE_FLOCK
int retry = 0;
- while (flock (fileno (out), LOCK_EX) && ++retry <= 3)
- delay (0.25);
-#endif
+ if (!mNoLock)
+ while (flock (fileno (out), LOCK_EX) && ++retry <= 3)
+ delay (0.1);
std::vector ::iterator it;
for (it = all.begin (); it != all.end (); ++it)
fputs (it->compose ().c_str (), out);
fclose (out);
- dbChanged ();
return true;
}
@@ -328,16 +326,14 @@ bool TDB::writePending (const T& t)
FILE* out;
if ((out = fopen (mPendingFile.c_str (), "a")))
{
-#ifdef HAVE_FLOCK
int retry = 0;
- while (flock (fileno (out), LOCK_EX) && ++retry <= 3)
- delay (0.25);
-#endif
+ if (!mNoLock)
+ while (flock (fileno (out), LOCK_EX) && ++retry <= 3)
+ delay (0.1);
fputs (t.compose ().c_str (), out);
fclose (out);
- dbChanged ();
return true;
}
@@ -351,17 +347,14 @@ bool TDB::writeCompleted (const T& t)
FILE* out;
if ((out = fopen (mCompletedFile.c_str (), "a")))
{
-#ifdef HAVE_FLOCK
int retry = 0;
- while (flock (fileno (out), LOCK_EX) && ++retry <= 3)
- delay (0.25);
-#endif
+ if (!mNoLock)
+ while (flock (fileno (out), LOCK_EX) && ++retry <= 3)
+ delay (0.1);
fputs (t.compose ().c_str (), out);
fclose (out);
- // Note: No call to dbChanged here because this call never occurs by itself.
- // It is always accompanied by an overwritePending call.
return true;
}
@@ -380,11 +373,10 @@ bool TDB::readLockedFile (
FILE* in;
if ((in = fopen (file.c_str (), "r")))
{
-#ifdef HAVE_FLOCK
int retry = 0;
- while (flock (fileno (in), LOCK_EX) && ++retry <= 3)
- delay (0.25);
-#endif
+ if (!mNoLock)
+ while (flock (fileno (in), LOCK_EX) && ++retry <= 3)
+ delay (0.1);
char line[T_LINE_MAX];
while (fgets (line, T_LINE_MAX, in))
@@ -406,6 +398,8 @@ bool TDB::readLockedFile (
}
////////////////////////////////////////////////////////////////////////////////
+// Scans the pending tasks for any that are completed or deleted, and if so,
+// moves them to the completed.data file. Returns a count of tasks moved.
int TDB::gc ()
{
int count = 0;
@@ -423,7 +417,9 @@ int TDB::gc ()
// Some tasks stay in the pending file.
if (it->getStatus () == T::pending ||
it->getStatus () == T::recurring)
+ {
pending.push_back (*it);
+ }
// Others are transferred to the completed file.
else
@@ -448,19 +444,9 @@ int TDB::nextId ()
}
////////////////////////////////////////////////////////////////////////////////
-void TDB::onChange (void (*callback)())
+void TDB::noLock ()
{
- if (callback)
- mOnChange.push_back (callback);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Iterate over callbacks.
-void TDB::dbChanged ()
-{
- foreach (i, mOnChange)
- if (*i)
- (**i) ();
+ mNoLock = true;
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/TDB.h b/src/TDB.h
index d335e909b..49a68fa11 100644
--- a/src/TDB.h
+++ b/src/TDB.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -51,7 +51,7 @@ public:
int gc ();
int nextId ();
- void onChange (void (*)());
+ void noLock ();
private:
bool lock (FILE*) const;
@@ -59,13 +59,12 @@ private:
bool writePending (const T&);
bool writeCompleted (const T&);
bool readLockedFile (const std::string&, std::vector &) const;
- void dbChanged ();
private:
std::string mPendingFile;
std::string mCompletedFile;
int mId;
- std::vector mOnChange;
+ bool mNoLock;
};
#endif
diff --git a/src/Table.cpp b/src/Table.cpp
index 6de729b4b..6f20c1b50 100644
--- a/src/Table.cpp
+++ b/src/Table.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -736,34 +736,84 @@ int Table::columnCount ()
////////////////////////////////////////////////////////////////////////////////
// Removes extraneous output characters, such as:
-// - spaces followed by a newline is collapsed to just a newline, if there is
-// no Bg color.
// - removal of redundant color codes:
// ^[[31mName^[[0m ^[[31mValue^[[0m -> ^[[31mName Value^[[0m
//
// This method is a work in progress.
-void Table::optimize (std::string& output)
+void Table::optimize (std::string& output) const
{
-/*
- int start = output.length ();
-*/
-
- // \s\n -> \n
- size_t i = 0;
- while ((i = output.find (" \n")) != std::string::npos)
- {
- output = output.substr (0, i) +
- output.substr (i + 1, std::string::npos);
- }
+// int start = output.length ();
/*
- std::cout << int ((100 * (start - output.length ()) / start))
- << "%" << std::endl;
+ Well, how about that!
+
+ The benchmark.t unit test adds a 1000 tasks, fiddles with some of them, then
+ runs a series of reports. The results are timed, and look like this:
+
+ 1000 tasks added in 3 seconds
+ 600 tasks altered in 32 seconds
+ 'task ls' in 26 seconds
+ 'task list' in 17 seconds
+ 'task list pri:H' in 19 seconds
+ 'task list +tag' in 0 seconds
+ 'task list project_A' in 0 seconds
+ 'task long' in 29 seconds
+ 'task completed' in 2 seconds
+ 'task history' in 0 seconds
+ 'task ghistory' in 0 seconds
+
+ This performance is terrible. To identify the worst offender, Various Timer
+ objects were added in Table::render, assuming that table sorting is the major
+ bottleneck. But no, it is Table::optimize that is the problem. After
+ commenting out this method, the results are now:
+
+ 1000 tasks added in 3 seconds
+ 600 tasks altered in 29 seconds
+ 'task ls' in 0 seconds
+ 'task list' in 0 seconds
+ 'task list pri:H' in 1 seconds
+ 'task list +tag' in 0 seconds
+ 'task list project_A' in 0 seconds
+ 'task long' in 0 seconds
+ 'task completed' in 0 seconds
+ 'task history' in 0 seconds
+ 'task ghistory' in 0 seconds
+
+ Much better.
*/
+
+// std::cout << int ((100 * (start - output.length ()) / start))
+// << "%" << std::endl;
}
////////////////////////////////////////////////////////////////////////////////
// Combsort11, with O(n log n) average, O(n log n) worst case performance.
+//
+// function combsort11(array input)
+// gap := input.size
+//
+// loop until gap <= 1 and swaps = 0
+// if gap > 1
+// gap := gap / 1.3
+// if gap = 10 or gap = 9
+// gap := 11
+// end if
+// end if
+//
+// i := 0
+// swaps := 0
+//
+// loop until i + gap >= input.size
+// if input[i] > input[i+gap]
+// swap(input[i], input[i+gap])
+// swaps := 1
+// end if
+// i := i + 1
+// end loop
+//
+// end loop
+// end function
+
#define SWAP \
{ \
int temp = order[r]; \
@@ -776,7 +826,7 @@ void Table::sort (std::vector & order)
int gap = order.size ();
int swaps = 1;
- while (gap > 1 || swaps != 0)
+ while (gap > 1 || swaps > 0)
{
if (gap > 1)
{
@@ -797,10 +847,28 @@ void Table::sort (std::vector & order)
Grid::Cell* left = mData.byRow (order[r], mSortColumns[c]);
Grid::Cell* right = mData.byRow (order[r + gap], mSortColumns[c]);
- if (left == NULL && right != NULL)
- SWAP
- if (left && right && *left != *right)
+ // Data takes precedence over missing data.
+ if (left == NULL && right != NULL)
+ {
+ SWAP
+ break;
+ }
+
+ // No data - try comparing the next column.
+ else if (left == NULL && right == NULL)
+ {
+ keepScanning = true;
+ }
+
+ // Identical data - try comparing the next column.
+ else if (left && right && *left == *right)
+ {
+ keepScanning = true;
+ }
+
+ // Differing data - do a proper comparison.
+ else if (left && right && *left != *right)
{
switch (mSortOrder[mSortColumns[c]])
{
@@ -861,24 +929,38 @@ void Table::sort (std::vector & order)
break;
case ascendingPriority:
- if (((std::string)*left == "" && (std::string)*right != "") ||
- ((std::string)*left == "M" && (std::string)*right == "L") ||
- ((std::string)*left == "H" && ((std::string)*right == "L" || (std::string)*right == "M")))
+ if (((std::string)*left == "" && (std::string)*right != "") ||
+ ((std::string)*left == "M" && (std::string)*right == "L") ||
+ ((std::string)*left == "H" && ((std::string)*right == "L" || (std::string)*right == "M")))
SWAP
break;
case descendingPriority:
- if (((std::string)*left == "" && (std::string)*right != "") ||
+ if (((std::string)*left == "" && (std::string)*right != "") ||
((std::string)*left == "L" && ((std::string)*right == "M" || (std::string)*right == "H")) ||
- ((std::string)*left == "M" && (std::string)*right == "H"))
+ ((std::string)*left == "M" && (std::string)*right == "H"))
+ SWAP
+ break;
+
+ case ascendingPeriod:
+ if ((std::string)*left == "" && (std::string)*right != "")
+ break;
+ else if ((std::string)*left != "" && (std::string)*right == "")
+ SWAP
+ else if (convertDuration ((std::string)*left) > convertDuration ((std::string)*right))
+ SWAP
+ break;
+
+ case descendingPeriod:
+ if ((std::string)*left != "" && (std::string)*right == "")
+ break;
+ else if ((std::string)*left == "" && (std::string)*right != "")
+ SWAP
+ else if (convertDuration ((std::string)*left) < convertDuration ((std::string)*right))
SWAP
break;
}
-
- break;
}
- else
- keepScanning = true;
}
++r;
@@ -912,7 +994,7 @@ void Table::clean (std::string& value)
}
////////////////////////////////////////////////////////////////////////////////
-const std::string Table::render ()
+const std::string Table::render (int maximum /* = 0 */)
{
calculateColumnWidths ();
@@ -946,8 +1028,14 @@ const std::string Table::render ()
if (mSortColumns.size ())
sort (order);
+ // If a non-zero maximum is specified, then it limits the number of rows of
+ // the table that are rendered.
+ int limit = mRows;
+ if (maximum != 0)
+ limit = min (maximum, mRows);
+
// Print all rows.
- for (int row = 0; row < mRows; ++row)
+ for (int row = 0; row < limit; ++row)
{
std::vector > columns;
std::vector blanks;
@@ -981,15 +1069,19 @@ const std::string Table::render ()
else
output += blanks[col];
+ // Trim right.
+ output.erase (output.find_last_not_of (" ") + 1);
output += "\n";
}
}
else
+ {
+ // Trim right.
+ output.erase (output.find_last_not_of (" ") + 1);
output += "\n";
+ }
}
- // Eliminate redundant color codes.
- optimize (output);
return output;
}
diff --git a/src/Table.h b/src/Table.h
index 6a793af0c..aed5587e1 100644
--- a/src/Table.h
+++ b/src/Table.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -37,9 +37,16 @@ class Table
{
public:
enum just {left, center, right};
- enum order {ascendingNumeric, ascendingCharacter, ascendingPriority,
- ascendingDate, descendingNumeric, descendingCharacter,
- descendingPriority, descendingDate};
+ enum order {ascendingNumeric,
+ ascendingCharacter,
+ ascendingPriority,
+ ascendingDate,
+ ascendingPeriod,
+ descendingNumeric,
+ descendingCharacter,
+ descendingPriority,
+ descendingDate,
+ descendingPeriod};
enum sizing {minimum = -1, flexible = 0};
Table ();
@@ -84,7 +91,7 @@ public:
int rowCount ();
int columnCount ();
- const std::string render ();
+ const std::string render (int maximum = 0);
private:
std::string getCell (const int, const int);
@@ -101,9 +108,9 @@ private:
const std::string formatHeader (const int, const int, const int);
const std::string formatHeaderDashedUnderline (const int, const int, const int);
void formatCell (const int, const int, const int, const int, std::vector &, std::string&);
- void optimize (std::string&);
void sort (std::vector &);
void clean (std::string&);
+ void optimize (std::string&) const;
private:
std::vector mColumns;
diff --git a/src/Timer.cpp b/src/Timer.cpp
new file mode 100644
index 000000000..cbd9f1dc7
--- /dev/null
+++ b/src/Timer.cpp
@@ -0,0 +1,56 @@
+////////////////////////////////////////////////////////////////////////////////
+// task - a command line task list manager.
+//
+// Copyright 2006 - 2009, Paul Beckingham.
+// All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation; either version 2 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the
+//
+// Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor,
+// Boston, MA
+// 02110-1301
+// USA
+//
+////////////////////////////////////////////////////////////////////////////////
+#include
+#include
+#include
+
+////////////////////////////////////////////////////////////////////////////////
+// Timer starts when the object is constructed.
+Timer::Timer (const std::string& description)
+: mDescription (description)
+{
+ ::gettimeofday (&mStart, NULL);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Timer stops when the object is desctructed.
+Timer::~Timer ()
+{
+ struct timeval end;
+ ::gettimeofday (&end, NULL);
+
+ std::cout << "Timer "
+ << mDescription
+ << " "
+ << std::setprecision (6)
+ << ((end.tv_sec - mStart.tv_sec) +
+ ((end.tv_usec - mStart.tv_usec ) / 1000000.0))
+ << std::endl;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
diff --git a/src/Timer.h b/src/Timer.h
new file mode 100644
index 000000000..6b561aac2
--- /dev/null
+++ b/src/Timer.h
@@ -0,0 +1,46 @@
+////////////////////////////////////////////////////////////////////////////////
+// task - a command line task list manager.
+//
+// Copyright 2006 - 2009, Paul Beckingham.
+// All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation; either version 2 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the
+//
+// Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor,
+// Boston, MA
+// 02110-1301
+// USA
+//
+////////////////////////////////////////////////////////////////////////////////
+#ifndef INCLUDED_TIMER
+#define INCLUDED_TIMER
+
+#include
+#include
+
+class Timer
+{
+public:
+ Timer (const std::string&);
+ ~Timer ();
+
+private:
+ std::string mDescription;
+ struct timeval mStart;
+};
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/src/color.cpp b/src/color.cpp
index 9de9b7942..6231dfe1f 100644
--- a/src/color.cpp
+++ b/src/color.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/color.h b/src/color.h
index 7c48d8e6a..d90b47c8d 100644
--- a/src/color.h
+++ b/src/color.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
diff --git a/src/command.cpp b/src/command.cpp
index 5742a3797..72724f668 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -48,8 +48,10 @@
#endif
////////////////////////////////////////////////////////////////////////////////
-void handleAdd (TDB& tdb, T& task, Config& conf)
+std::string handleAdd (TDB& tdb, T& task, Config& conf)
{
+ std::stringstream out;
+
char entryTime[16];
sprintf (entryTime, "%u", (unsigned int) time (NULL));
task.setAttribute ("entry", entryTime);
@@ -86,6 +88,8 @@ void handleAdd (TDB& tdb, T& task, Config& conf)
if (!tdb.addT (task))
throw std::string ("Could not create new task.");
+
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
@@ -113,7 +117,7 @@ std::string handleProjects (TDB& tdb, T& task, Config& conf)
table.addColumn ("Project");
table.addColumn ("Tasks");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -307,7 +311,9 @@ std::string handleVersion (Config& conf)
link.setColumnWidth (0, Table::flexible);
link.setColumnJustification (0, Table::left);
link.addCell (link.addRow (), 0,
- "See http://www.beckingham.net/task.html for the latest releases and a full tutorial.");
+ "See http://www.beckingham.net/task.html for the latest releases and a "
+ "full tutorial. New releases containing fixes and enhancements are "
+ "made frequently.");
// Create a table for output.
Table table;
@@ -316,7 +322,7 @@ std::string handleVersion (Config& conf)
table.addColumn ("Config variable");
table.addColumn ("Value");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -343,11 +349,15 @@ std::string handleVersion (Config& conf)
}
}
- out << "Copyright (C) 2006 - 2008, P. Beckingham."
+ out << "Copyright (C) 2006 - 2009, P. Beckingham."
<< std::endl
- << (conf.get ("color", true) ? Text::colorize (Text::bold, Text::nocolor, PACKAGE) : PACKAGE)
+ << ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
+ ? Text::colorize (Text::bold, Text::nocolor, PACKAGE)
+ : PACKAGE)
<< " "
- << (conf.get ("color", true) ? Text::colorize (Text::bold, Text::nocolor, VERSION) : VERSION)
+ << ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
+ ? Text::colorize (Text::bold, Text::nocolor, VERSION)
+ : VERSION)
<< std::endl
<< disclaimer.render ()
<< std::endl
@@ -355,6 +365,48 @@ std::string handleVersion (Config& conf)
<< link.render ()
<< std::endl;
+ // Complain about configuration variables that are not recognized.
+ // These are the regular configuration variables.
+ std::string recognized =
+ "blanklines color color.active color.due color.overdue color.pri.H "
+ "color.pri.L color.pri.M color.pri.none color.recurring color.tagged "
+ "confirmation curses data.location dateformat default.command "
+ "default.priority defaultwidth due locking monthsperline nag next project "
+ "shadow.command shadow.file shadow.notify";
+
+ // This configuration variable is supported, but not documented. It exists
+ // so that unit tests can force color to be on even when the output from task
+ // is redirected to a file, or stdout is not a tty.
+ recognized += " _forcecolor";
+
+ std::vector unrecognized;
+ foreach (i, all)
+ {
+ if (recognized.find (*i) == std::string::npos)
+ {
+ // These are special configuration variables, because their name is
+ // dynamic.
+ if (i->find ("color.keyword.") == std::string::npos &&
+ i->find ("color.project.") == std::string::npos &&
+ i->find ("color.tag.") == std::string::npos &&
+ i->find ("report.") == std::string::npos)
+ {
+ unrecognized.push_back (*i);
+ }
+ }
+ }
+
+ if (unrecognized.size ())
+ {
+ out << "Your .taskrc file contains these unrecognized variables:"
+ << std::endl;
+
+ foreach (i, unrecognized)
+ out << " " << *i << std::endl;
+
+ out << std::endl;
+ }
+
// Verify installation. This is mentioned in the documentation as the way to
// ensure everything is properly installed.
@@ -465,8 +517,44 @@ std::string handleStart (TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
-void handleDone (TDB& tdb, T& task, Config& conf)
+std::string handleStop (TDB& tdb, T& task, Config& conf)
{
+ std::vector all;
+ tdb.pendingT (all);
+
+ std::vector ::iterator it;
+ for (it = all.begin (); it != all.end (); ++it)
+ {
+ if (it->getId () == task.getId ())
+ {
+ T original (*it);
+
+ if (original.getAttribute ("start") != "")
+ {
+ original.removeAttribute ("start");
+ original.setId (task.getId ());
+ tdb.modifyT (original);
+
+ return std::string ("");
+ }
+ else
+ {
+ std::stringstream out;
+ out << "Task " << task.getId () << " not started." << std::endl;
+ return out.str ();
+ }
+ }
+ }
+
+ throw std::string ("Task not found.");
+ return std::string (""); // To satisfy gcc.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string handleDone (TDB& tdb, T& task, Config& conf)
+{
+ std::stringstream out;
+
if (!tdb.completeT (task))
throw std::string ("Could not mark task as completed.");
@@ -484,11 +572,14 @@ void handleDone (TDB& tdb, T& task, Config& conf)
}
nag (tdb, task, conf);
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
-void handleExport (TDB& tdb, T& task, Config& conf)
+std::string handleExport (TDB& tdb, T& task, Config& conf)
{
+ std::stringstream output;
+
// Use the description as a file name, then clobber the description so the
// file name isn't used for filtering.
std::string file = trim (task.getDescription ());
@@ -500,11 +591,13 @@ void handleExport (TDB& tdb, T& task, Config& conf)
if (out.good ())
{
out << "'id',"
+ << "'uuid',"
<< "'status',"
<< "'tags',"
<< "'entry',"
<< "'start',"
<< "'due',"
+ << "'recur',"
<< "'end',"
<< "'project',"
<< "'priority',"
@@ -513,25 +606,36 @@ void handleExport (TDB& tdb, T& task, Config& conf)
<< "'description'"
<< "\n";
+ int count = 0;
std::vector all;
- tdb.allT (all);
+ tdb.allPendingT (all);
filter (all, task);
foreach (t, all)
{
- out << t->composeCSV ().c_str ();
+ if (t->getStatus () != T::recurring &&
+ t->getStatus () != T::deleted)
+ {
+ out << t->composeCSV ().c_str ();
+ ++count;
+ }
}
out.close ();
+
+ output << count << " tasks exported to '" << file << "'" << std::endl;
}
else
throw std::string ("Could not write to export file.");
}
else
throw std::string ("You must specify a file to write to.");
+
+ return output.str ();
}
////////////////////////////////////////////////////////////////////////////////
-void handleModify (TDB& tdb, T& task, Config& conf)
+std::string handleModify (TDB& tdb, T& task, Config& conf)
{
+ std::stringstream out;
std::vector all;
tdb.pendingT (all);
@@ -612,11 +716,12 @@ void handleModify (TDB& tdb, T& task, Config& conf)
tdb.modifyT (original);
}
- return;
+ return out.str ();
}
}
throw std::string ("Task not found.");
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
@@ -624,7 +729,7 @@ std::string handleColor (Config& conf)
{
std::stringstream out;
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
out << optionalBlankLine (conf) << "Foreground" << std::endl
<< " "
diff --git a/src/parse.cpp b/src/parse.cpp
index 776480eed..a8e41a914 100644
--- a/src/parse.cpp
+++ b/src/parse.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -35,6 +35,8 @@
#include "T.h"
////////////////////////////////////////////////////////////////////////////////
+// NOTE: These are static arrays only because there is no initializer list for
+// std::vector.
static const char* colors[] =
{
"bold",
@@ -128,16 +130,12 @@ static const char* commands[] =
"history",
"ghistory",
"info",
- "list",
- "long",
- "ls",
- "newest",
"next",
- "oldest",
"overdue",
"projects",
"start",
"stats",
+ "stop",
"summary",
"tags",
"undelete",
@@ -146,6 +144,9 @@ static const char* commands[] =
"",
};
+static std::vector customReports;
+
+////////////////////////////////////////////////////////////////////////////////
void guess (const std::string& type, const char** list, std::string& candidate)
{
std::vector options;
@@ -157,6 +158,35 @@ void guess (const std::string& type, const char** list, std::string& candidate)
if (1 == matches.size ())
candidate = matches[0];
+ else if (0 == matches.size ())
+ candidate = "";
+
+ else
+ {
+ std::string error = "Ambiguous ";
+ error += type;
+ error += " '";
+ error += candidate;
+ error += "' - could be either of ";
+ for (size_t i = 0; i < matches.size (); ++i)
+ {
+ if (i)
+ error += ", ";
+ error += matches[i];
+ }
+
+ throw error;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void guess (const std::string& type, std::vector& options, std::string& candidate)
+{
+ std::vector matches;
+ autoComplete (candidate, options, matches);
+ if (1 == matches.size ())
+ candidate = matches[0];
+
else if (0 == matches.size ())
// throw std::string ("Unrecognized ") + type + " '" + candidate + "'";
candidate = "";
@@ -189,7 +219,11 @@ static bool isCommand (const std::string& candidate)
std::vector matches;
autoComplete (candidate, options, matches);
if (0 == matches.size ())
- return false;
+ {
+ autoComplete (candidate, customReports, matches);
+ if (0 == matches.size ())
+ return false;
+ }
return true;
}
@@ -283,15 +317,6 @@ static bool validTag (const std::string& input)
////////////////////////////////////////////////////////////////////////////////
static bool validDescription (const std::string& input)
{
-/*
- if (input.length () > 0 &&
- input.find ("\r") == std::string::npos &&
- input.find ("\f") == std::string::npos &&
- input.find ("\n") == std::string::npos)
- return true;
-
- return false;
-*/
if (input.length () == 0) return false;
if (input.find ("\r") != std::string::npos) return false;
if (input.find ("\f") != std::string::npos) return false;
@@ -306,7 +331,12 @@ static bool validCommand (std::string& input)
std::string copy = input;
guess ("command", commands, copy);
if (copy == "")
- return false;
+ {
+ copy = input;
+ guess ("command", customReports, copy);
+ if (copy == "")
+ return false;
+ }
input = copy;
return true;
@@ -427,12 +457,20 @@ void parse (
if (isCommand (l) && validCommand (l))
command = l;
else
- descCandidate += arg;
+ {
+ if (descCandidate.length ())
+ descCandidate += " ";
+ descCandidate += std::string (arg);
+ }
}
// Anything else is just considered description.
else
- descCandidate += std::string (arg) + " ";
+ {
+ if (descCandidate.length ())
+ descCandidate += " ";
+ descCandidate += std::string (arg);
+ }
}
}
@@ -449,4 +487,40 @@ void parse (
}
////////////////////////////////////////////////////////////////////////////////
+void loadCustomReports (Config& conf)
+{
+ std::vector all;
+ conf.all (all);
+
+ foreach (i, all)
+ {
+ if (i->substr (0, 7) == "report.")
+ {
+ std::string report = i->substr (7, std::string::npos);
+ std::string::size_type columns = report.find (".columns");
+ if (columns != std::string::npos)
+ {
+ report = report.substr (0, columns);
+ customReports.push_back (report);
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool isCustomReport (const std::string& report)
+{
+ foreach (i, customReports)
+ if (*i == report)
+ return true;
+
+ return false;
+}
+////////////////////////////////////////////////////////////////////////////////
+void allCustomReports (std::vector & all)
+{
+ all = customReports;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/src/report.cpp b/src/report.cpp
index e38b39b08..44752327a 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -86,9 +86,40 @@ void filter (std::vector& all, T& task)
if (a->second.length () <= refTask.getAttribute (a->first).length ())
if (a->second == refTask.getAttribute (a->first).substr (0, a->second.length ()))
++matches;
+/*
+ TODO Attempt at allowing "pri:!H", thwarted by a lack of coffee and the
+ validation of "!H" as a priority value. To be revisited soon.
+ {
+ if (a->second[0] == '!') // Inverted search.
+ {
+ if (a->second.substr (1, std::string::npos) != refTask.getAttribute (a->first).substr (0, a->second.length ()))
+ ++matches;
+ }
+ else
+ {
+ if (a->second == refTask.getAttribute (a->first).substr (0, a->second.length ()))
+ ++matches;
+ }
+ }
+*/
}
else if (a->second == refTask.getAttribute (a->first))
++matches;
+/*
+ else
+ {
+ if (a->second[0] == '!') // Inverted search.
+ {
+ if (a->second.substr (1, std::string::npos) != refTask.getAttribute (a->first))
+ ++matches;
+ }
+ else
+ {
+ if (a->second == refTask.getAttribute (a->first))
+ ++matches;
+ }
+ }
+*/
}
if (matches == attrList.size ())
@@ -108,287 +139,6 @@ void filter (std::vector& all, T& task)
all = filtered;
}
-////////////////////////////////////////////////////////////////////////////////
-// Successively apply filters based on the task object built from the command
-// line. Tasks that match all the specified criteria are listed.
-std::string handleList (TDB& tdb, T& task, Config& conf)
-{
- std::stringstream out;
-
- // Determine window size, and set table accordingly.
- int width = conf.get ("defaultwidth", 80);
-#ifdef HAVE_LIBNCURSES
- if (conf.get ("curses", true))
- {
- WINDOW* w = initscr ();
- width = w->_maxx + 1;
- endwin ();
- }
-#endif
-
- // Get the pending tasks.
- std::vector tasks;
- tdb.allPendingT (tasks);
- handleRecurrence (tdb, tasks);
- filter (tasks, task);
-
- initializeColorRules (conf);
-
- bool showAge = conf.get ("showage", true);
-
- // Create a table for output.
- Table table;
- table.setTableWidth (width);
- table.addColumn ("ID");
- table.addColumn ("Project");
- table.addColumn ("Pri");
- table.addColumn ("Due");
- table.addColumn ("Active");
- if (showAge) table.addColumn ("Age");
- table.addColumn ("Description");
-
- if (conf.get (std::string ("color"), true))
- {
- table.setColumnUnderline (0);
- table.setColumnUnderline (1);
- table.setColumnUnderline (2);
- table.setColumnUnderline (3);
- table.setColumnUnderline (4);
- table.setColumnUnderline (5);
- if (showAge) table.setColumnUnderline (6);
- }
- else
- table.setTableDashedUnderline ();
-
- table.setColumnWidth (0, Table::minimum);
- table.setColumnWidth (1, Table::minimum);
- table.setColumnWidth (2, Table::minimum);
- table.setColumnWidth (3, Table::minimum);
- table.setColumnWidth (4, Table::minimum);
- if (showAge) table.setColumnWidth (5, Table::minimum);
- table.setColumnWidth ((showAge ? 6 : 5), Table::flexible);
-
- table.setColumnJustification (0, Table::right);
- table.setColumnJustification (3, Table::right);
- if (showAge) table.setColumnJustification (5, Table::right);
-
- table.sortOn (3, Table::ascendingDate);
- table.sortOn (2, Table::descendingPriority);
- table.sortOn (1, Table::ascendingCharacter);
-
- table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
-
- for (unsigned int i = 0; i < tasks.size (); ++i)
- {
- T refTask (tasks[i]);
- if (refTask.getStatus () != T::pending)
- continue;
-
- // Now format the matching task.
- bool imminent = false;
- bool overdue = false;
- std::string due = refTask.getAttribute ("due");
- if (due.length ())
- {
- switch (getDueState (due))
- {
- case 2: overdue = true; break;
- case 1: imminent = true; break;
- case 0:
- default: break;
- }
-
- Date dt (::atoi (due.c_str ()));
- due = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- std::string active;
- if (refTask.getAttribute ("start") != "")
- active = "*";
-
- std::string age;
- std::string created = refTask.getAttribute ("entry");
- if (created.length ())
- {
- Date now;
- Date dt (::atoi (created.c_str ()));
- formatTimeDeltaDays (age, (time_t) (now - dt));
- }
-
- // All criteria match, so add refTask to the output table.
- int row = table.addRow ();
- table.addCell (row, 0, refTask.getId ());
- table.addCell (row, 1, refTask.getAttribute ("project"));
- table.addCell (row, 2, refTask.getAttribute ("priority"));
- table.addCell (row, 3, due);
- table.addCell (row, 4, active);
- if (showAge) table.addCell (row, 5, age);
- table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ());
-
- if (conf.get ("color", true))
- {
- Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
- Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
- table.setRowFg (row, fg);
- table.setRowBg (row, bg);
-
- if (fg == Text::nocolor)
- {
- if (overdue)
- table.setCellFg (row, 3, Text::red);
- else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
- }
- }
- }
-
- if (table.rowCount ())
- out << optionalBlankLine (conf)
- << table.render ()
- << optionalBlankLine (conf)
- << table.rowCount ()
- << (table.rowCount () == 1 ? " task" : " tasks")
- << std::endl;
- else
- out << "No matches."
- << std::endl;
-
- return out.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Successively apply filters based on the task object built from the command
-// line. Tasks that match all the specified criteria are listed. Show a narrow
-// list that works better on mobile devices.
-std::string handleSmallList (TDB& tdb, T& task, Config& conf)
-{
- std::stringstream out;
-
- // Determine window size, and set table accordingly.
- int width = conf.get ("defaultwidth", 80);
-#ifdef HAVE_LIBNCURSES
- if (conf.get ("curses", true))
- {
- WINDOW* w = initscr ();
- width = w->_maxx + 1;
- endwin ();
- }
-#endif
-
- // Get the pending tasks.
- std::vector tasks;
- tdb.allPendingT (tasks);
- handleRecurrence (tdb, tasks);
- filter (tasks, task);
-
- initializeColorRules (conf);
-
- // Create a table for output.
- Table table;
- table.setTableWidth (width);
- table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
- table.addColumn ("ID");
- table.addColumn ("Project");
- table.addColumn ("Pri");
- table.addColumn ("Description");
-
- if (conf.get ("color", true))
- {
- table.setColumnUnderline (0);
- table.setColumnUnderline (1);
- table.setColumnUnderline (2);
- table.setColumnUnderline (3);
- }
- else
- table.setTableDashedUnderline ();
-
- table.setColumnWidth (0, Table::minimum);
- table.setColumnWidth (1, Table::minimum);
- table.setColumnWidth (2, Table::minimum);
- table.setColumnWidth (3, Table::flexible);
-
- table.setColumnJustification (0, Table::right);
- table.setColumnJustification (3, Table::left);
-
- table.sortOn (2, Table::descendingPriority);
- table.sortOn (1, Table::ascendingCharacter);
-
- // Iterate over each task, and apply selection criteria.
- for (unsigned int i = 0; i < tasks.size (); ++i)
- {
- T refTask (tasks[i]);
-
- // Now format the matching task.
- bool imminent = false;
- bool overdue = false;
- std::string due = refTask.getAttribute ("due");
- if (due.length ())
- {
- switch (getDueState (due))
- {
- case 2: overdue = true; break;
- case 1: imminent = true; break;
- case 0:
- default: break;
- }
-
- Date dt (::atoi (due.c_str ()));
- due = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- std::string active;
- if (refTask.getAttribute ("start") != "")
- active = "*";
-
- std::string age;
- std::string created = refTask.getAttribute ("entry");
- if (created.length ())
- {
- Date now;
- Date dt (::atoi (created.c_str ()));
- formatTimeDeltaDays (age, (time_t) (now - dt));
- }
-
- // All criteria match, so add refTask to the output table.
- int row = table.addRow ();
- table.addCell (row, 0, refTask.getId ());
- table.addCell (row, 1, refTask.getAttribute ("project"));
- table.addCell (row, 2, refTask.getAttribute ("priority"));
- table.addCell (row, 3, refTask.getDescription ());
-
- if (conf.get ("color", true))
- {
- Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
- Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
- table.setRowFg (row, fg);
- table.setRowBg (row, bg);
-
- if (fg == Text::nocolor)
- {
- if (overdue)
- table.setCellFg (row, 3, Text::red);
- else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
- }
- }
- }
-
- if (table.rowCount ())
- out << optionalBlankLine (conf)
- << table.render ()
- << optionalBlankLine (conf)
- << table.rowCount ()
- << (table.rowCount () == 1 ? " task" : " tasks")
- << std::endl;
- else
- out << "No matches."
- << std::endl;
-
- return out.str ();
-}
-
////////////////////////////////////////////////////////////////////////////////
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed.
@@ -422,7 +172,7 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf)
table.addColumn ("Project");
table.addColumn ("Description");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -439,7 +189,10 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf)
table.setColumnJustification (1, Table::left);
table.setColumnJustification (2, Table::left);
- table.sortOn (0, Table::ascendingDate);
+ // Note: There is deliberately no sorting. The original sorting was on the
+ // end date. Tasks are appended to completed.data naturally sorted by
+ // the end date, so that sequence is assumed to remain unchanged, and
+ // relied upon here.
// Iterate over each task, and apply selection criteria.
for (unsigned int i = 0; i < tasks.size (); ++i)
@@ -456,11 +209,11 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf)
table.addCell (row, 1, refTask.getAttribute ("project"));
table.addCell (row, 2, refTask.getDescription ());
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
+ autoColorize (refTask, fg, bg, conf);
table.setRowFg (row, fg);
table.setRowBg (row, bg);
}
@@ -508,7 +261,7 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
table.addColumn ("Name");
table.addColumn ("Value");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -606,12 +359,12 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
Date nextweek = now + 7 * 86400;
imminent = dt < nextweek ? true : false;
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
if (overdue)
- table.setCellFg (row, 1, Text::red);
+ table.setCellFg (row, 1, Text::colorCode (conf.get ("color.overdue", "red")));
else if (imminent)
- table.setCellFg (row, 1, Text::yellow);
+ table.setCellFg (row, 1, Text::colorCode (conf.get ("color.due", "yellow")));
}
}
}
@@ -679,206 +432,23 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
-// Successively apply filters based on the task object built from the command
-// line. Tasks that match all the specified criteria are listed.
-std::string handleLongList (TDB& tdb, T& task, Config& conf)
-{
- std::stringstream out;
-
- // Determine window size, and set table accordingly.
- int width = conf.get ("defaultwidth", 80);
-#ifdef HAVE_LIBNCURSES
- if (conf.get ("curses", true))
- {
- WINDOW* w = initscr ();
- width = w->_maxx + 1;
- endwin ();
- }
-#endif
-
- // Get all the tasks.
- std::vector tasks;
- tdb.allPendingT (tasks);
- handleRecurrence (tdb, tasks);
- filter (tasks, task);
-
- initializeColorRules (conf);
-
- bool showAge = conf.get ("showage", true);
-
- // Create a table for output.
- Table table;
- table.setTableWidth (width);
- table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
- table.addColumn ("ID");
- table.addColumn ("Project");
- table.addColumn ("Pri");
- table.addColumn ("Entry");
- table.addColumn ("Start");
- table.addColumn ("Due");
- if (showAge) table.addColumn ("Age");
- table.addColumn ("Tags");
- table.addColumn ("Description");
-
- if (conf.get ("color", true))
- {
- table.setColumnUnderline (0);
- table.setColumnUnderline (1);
- table.setColumnUnderline (2);
- table.setColumnUnderline (3);
- table.setColumnUnderline (4);
- table.setColumnUnderline (5);
- table.setColumnUnderline (6);
- table.setColumnUnderline (7);
- if (showAge) table.setColumnUnderline (8);
- }
- else
- table.setTableDashedUnderline ();
-
- table.setColumnWidth (0, Table::minimum);
- table.setColumnWidth (1, Table::minimum);
- table.setColumnWidth (2, Table::minimum);
- table.setColumnWidth (3, Table::minimum);
- table.setColumnWidth (4, Table::minimum);
- table.setColumnWidth (5, Table::minimum);
- if (showAge) table.setColumnWidth (6, Table::minimum);
- table.setColumnWidth ((showAge ? 7 : 6), Table::minimum);
- table.setColumnWidth ((showAge ? 8 : 7), Table::flexible);
-
- table.setColumnJustification (0, Table::right);
- table.setColumnJustification (3, Table::right);
- table.setColumnJustification (4, Table::right);
- table.setColumnJustification (5, Table::right);
- if (showAge) table.setColumnJustification (6, Table::right);
-
- table.sortOn (5, Table::ascendingDate);
- table.sortOn (2, Table::descendingPriority);
- table.sortOn (1, Table::ascendingCharacter);
-
- // Iterate over each task, and apply selection criteria.
- for (unsigned int i = 0; i < tasks.size (); ++i)
- {
- T refTask (tasks[i]);
-
- Date now;
-
- std::string started = refTask.getAttribute ("start");
- if (started.length ())
- {
- Date dt (::atoi (started.c_str ()));
- started = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- std::string entered = refTask.getAttribute ("entry");
- if (entered.length ())
- {
- Date dt (::atoi (entered.c_str ()));
- entered = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- // Now format the matching task.
- bool imminent = false;
- bool overdue = false;
- std::string due = refTask.getAttribute ("due");
- if (due.length ())
- {
- switch (getDueState (due))
- {
- case 2: overdue = true; break;
- case 1: imminent = true; break;
- case 0:
- default: break;
- }
-
- Date dt (::atoi (due.c_str ()));
- due = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- std::string age;
- std::string created = refTask.getAttribute ("entry");
- if (created.length ())
- {
- Date dt (::atoi (created.c_str ()));
- formatTimeDeltaDays (age, (time_t) (now - dt));
- }
-
- // Make a list of tags.
- std::string tags;
- std::vector all;
- refTask.getTags (all);
- join (tags, " ", all);
-
- // All criteria match, so add refTask to the output table.
- int row = table.addRow ();
- table.addCell (row, 0, refTask.getId ());
- table.addCell (row, 1, refTask.getAttribute ("project"));
- table.addCell (row, 2, refTask.getAttribute ("priority"));
- table.addCell (row, 3, entered);
- table.addCell (row, 4, started);
- table.addCell (row, 5, due);
- if (showAge) table.addCell (row, 6, age);
- table.addCell (row, (showAge ? 7 : 6), tags);
- table.addCell (row, (showAge ? 8 : 7), refTask.getDescription ());
-
- if (conf.get ("color", true))
- {
- Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
- Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
- table.setRowFg (row, fg);
- table.setRowBg (row, bg);
-
- if (fg == Text::nocolor)
- {
- if (overdue)
- table.setCellFg (row, 3, Text::red);
- else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
- }
- }
- }
-
- if (table.rowCount ())
- out << optionalBlankLine (conf)
- << table.render ()
- << optionalBlankLine (conf)
- << table.rowCount ()
- << (table.rowCount () == 1 ? " task" : " tasks")
- << std::endl;
- else
- out << "No matches." << std::endl;
-
- return out.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Project Tasks Avg Age Status
-// A 12 13d XXXXXXXX------
-// B 109 3d 12h XX------------
+// Project Remaining Avg Age Complete 0% 100%
+// A 12 13d 55% XXXXXXXXXXXXX-----------
+// B 109 3d 12h 10% XXX---------------------
std::string handleReportSummary (TDB& tdb, T& task, Config& conf)
{
std::stringstream out;
- // Generate unique list of project names.
- std::map allProjects;
- std::vector pending;
- tdb.allPendingT (pending);
- handleRecurrence (tdb, pending);
- filter (pending, task);
- for (unsigned int i = 0; i < pending.size (); ++i)
- {
- T task (pending[i]);
- allProjects[task.getAttribute ("project")] = false;
- }
+ std::vector tasks;
+ tdb.allT (tasks);
+ handleRecurrence (tdb, tasks);
+ filter (tasks, task);
- std::vector completed;
- tdb.allCompletedT (completed);
- filter (completed, task);
- for (unsigned int i = 0; i < completed.size (); ++i)
- {
- T task (completed[i]);
- allProjects[task.getAttribute ("project")] = false;
- }
+ // Generate unique list of project names from all pending tasks.
+ std::map allProjects;
+ foreach (t, tasks)
+ if (t->getStatus () == T::pending)
+ allProjects[t->getAttribute ("project")] = false;
// Initialize counts, sum.
std::map countPending;
@@ -887,6 +457,7 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf)
std::map counter;
time_t now = time (NULL);
+ // Initialize counters.
foreach (i, allProjects)
{
countPending [i->first] = 0;
@@ -895,34 +466,30 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf)
counter [i->first] = 0;
}
- // Count the pending tasks.
- for (unsigned int i = 0; i < pending.size (); ++i)
+ // Count the various tasks.
+ foreach (t, tasks)
{
- T task (pending[i]);
- std::string project = task.getAttribute ("project");
- if (task.getStatus () == T::pending)
- ++countPending[project];
-
- time_t entry = ::atoi (task.getAttribute ("entry").c_str ());
- if (entry)
- {
- sumEntry[project] = sumEntry[project] + (double) (now - entry);
- ++counter[project];
- }
- }
-
- // Count the completed tasks.
- for (unsigned int i = 0; i < completed.size (); ++i)
- {
- T task (completed[i]);
- std::string project = task.getAttribute ("project");
- countCompleted[project] = countCompleted[project] + 1;
+ std::string project = t->getAttribute ("project");
++counter[project];
- time_t entry = ::atoi (task.getAttribute ("entry").c_str ());
- time_t end = ::atoi (task.getAttribute ("end").c_str ());
- if (entry && end)
- sumEntry[project] = sumEntry[project] + (double) (end - entry);
+ if (t->getStatus () == T::pending)
+ {
+ ++countPending[project];
+
+ time_t entry = ::atoi (t->getAttribute ("entry").c_str ());
+ if (entry)
+ sumEntry[project] = sumEntry[project] + (double) (now - entry);
+ }
+
+ else if (t->getStatus () == T::completed)
+ {
+ ++countCompleted[project];
+
+ time_t entry = ::atoi (t->getAttribute ("entry").c_str ());
+ time_t end = ::atoi (task.getAttribute ("end").c_str ());
+ if (entry && end)
+ sumEntry[project] = sumEntry[project] + (double) (end - entry);
+ }
}
// Create a table for output.
@@ -933,7 +500,7 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf)
table.addColumn ("Complete");
table.addColumn ("0% 100%");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -970,7 +537,7 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf)
int completedBar = (c * barWidth) / (c + p);
std::string bar;
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
bar = "\033[42m";
for (int b = 0; b < completedBar; ++b)
@@ -1062,8 +629,6 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf)
initializeColorRules (conf);
- bool showAge = conf.get ("showage", true);
-
// Create a table for output.
Table table;
table.setTableWidth (width);
@@ -1073,10 +638,10 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf)
table.addColumn ("Pri");
table.addColumn ("Due");
table.addColumn ("Active");
- if (showAge) table.addColumn ("Age");
+ table.addColumn ("Age");
table.addColumn ("Description");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -1084,7 +649,7 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf)
table.setColumnUnderline (3);
table.setColumnUnderline (4);
table.setColumnUnderline (5);
- if (showAge) table.setColumnUnderline (6);
+ table.setColumnUnderline (6);
}
else
table.setTableDashedUnderline ();
@@ -1094,12 +659,12 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf)
table.setColumnWidth (2, Table::minimum);
table.setColumnWidth (3, Table::minimum);
table.setColumnWidth (4, Table::minimum);
- if (showAge) table.setColumnWidth (5, Table::minimum);
- table.setColumnWidth ((showAge ? 6 : 5), Table::flexible);
+ table.setColumnWidth (5, Table::minimum);
+ table.setColumnWidth (6, Table::flexible);
table.setColumnJustification (0, Table::right);
table.setColumnJustification (3, Table::right);
- if (showAge) table.setColumnJustification (5, Table::right);
+ table.setColumnJustification (5, Table::right);
table.sortOn (3, Table::ascendingDate);
table.sortOn (2, Table::descendingPriority);
@@ -1148,23 +713,23 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf)
table.addCell (row, 2, refTask.getAttribute ("priority"));
table.addCell (row, 3, due);
table.addCell (row, 4, active);
- if (showAge) table.addCell (row, 5, age);
- table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ());
+ table.addCell (row, 5, age);
+ table.addCell (row, 6, refTask.getDescription ());
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
+ autoColorize (refTask, fg, bg, conf);
table.setRowFg (row, fg);
table.setRowBg (row, bg);
if (fg == Text::nocolor)
{
if (overdue)
- table.setCellFg (row, 3, Text::red);
+ table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red")));
else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
+ table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow")));
}
}
}
@@ -1235,6 +800,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
if (task.getStatus () == T::deleted)
{
epoch = monthlyEpoch (task.getAttribute ("end"));
+ groups[epoch] = 0;
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
@@ -1244,6 +810,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
else if (task.getStatus () == T::completed)
{
epoch = monthlyEpoch (task.getAttribute ("end"));
+ groups[epoch] = 0;
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
@@ -1274,6 +841,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
if (task.getStatus () == T::deleted)
{
epoch = monthlyEpoch (task.getAttribute ("end"));
+ groups[epoch] = 0;
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
@@ -1283,6 +851,8 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
else if (task.getStatus () == T::completed)
{
epoch = monthlyEpoch (task.getAttribute ("end"));
+ groups[epoch] = 0;
+
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
@@ -1301,7 +871,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
table.addColumn ("Deleted");
table.addColumn ("Net");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -1328,9 +898,9 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
{
row = table.addRow ();
- totalAdded += addedGroup[i->first];
- totalCompleted += completedGroup[i->first];
- totalDeleted += deletedGroup[i->first];
+ totalAdded += addedGroup [i->first];
+ totalCompleted += completedGroup [i->first];
+ totalDeleted += deletedGroup [i->first];
Date dt (i->first);
int m, d, y;
@@ -1364,7 +934,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
}
table.addCell (row, 5, net);
- if (conf.get ("color", true) && net)
+ if ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) && net)
table.setCellFg (row, 5, net > 0 ? Text::red: Text::green);
}
@@ -1374,7 +944,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf)
row = table.addRow ();
table.addCell (row, 1, "Average");
- if (conf.get ("color", true)) table.setRowFg (row, Text::bold);
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) table.setRowFg (row, Text::bold);
table.addCell (row, 2, totalAdded / (table.rowCount () - 2));
table.addCell (row, 3, totalCompleted / (table.rowCount () - 2));
table.addCell (row, 4, totalDeleted / (table.rowCount () - 2));
@@ -1406,7 +976,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
endwin ();
}
#endif
- int widthOfBar = width - 15; // strlen ("2008 September ")
+ int widthOfBar = width - 15; // 15 == strlen ("2008 September ")
std::map groups;
std::map addedGroup;
@@ -1495,9 +1065,9 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
table.addColumn ("Year");
table.addColumn ("Month");
- table.addColumn ("Added/Completed/Deleted");
+ table.addColumn ("Number Added/Completed/Deleted");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -1505,18 +1075,24 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
else
table.setTableDashedUnderline ();
- // Determine the longest line.
- int maxLine = 0;
+ // Determine the longest line, and the longest "added" line.
+ int maxAddedLine = 0;
+ int maxRemovedLine = 0;
foreach (i, groups)
{
- int line = addedGroup[i->first] + completedGroup[i->first] + deletedGroup[i->first];
+ if (completedGroup[i->first] + deletedGroup[i->first] > maxRemovedLine)
+ maxRemovedLine = completedGroup[i->first] + deletedGroup[i->first];
- if (line > maxLine)
- maxLine = line;
+ if (addedGroup[i->first] > maxAddedLine)
+ maxAddedLine = addedGroup[i->first];
}
+ int maxLine = maxAddedLine + maxRemovedLine;
+
if (maxLine > 0)
{
+ unsigned int leftOffset = (widthOfBar * maxAddedLine) / maxLine;
+
int totalAdded = 0;
int totalCompleted = 0;
int totalDeleted = 0;
@@ -1546,8 +1122,8 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine;
unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine;
- std::string bar;
- if (conf.get ("color", true))
+ std::string bar = "";
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
char number[24];
std::string aBar = "";
@@ -1577,9 +1153,12 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
dBar = " " + dBar;
}
- bar = Text::colorize (Text::black, Text::on_red, aBar);
- bar += Text::colorize (Text::black, Text::on_green, cBar);
- bar += Text::colorize (Text::black, Text::on_yellow, dBar);
+ while (bar.length () < leftOffset - aBar.length ())
+ bar += " ";
+
+ bar += Text::colorize (Text::black, Text::on_red, aBar);
+ bar += Text::colorize (Text::black, Text::on_green, cBar);
+ bar += Text::colorize (Text::black, Text::on_yellow, dBar);
}
else
{
@@ -1587,7 +1166,10 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
std::string cBar = ""; while (cBar.length () < completedBar) cBar += "X";
std::string dBar = ""; while (dBar.length () < deletedBar) dBar += "-";
- bar = aBar + cBar + dBar;
+ while (bar.length () < leftOffset - aBar.length ())
+ bar += " ";
+
+ bar += aBar + cBar + dBar;
}
table.addCell (row, 2, bar);
@@ -1600,7 +1182,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
<< table.render ()
<< std::endl;
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
out << "Legend: "
<< Text::colorize (Text::black, Text::on_red, "added")
<< ", "
@@ -1624,11 +1206,11 @@ std::string renderMonths (
int firstYear,
const Date& today,
std::vector & all,
- Config& conf)
+ Config& conf,
+ int monthsPerLine)
{
Table table;
table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
- int monthsPerLine = (conf.get ("monthsperline", 1));
// Build table for the number of months to be displayed.
for (int i = 0 ; i < (monthsPerLine * 8); i += 8)
@@ -1642,7 +1224,7 @@ std::string renderMonths (
table.addColumn ("Fr");
table.addColumn ("Sa");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (i + 1);
table.setColumnUnderline (i + 2);
@@ -1712,7 +1294,7 @@ std::string renderMonths (
table.addCell (row, thisCol, d);
- if (conf.get ("color", true) &&
+ if ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) &&
today.day () == d &&
today.month () == months.at (c) &&
today.year () == years.at (c))
@@ -1723,7 +1305,7 @@ std::string renderMonths (
{
Date due (::atoi (it->getAttribute ("due").c_str ()));
- if (conf.get ("color", true) &&
+ if ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) &&
due.day () == d &&
due.month () == months.at (c) &&
due.year () == years.at (c))
@@ -1747,6 +1329,26 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf)
{
std::stringstream out;
+ // Determine window size, and set table accordingly.
+ int width = conf.get ("defaultwidth", 80);
+#ifdef HAVE_LIBNCURSES
+ if (conf.get ("curses", true))
+ {
+ WINDOW* w = initscr ();
+ width = w->_maxx + 1;
+ endwin ();
+ }
+#endif
+
+ // Each month requires 23 text columns width. See how many will actually
+ // fit. But if a preference is specified, and it fits, use it.
+ int preferredMonthsPerLine = (conf.get (std::string ("monthsperline"), 0));
+ int monthsThatFit = width / 23;
+
+ int monthsPerLine = monthsThatFit;
+ if (preferredMonthsPerLine != 0 && preferredMonthsPerLine < monthsThatFit)
+ monthsPerLine = preferredMonthsPerLine;
+
// Load all the pending tasks.
std::vector pending;
tdb.allPendingT (pending);
@@ -1779,8 +1381,6 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf)
out << std::endl;
std::string output;
- int monthsPerLine = (conf.get ("monthsperline", 1));
-
while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo))
{
int nextM = mFrom;
@@ -1808,7 +1408,7 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf)
out << std::endl
<< optionalBlankLine (conf)
- << renderMonths (mFrom, yFrom, today, pending, conf)
+ << renderMonths (mFrom, yFrom, today, pending, conf, monthsPerLine)
<< std::endl;
mFrom += monthsPerLine;
@@ -1819,15 +1419,16 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf)
}
}
- out << "Legend: "
- << Text::colorize (Text::cyan, Text::nocolor, "today")
- << ", "
- << Text::colorize (Text::black, Text::on_yellow, "due")
- << ", "
- << Text::colorize (Text::black, Text::on_red, "overdue")
- << "."
- << optionalBlankLine (conf)
- << std::endl;
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
+ out << "Legend: "
+ << Text::colorize (Text::cyan, Text::nocolor, "today")
+ << ", "
+ << Text::colorize (Text::black, Text::on_yellow, "due")
+ << ", "
+ << Text::colorize (Text::black, Text::on_red, "overdue")
+ << "."
+ << optionalBlankLine (conf)
+ << std::endl;
return out.str ();
}
@@ -1865,7 +1466,7 @@ std::string handleReportActive (TDB& tdb, T& task, Config& conf)
table.addColumn ("Due");
table.addColumn ("Description");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -1921,20 +1522,20 @@ std::string handleReportActive (TDB& tdb, T& task, Config& conf)
table.addCell (row, 3, due);
table.addCell (row, 4, refTask.getDescription ());
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
+ autoColorize (refTask, fg, bg, conf);
table.setRowFg (row, fg);
table.setRowBg (row, bg);
if (fg == Text::nocolor)
{
if (overdue)
- table.setCellFg (row, 3, Text::red);
+ table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red")));
else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
+ table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow")));
}
}
}
@@ -1986,7 +1587,7 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf)
table.addColumn ("Due");
table.addColumn ("Description");
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
@@ -2035,11 +1636,11 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf)
table.addCell (row, 3, due);
table.addCell (row, 4, refTask.getDescription ());
- if (conf.get ("color", true))
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
+ autoColorize (refTask, fg, bg, conf);
table.setRowFg (row, fg);
table.setRowBg (row, bg);
@@ -2064,304 +1665,6 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf)
return out.str ();
}
-////////////////////////////////////////////////////////////////////////////////
-// Successively apply filters based on the task object built from the command
-// line. Tasks that match all the specified criteria are listed.
-std::string handleReportOldest (TDB& tdb, T& task, Config& conf)
-{
- std::stringstream out;
-
- // Determine window size, and set table accordingly.
- int width = conf.get ("defaultwidth", 80);
-#ifdef HAVE_LIBNCURSES
- if (conf.get ("curses", true))
- {
- WINDOW* w = initscr ();
- width = w->_maxx + 1;
- endwin ();
- }
-#endif
-
- // Get the pending tasks.
- std::vector tasks;
- tdb.allPendingT (tasks);
- handleRecurrence (tdb, tasks);
- filter (tasks, task);
-
- initializeColorRules (conf);
-
- bool showAge = conf.get ("showage", true);
- unsigned int quantity = conf.get ("oldest", 10);
-
- // Create a table for output.
- Table table;
- table.setTableWidth (width);
- table.addColumn ("ID");
- table.addColumn ("Project");
- table.addColumn ("Pri");
- table.addColumn ("Due");
- table.addColumn ("Active");
- if (showAge) table.addColumn ("Age");
- table.addColumn ("Description");
-
- if (conf.get ("color", true))
- {
- table.setColumnUnderline (0);
- table.setColumnUnderline (1);
- table.setColumnUnderline (2);
- table.setColumnUnderline (3);
- table.setColumnUnderline (4);
- table.setColumnUnderline (5);
- if (showAge) table.setColumnUnderline (6);
- }
- else
- table.setTableDashedUnderline ();
-
- table.setColumnWidth (0, Table::minimum);
- table.setColumnWidth (1, Table::minimum);
- table.setColumnWidth (2, Table::minimum);
- table.setColumnWidth (3, Table::minimum);
- table.setColumnWidth (4, Table::minimum);
- if (showAge) table.setColumnWidth (5, Table::minimum);
- table.setColumnWidth ((showAge ? 6 : 5), Table::flexible);
-
- table.setColumnJustification (0, Table::right);
- table.setColumnJustification (3, Table::right);
- if (showAge) table.setColumnJustification (5, Table::right);
-
- table.sortOn (3, Table::ascendingDate);
- table.sortOn (2, Table::descendingPriority);
- table.sortOn (1, Table::ascendingCharacter);
-
- table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
-
- for (unsigned int i = 0; i < min (quantity, tasks.size ()); ++i)
- {
- T refTask (tasks[i]);
- Date now;
-
- // Now format the matching task.
- bool imminent = false;
- bool overdue = false;
- std::string due = refTask.getAttribute ("due");
- if (due.length ())
- {
- switch (getDueState (due))
- {
- case 2: overdue = true; break;
- case 1: imminent = true; break;
- case 0:
- default: break;
- }
-
- Date dt (::atoi (due.c_str ()));
- due = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- std::string active;
- if (refTask.getAttribute ("start") != "")
- active = "*";
-
- std::string age;
- std::string created = refTask.getAttribute ("entry");
- if (created.length ())
- {
- Date dt (::atoi (created.c_str ()));
- formatTimeDeltaDays (age, (time_t) (now - dt));
- }
-
- // All criteria match, so add refTask to the output table.
- int row = table.addRow ();
- table.addCell (row, 0, refTask.getId ());
- table.addCell (row, 1, refTask.getAttribute ("project"));
- table.addCell (row, 2, refTask.getAttribute ("priority"));
- table.addCell (row, 3, due);
- table.addCell (row, 4, active);
- if (showAge) table.addCell (row, 5, age);
- table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ());
-
- if (conf.get ("color", true))
- {
- Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
- Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
- table.setRowFg (row, fg);
- table.setRowBg (row, bg);
-
- if (fg == Text::nocolor)
- {
- if (overdue)
- table.setCellFg (row, 3, Text::red);
- else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
- }
- }
- }
-
- if (table.rowCount ())
- out << optionalBlankLine (conf)
- << table.render ()
- << optionalBlankLine (conf)
- << table.rowCount ()
- << (table.rowCount () == 1 ? " task" : " tasks")
- << std::endl;
- else
- out << "No matches."
- << std::endl;
-
- return out.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Successively apply filters based on the task object built from the command
-// line. Tasks that match all the specified criteria are listed.
-std::string handleReportNewest (TDB& tdb, T& task, Config& conf)
-{
- std::stringstream out;
-
- // Determine window size, and set table accordingly.
- int width = conf.get ("defaultwidth", 80);
-#ifdef HAVE_LIBNCURSES
- if (conf.get ("curses", true))
- {
- WINDOW* w = initscr ();
- width = w->_maxx + 1;
- endwin ();
- }
-#endif
-
- // Get the pending tasks.
- std::vector tasks;
- tdb.allPendingT (tasks);
- handleRecurrence (tdb, tasks);
- filter (tasks, task);
-
- initializeColorRules (conf);
-
- bool showAge = conf.get ("showage", true);
- int quantity = conf.get ("newest", 10);
-
- // Create a table for output.
- Table table;
- table.setTableWidth (width);
- table.addColumn ("ID");
- table.addColumn ("Project");
- table.addColumn ("Pri");
- table.addColumn ("Due");
- table.addColumn ("Active");
- if (showAge) table.addColumn ("Age");
- table.addColumn ("Description");
-
- if (conf.get ("color", true))
- {
- table.setColumnUnderline (0);
- table.setColumnUnderline (1);
- table.setColumnUnderline (2);
- table.setColumnUnderline (3);
- table.setColumnUnderline (4);
- table.setColumnUnderline (5);
- if (showAge) table.setColumnUnderline (6);
- }
- else
- table.setTableDashedUnderline ();
-
- table.setColumnWidth (0, Table::minimum);
- table.setColumnWidth (1, Table::minimum);
- table.setColumnWidth (2, Table::minimum);
- table.setColumnWidth (3, Table::minimum);
- table.setColumnWidth (4, Table::minimum);
- if (showAge) table.setColumnWidth (5, Table::minimum);
- table.setColumnWidth ((showAge ? 6 : 5), Table::flexible);
-
- table.setColumnJustification (0, Table::right);
- table.setColumnJustification (3, Table::right);
- if (showAge) table.setColumnJustification (5, Table::right);
-
- table.sortOn (3, Table::ascendingDate);
- table.sortOn (2, Table::descendingPriority);
- table.sortOn (1, Table::ascendingCharacter);
-
- table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
-
- int total = tasks.size ();
- for (int i = total - 1; i >= max (0, total - quantity); --i)
- {
- T refTask (tasks[i]);
- Date now;
-
- // Now format the matching task.
- bool imminent = false;
- bool overdue = false;
- std::string due = refTask.getAttribute ("due");
- if (due.length ())
- {
- switch (getDueState (due))
- {
- case 2: overdue = true; break;
- case 1: imminent = true; break;
- case 0:
- default: break;
- }
-
- Date dt (::atoi (due.c_str ()));
- due = dt.toString (conf.get ("dateformat", "m/d/Y"));
- }
-
- std::string active;
- if (refTask.getAttribute ("start") != "")
- active = "*";
-
- std::string age;
- std::string created = refTask.getAttribute ("entry");
- if (created.length ())
- {
- Date dt (::atoi (created.c_str ()));
- formatTimeDeltaDays (age, (time_t) (now - dt));
- }
-
- // All criteria match, so add refTask to the output table.
- int row = table.addRow ();
- table.addCell (row, 0, refTask.getId ());
- table.addCell (row, 1, refTask.getAttribute ("project"));
- table.addCell (row, 2, refTask.getAttribute ("priority"));
- table.addCell (row, 3, due);
- table.addCell (row, 4, active);
- if (showAge) table.addCell (row, 5, age);
- table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ());
-
- if (conf.get ("color", true))
- {
- Text::color fg = Text::colorCode (refTask.getAttribute ("fg"));
- Text::color bg = Text::colorCode (refTask.getAttribute ("bg"));
- autoColorize (refTask, fg, bg);
- table.setRowFg (row, fg);
- table.setRowBg (row, bg);
-
- if (fg == Text::nocolor)
- {
- if (overdue)
- table.setCellFg (row, 3, Text::red);
- else if (imminent)
- table.setCellFg (row, 3, Text::yellow);
- }
- }
- }
-
- if (table.rowCount ())
- out << optionalBlankLine (conf)
- << table.render ()
- << optionalBlankLine (conf)
- << table.rowCount ()
- << (table.rowCount () == 1 ? " task" : " tasks")
- << std::endl;
- else
- out << "No matches."
- << std::endl;
-
- return out.str ();
-}
-
-
////////////////////////////////////////////////////////////////////////////////
std::string handleReportStats (TDB& tdb, T& task, Config& conf)
{
@@ -2383,6 +1686,8 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf)
int recurringT = 0;
float daysPending = 0.0;
int descLength = 0;
+ std::map allTags;
+ std::map allProjects;
std::vector ::iterator it;
for (it = tasks.begin (); it != tasks.end (); ++it)
@@ -2411,6 +1716,13 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf)
std::vector tags;
it->getTags (tags);
if (tags.size ()) ++taggedT;
+
+ foreach (t, tags)
+ allTags[*t] = 0;
+
+ std::string project = it->getAttribute ("project");
+ if (project != "")
+ allProjects[project] = 0;
}
out << "Pending " << pendingT << std::endl
@@ -2448,6 +1760,9 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf)
out << "Tasks tagged " << std::setprecision (3) << (100.0 * taggedT / totalT) << "%" << std::endl;
}
+ out << "Unique tags " << allTags.size () << std::endl;
+ out << "Projects " << allProjects.size () << std::endl;
+
return out.str ();
}
@@ -2638,3 +1953,401 @@ void gatherNextTasks (
}
////////////////////////////////////////////////////////////////////////////////
+// This report will eventually become the one report that many others morph into
+// via the .taskrc file.
+std::string handleCustomReport (
+ TDB& tdb,
+ T& task,
+ Config& conf,
+ const std::string& report)
+{
+ // Determine window size, and set table accordingly.
+ int width = conf.get ("defaultwidth", 80);
+#ifdef HAVE_LIBNCURSES
+ if (conf.get ("curses", true))
+ {
+ WINDOW* w = initscr ();
+ width = w->_maxx + 1;
+ endwin ();
+ }
+#endif
+
+ // Load report configuration.
+ std::string columnList = conf.get ("report." + report + ".columns");
+ std::vector columns;
+ split (columns, columnList, ',');
+ validReportColumns (columns);
+
+ std::string sortList = conf.get ("report." + report + ".sort");
+ std::vector sortOrder;
+ split (sortOrder, sortList, ',');
+ validSortColumns (columns, sortOrder);
+
+ std::string filterList = conf.get ("report." + report + ".filter");
+ std::vector filterArgs;
+ split (filterArgs, filterList, ' ');
+
+ // Load all pending tasks.
+ std::vector tasks;
+ tdb.allPendingT (tasks);
+ handleRecurrence (tdb, tasks);
+
+ // Apply filters.
+ {
+ std::string ignore;
+ T filterTask;
+ parse (filterArgs, ignore, filterTask, conf);
+
+ filter (tasks, filterTask); // Filter from custom report
+ filter (tasks, task); // Filter from command line
+ }
+
+ // Initialize colorization for subsequent auto colorization.
+ initializeColorRules (conf);
+
+ Table table;
+ table.setTableWidth (width);
+ table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
+
+ for (unsigned int i = 0; i < tasks.size (); ++i)
+ table.addRow ();
+
+ int columnCount = 0;
+ int dueColumn = -1;
+ foreach (col, columns)
+ {
+ // Add each column individually.
+ if (*col == "id")
+ {
+ table.addColumn ("ID");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::right);
+
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ table.addCell (row, columnCount, tasks[row].getId ());
+ }
+
+ else if (*col == "uuid")
+ {
+ table.addColumn ("UUID");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::left);
+
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ table.addCell (row, columnCount, tasks[row].getUUID ());
+ }
+
+ else if (*col == "project")
+ {
+ table.addColumn ("Project");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::left);
+
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ table.addCell (row, columnCount, tasks[row].getAttribute ("project"));
+ }
+
+ else if (*col == "priority")
+ {
+ table.addColumn ("Pri");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::left);
+
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ table.addCell (row, columnCount, tasks[row].getAttribute ("priority"));
+ }
+
+ else if (*col == "entry")
+ {
+ table.addColumn ("Added");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::right);
+
+ std::string entered;
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ {
+ entered = tasks[row].getAttribute ("entry");
+ if (entered.length ())
+ {
+ Date dt (::atoi (entered.c_str ()));
+ entered = dt.toString (conf.get ("dateformat", "m/d/Y"));
+ table.addCell (row, columnCount, entered);
+ }
+ }
+ }
+
+ else if (*col == "start")
+ {
+ table.addColumn ("Started");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::right);
+
+ std::string started;
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ {
+ started = tasks[row].getAttribute ("start");
+ if (started.length ())
+ {
+ Date dt (::atoi (started.c_str ()));
+ started = dt.toString (conf.get ("dateformat", "m/d/Y"));
+ table.addCell (row, columnCount, started);
+ }
+ }
+ }
+
+ else if (*col == "due")
+ {
+ table.addColumn ("Due");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::right);
+
+ std::string due;
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ {
+ due = tasks[row].getAttribute ("due");
+ if (due.length ())
+ {
+ Date dt (::atoi (due.c_str ()));
+ due = dt.toString (conf.get ("dateformat", "m/d/Y"));
+ table.addCell (row, columnCount, due);
+ }
+ }
+
+ dueColumn = columnCount;
+ }
+
+ else if (*col == "age")
+ {
+ table.addColumn ("Age");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::right);
+
+ std::string created;
+ std::string age;
+ Date now;
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ {
+ created = tasks[row].getAttribute ("entry");
+ if (created.length ())
+ {
+ Date dt (::atoi (created.c_str ()));
+ formatTimeDeltaDays (age, (time_t) (now - dt));
+ table.addCell (row, columnCount, age);
+ }
+ }
+ }
+
+ else if (*col == "active")
+ {
+ table.addColumn ("Active");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::left);
+
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ if (tasks[row].getAttribute ("start") != "")
+ table.addCell (row, columnCount, "*");
+ }
+
+ else if (*col == "tags")
+ {
+ table.addColumn ("Tags");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::left);
+
+ std::vector all;
+ std::string tags;
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ {
+ tasks[row].getTags (all);
+ join (tags, " ", all);
+ table.addCell (row, columnCount, tags);
+ }
+ }
+
+ else if (*col == "description")
+ {
+ table.addColumn ("Description");
+ table.setColumnWidth (columnCount, Table::flexible);
+ table.setColumnJustification (columnCount, Table::left);
+
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ table.addCell (row, columnCount, tasks[row].getDescription ());
+ }
+
+ else if (*col == "recur")
+ {
+ table.addColumn ("Recur");
+ table.setColumnWidth (columnCount, Table::minimum);
+ table.setColumnJustification (columnCount, Table::right);
+
+ for (unsigned int row = 0; row < tasks.size (); ++row)
+ table.addCell (row, columnCount, tasks[row].getAttribute ("recur"));
+ }
+
+ // Common to all columns.
+ // Add underline.
+ if (conf.get (std::string ("color"), true) || conf.get (std::string ("_forcecolor"), false))
+ table.setColumnUnderline (columnCount);
+ else
+ table.setTableDashedUnderline ();
+
+ ++columnCount;
+ }
+
+ // Dynamically add sort criteria.
+ // Build a map of column names -> index.
+ std::map columnIndex;
+ for (unsigned int c = 0; c < columns.size (); ++c)
+ columnIndex[columns[c]] = c;
+
+ foreach (sortColumn, sortOrder)
+ {
+ // Separate column and direction.
+ std::string column = sortColumn->substr (0, sortColumn->length () - 1);
+ char direction = (*sortColumn)[sortColumn->length () - 1];
+
+ if (column == "id")
+ table.sortOn (columnIndex[column],
+ (direction == '+' ?
+ Table::ascendingNumeric :
+ Table::descendingNumeric));
+
+ else if (column == "priority")
+ table.sortOn (columnIndex[column],
+ (direction == '+' ?
+ Table::ascendingPriority :
+ Table::descendingPriority));
+
+ else if (column == "entry" || column == "start" || column == "due")
+ table.sortOn (columnIndex[column],
+ (direction == '+' ?
+ Table::ascendingDate :
+ Table::descendingDate));
+
+ else if (column == "recur")
+ table.sortOn (columnIndex[column],
+ (direction == '+' ?
+ Table::ascendingPeriod :
+ Table::descendingPeriod));
+
+ else
+ table.sortOn (columnIndex[column],
+ (direction == '+' ?
+ Table::ascendingCharacter :
+ Table::descendingCharacter));
+ }
+
+ // Now auto colorize all rows.
+ std::string due;
+ bool imminent;
+ bool overdue;
+ for (unsigned int row = 0; row < tasks.size (); ++row)
+ {
+ imminent = false;
+ overdue = false;
+ due = tasks[row].getAttribute ("due");
+ if (due.length ())
+ {
+ switch (getDueState (due))
+ {
+ case 2: overdue = true; break;
+ case 1: imminent = true; break;
+ case 0:
+ default: break;
+ }
+ }
+
+ if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
+ {
+ Text::color fg = Text::colorCode (tasks[row].getAttribute ("fg"));
+ Text::color bg = Text::colorCode (tasks[row].getAttribute ("bg"));
+ autoColorize (tasks[row], fg, bg, conf);
+ table.setRowFg (row, fg);
+ table.setRowBg (row, bg);
+
+ if (fg == Text::nocolor)
+ {
+ if (dueColumn != -1)
+ {
+ if (overdue)
+ table.setCellFg (row, columnCount, Text::colorCode (conf.get ("color.overdue", "red")));
+ else if (imminent)
+ table.setCellFg (row, columnCount, Text::colorCode (conf.get ("color.due", "yellow")));
+ }
+ }
+ }
+ }
+
+ int maximum = conf.get (std::string ("report.") + report + ".limit", (int)0);
+
+ std::stringstream out;
+ if (table.rowCount ())
+ out << optionalBlankLine (conf)
+ << table.render (maximum)
+ << optionalBlankLine (conf)
+ << table.rowCount ()
+ << (table.rowCount () == 1 ? " task" : " tasks")
+ << std::endl;
+ else
+ out << "No matches."
+ << std::endl;
+
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void validReportColumns (const std::vector & columns)
+{
+ std::vector bad;
+
+ std::vector ::const_iterator it;
+ for (it = columns.begin (); it != columns.end (); ++it)
+ if (*it != "id" &&
+ *it != "uuid" &&
+ *it != "project" &&
+ *it != "priority" &&
+ *it != "entry" &&
+ *it != "start" &&
+ *it != "due" &&
+ *it != "age" &&
+ *it != "active" &&
+ *it != "tags" &&
+ *it != "recur" &&
+ *it != "description")
+ bad.push_back (*it);
+
+ if (bad.size ())
+ {
+ std::string error;
+ join (error, ", ", bad);
+ throw std::string ("Unrecognized column name: ") + error;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void validSortColumns (
+ const std::vector & columns,
+ const std::vector & sortColumns)
+{
+ std::vector bad;
+ std::vector ::const_iterator sc;
+ for (sc = sortColumns.begin (); sc != sortColumns.end (); ++sc)
+ {
+ std::vector ::const_iterator co;
+ for (co = columns.begin (); co != columns.end (); ++co)
+ if (sc->substr (0, sc->length () - 1) == *co)
+ break;
+
+ if (co == columns.end ())
+ bad.push_back (*sc);
+ }
+
+ if (bad.size ())
+ {
+ std::string error;
+ join (error, ", ", bad);
+ throw std::string ("Sort column is not part of the report: ") + error;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/src/rules.cpp b/src/rules.cpp
index 4d9ccabe2..5744dd3ca 100644
--- a/src/rules.cpp
+++ b/src/rules.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -80,10 +80,14 @@ void initializeColorRules (Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
-void autoColorize (T& task, Text::color& fg, Text::color& bg)
+void autoColorize (
+ T& task,
+ Text::color& fg,
+ Text::color& bg,
+ Config& conf)
{
// Note: fg, bg already contain colors specifically assigned via command.
- // Note: These rules form a hierarchy - the last rule is king.
+ // Note: These rules form a hierarchy - the last rule is King.
// Colorization of the tagged.
if (gsFg["color.tagged"] != Text::nocolor ||
@@ -153,29 +157,6 @@ void autoColorize (T& task, Text::color& fg, Text::color& bg)
}
}
- // Colorization of the due and overdue.
- std::string due = task.getAttribute ("due");
- if (due != "")
- {
- Date dueDate (::atoi (due.c_str ()));
- Date now;
- Date then (now + 7 * 86400);
-
- // Overdue
- if (dueDate < now)
- {
- fg = gsFg["color.overdue"];
- bg = gsBg["color.overdue"];
- }
-
- // Imminent
- else if (dueDate < then)
- {
- fg = gsFg["color.due"];
- bg = gsBg["color.due"];
- }
- }
-
// Colorization by tag value.
std::map ::iterator it;
for (it = gsFg.begin (); it != gsFg.end (); ++it)
@@ -219,6 +200,40 @@ void autoColorize (T& task, Text::color& fg, Text::color& bg)
}
}
}
+
+ // Colorization of the due and overdue.
+ std::string due = task.getAttribute ("due");
+ if (due != "")
+ {
+ Date dueDate (::atoi (due.c_str ()));
+ Date now;
+ Date then (now + conf.get ("due", 7) * 86400);
+
+ // Overdue
+ if (dueDate < now)
+ {
+ fg = gsFg["color.overdue"];
+ bg = gsBg["color.overdue"];
+ }
+
+ // Imminent
+ else if (dueDate < then)
+ {
+ fg = gsFg["color.due"];
+ bg = gsBg["color.due"];
+ }
+ }
+
+ // Colorization of the recurring.
+ if (gsFg["color.recurring"] != Text::nocolor ||
+ gsBg["color.recurring"] != Text::nocolor)
+ {
+ if (task.getAttribute ("recur") != "")
+ {
+ fg = gsFg["color.recurring"];
+ bg = gsBg["color.recurring"];
+ }
+ }
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/task.cpp b/src/task.cpp
index cdb4e0b13..d31952f02 100644
--- a/src/task.cpp
+++ b/src/task.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -47,13 +48,9 @@
#endif
////////////////////////////////////////////////////////////////////////////////
-// Globals for exclusive use by callback function.
-static TDB* gTdb = NULL;
-static Config* gConf = NULL;
-
-////////////////////////////////////////////////////////////////////////////////
-static void shortUsage (Config& conf)
+static std::string shortUsage (Config& conf)
{
+ std::stringstream out;
Table table;
int width = conf.get ("defaultwidth", 80);
#ifdef HAVE_LIBNCURSES
@@ -87,18 +84,6 @@ static void shortUsage (Config& conf)
table.addCell (row, 1, "task add [tags] [attrs] desc...");
table.addCell (row, 2, "Adds a new task");
- row = table.addRow ();
- table.addCell (row, 1, "task list [tags] [attrs] desc...");
- table.addCell (row, 2, "Lists all tasks matching the specified criteria");
-
- row = table.addRow ();
- table.addCell (row, 1, "task long [tags] [attrs] desc...");
- table.addCell (row, 2, "Lists all task, all data, matching the specified criteria");
-
- row = table.addRow ();
- table.addCell (row, 1, "task ls [tags] [attrs] desc...");
- table.addCell (row, 2, "Minimal listing of all tasks matching the specified criteria");
-
row = table.addRow ();
table.addCell (row, 1, "task completed [tags] [attrs] desc...");
table.addCell (row, 2, "Chronological listing of all completed tasks matching the specified criteria");
@@ -125,7 +110,11 @@ static void shortUsage (Config& conf)
row = table.addRow ();
table.addCell (row, 1, "task start ID");
- table.addCell (row, 2, "Marks specified task as started, starts the clock ticking");
+ table.addCell (row, 2, "Marks specified task as started");
+
+ row = table.addRow ();
+ table.addCell (row, 1, "task stop ID");
+ table.addCell (row, 2, "Removes the 'start' time from a task");
row = table.addRow ();
table.addCell (row, 1, "task done ID");
@@ -171,14 +160,6 @@ static void shortUsage (Config& conf)
table.addCell (row, 1, "task overdue");
table.addCell (row, 2, "Shows all incomplete tasks that are beyond their due date");
- row = table.addRow ();
- table.addCell (row, 1, "task oldest");
- table.addCell (row, 2, "Shows the oldest tasks");
-
- row = table.addRow ();
- table.addCell (row, 1, "task newest");
- table.addCell (row, 2, "Shows the newest tasks");
-
row = table.addRow ();
table.addCell (row, 1, "task stats");
table.addCell (row, 2, "Shows task database statistics");
@@ -199,46 +180,65 @@ static void shortUsage (Config& conf)
table.addCell (row, 1, "task help");
table.addCell (row, 2, "Shows the long usage text");
- std::cout << table.render ()
- << std::endl
- << "See http://www.beckingham.net/task.html for the latest releases and a full tutorial."
- << std::endl
- << std::endl;
+ // Add custom reports here...
+ std::vector all;
+ allCustomReports (all);
+ foreach (report, all)
+ {
+ std::string command = std::string ("task ") + *report + std::string (" [tags] [attrs] desc...");
+ std::string description = conf.get (
+ std::string ("report.") + *report + ".description", std::string ("(missing description)"));
+
+ row = table.addRow ();
+ table.addCell (row, 1, command);
+ table.addCell (row, 2, description);
+ }
+
+ out << table.render ()
+ << std::endl
+ << "See http://www.beckingham.net/task.html for the latest releases and a "
+ << "full tutorial. New releases containing fixes and enhancements are "
+ << "made frequently."
+ << std::endl
+ << std::endl;
+
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
-static void longUsage (Config& conf)
+static std::string longUsage (Config& conf)
{
- shortUsage (conf);
+ std::stringstream out;
+ out << shortUsage (conf)
+ << "ID is the numeric identifier displayed by the 'task list' command." << "\n"
+ << "\n"
+ << "Tags are arbitrary words, any quantity:" << "\n"
+ << " +tag The + means add the tag" << "\n"
+ << " -tag The - means remove the tag" << "\n"
+ << "\n"
+ << "Attributes are:" << "\n"
+ << " project: Project name" << "\n"
+ << " priority: Priority" << "\n"
+ << " due: Due date" << "\n"
+ << " recur: Recurrence frequency" << "\n"
+ << " until: Recurrence end date" << "\n"
+ << " fg: Foreground color" << "\n"
+ << " bg: Background color" << "\n"
+ << " rc: Alternate .taskrc file" << "\n"
+ << "\n"
+ << "Any command or attribute name may be abbreviated if still unique:" << "\n"
+ << " task list project:Home" << "\n"
+ << " task li pro:Home" << "\n"
+ << "\n"
+ << "Some task descriptions need to be escaped because of the shell:" << "\n"
+ << " task add \"quoted ' quote\"" << "\n"
+ << " task add escaped \\' quote" << "\n"
+ << "\n"
+ << "Many characters have special meaning to the shell, including:" << "\n"
+ << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n"
+ << std::endl;
- std::cout
- << "ID is the numeric identifier displayed by the 'task list' command" << "\n"
- << "\n"
- << "Tags are arbitrary words, any quantity:" << "\n"
- << " +tag The + means add the tag" << "\n"
- << " -tag The - means remove the tag" << "\n"
- << "\n"
- << "Attributes are:" << "\n"
- << " project: Project name" << "\n"
- << " priority: Priority" << "\n"
- << " due: Due date" << "\n"
- << " recur: Recurrence frequency" << "\n"
- << " until: Recurrence end date" << "\n"
- << " fg: Foreground color" << "\n"
- << " bg: Background color" << "\n"
- << " rc: Alternate .taskrc file" << "\n"
- << "\n"
- << "Any command or attribute name may be abbreviated if still unique:" << "\n"
- << " task list project:Home" << "\n"
- << " task li pro:Home" << "\n"
- << "\n"
- << "Some task descriptions need to be escaped because of the shell:" << "\n"
- << " task add \"quoted ' quote\"" << "\n"
- << " task add escaped \\' quote" << "\n"
- << "\n"
- << "Many characters have special meaning to the shell, including:" << "\n"
- << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n"
- << std::endl;
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
@@ -270,9 +270,6 @@ void loadConfFile (int argc, char** argv, Config& conf)
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
-// TODO Find out what this is, and either promote it to live code, or remove it.
-// std::set_terminate (__gnu_cxx::__verbose_terminate_handler);
-
// Set up randomness.
#ifdef HAVE_SRANDOM
srandom (time (NULL));
@@ -285,22 +282,26 @@ int main (int argc, char** argv)
// Load the config file from the home directory. If the file cannot be
// found, offer to create a sample one.
Config conf;
- gConf = &conf;
loadConfFile (argc, argv, conf);
// When redirecting output to a file, do not use color, curses.
if (!isatty (fileno (stdout)))
{
conf.set ("curses", "off");
- conf.set ("color", "off");
+
+ if (! conf.get (std::string ("_forcecolor"), false))
+ conf.set ("color", "off");
}
TDB tdb;
- gTdb = &tdb;
std::string dataLocation = expandPath (conf.get ("data.location"));
tdb.dataDirectory (dataLocation);
- // Set up TDB callback.
+ // Allow user override of file locking. Solaris/NFS machines may want this.
+ if (! conf.get ("locking", true))
+ tdb.noLock ();
+
+ // Check for silly shadow file settings.
std::string shadowFile = expandPath (conf.get ("shadow.file"));
if (shadowFile != "")
{
@@ -311,8 +312,6 @@ int main (int argc, char** argv)
if (shadowFile == dataLocation + "/completed.data")
throw std::string ("Configuration variable 'shadow.file' is set to "
"overwrite your completed tasks. Please change it.");
-
- tdb.onChange (&onChangeCallback);
}
std::cout << runTaskCommand (argc, argv, tdb, conf);
@@ -339,18 +338,56 @@ void nag (TDB& tdb, T& task, Config& conf)
std::string nagMessage = conf.get ("nag", std::string (""));
if (nagMessage != "")
{
- // Load all pending.
+ // Load all pending tasks.
std::vector pending;
tdb.allPendingT (pending);
- // Restrict to matching subset.
- std::vector matching;
- gatherNextTasks (tdb, task, conf, pending, matching);
+ // Counters.
+ int overdue = 0;
+ int high = 0;
+ int medium = 0;
+ int low = 0;
+ bool isOverdue = false;
+ char pri = ' ';
- foreach (i, matching)
- if (pending[*i].getId () == task.getId ())
- return;
+ // Scan all pending tasks.
+ foreach (t, pending)
+ {
+ if (t->getId () == task.getId ())
+ {
+ if (getDueState (t->getAttribute ("due")) == 2)
+ isOverdue = true;
+ std::string priority = t->getAttribute ("priority");
+ if (priority.length ())
+ pri = priority[0];
+ }
+ else if (t->getStatus () == T::pending)
+ {
+ if (getDueState (t->getAttribute ("due")) == 2)
+ overdue++;
+
+ std::string priority = t->getAttribute ("priority");
+ if (priority.length ())
+ {
+ switch (priority[0])
+ {
+ case 'H': high++; break;
+ case 'M': medium++; break;
+ case 'L': low++; break;
+ }
+ }
+ }
+ }
+
+ // General form is "if there are no more deserving tasks", suppress the nag.
+ if (isOverdue ) return;
+ if (pri == 'H' && !overdue ) return;
+ if (pri == 'M' && !overdue && !high ) return;
+ if (pri == 'L' && !overdue && !high && !medium ) return;
+ if (pri == ' ' && !overdue && !high && !medium && !low) return;
+
+ // All the excuses are made, all that remains is to nag the user.
std::cout << nagMessage << std::endl;
}
}
@@ -368,15 +405,12 @@ int getDueState (const std::string& due)
// rightNow is the current date + time.
Date rightNow;
+ Date midnight (rightNow.month (), rightNow.day (), rightNow.year ());
- // By performing this conversion, today is set up as the same date, but
- // midnight.
- Date today (rightNow.month (), rightNow.day (), rightNow.year ());
-
- if (dt < today)
+ if (dt < midnight)
return 2;
- Date nextweek = today + 7 * 86400;
+ Date nextweek = midnight + 7 * 86400;
if (dt < nextweek)
return 1;
}
@@ -668,51 +702,43 @@ void updateRecurrenceMask (
}
////////////////////////////////////////////////////////////////////////////////
-// Using gTdb and gConf, generate a report.
-void onChangeCallback ()
+void updateShadowFile (TDB& tdb, Config& conf)
{
try
{
- if (gConf && gTdb)
+ // Determine if shadow file is enabled.
+ std::string shadowFile = expandPath (conf.get ("shadow.file"));
+ if (shadowFile != "")
{
- // Determine if shadow file is enabled.
- std::string shadowFile = expandPath (gConf->get ("shadow.file"));
- if (shadowFile != "")
+ std::string oldCurses = conf.get ("curses");
+ std::string oldColor = conf.get ("color");
+ conf.set ("curses", "off");
+ conf.set ("color", "off");
+
+ // Run report. Use shadow.command, using default.command as a fallback
+ // with "list" as a default.
+ std::string command = conf.get ("shadow.command",
+ conf.get ("default.command", "list"));
+ std::vector args;
+ split (args, command, ' ');
+ std::string result = runTaskCommand (args, tdb, conf);
+
+ std::ofstream out (shadowFile.c_str ());
+ if (out.good ())
{
- std::string oldCurses = gConf->get ("curses");
- std::string oldColor = gConf->get ("color");
- gConf->set ("curses", "off");
- gConf->set ("color", "off");
-
- // Run report. Use shadow.command, using default.command as a fallback
- // with "list" as a default.
- std::string command = gConf->get ("shadow.command",
- gConf->get ("default.command", "list"));
- std::vector args;
- split (args, command, ' ');
- std::string result = runTaskCommand (args, *gTdb, *gConf);
-
- std::ofstream out (shadowFile.c_str ());
- if (out.good ())
- {
- out << result;
- out.close ();
- }
- else
- throw std::string ("Could not write file '") + shadowFile + "'";
-
- gConf->set ("curses", oldCurses);
- gConf->set ("color", oldColor);
+ out << result;
+ out.close ();
}
else
- throw std::string ("No specified shadow file '") + shadowFile + "'.";
+ throw std::string ("Could not write file '") + shadowFile + "'";
- // Optionally display a notification that the shadow file was updated.
- if (gConf->get (std::string ("shadow.notify"), false))
- std::cout << "[Shadow file '" << shadowFile << "' updated]" << std::endl;
+ conf.set ("curses", oldCurses);
+ conf.set ("color", oldColor);
}
- else
- throw std::string ("Internal error (TDB/Config).");
+
+ // Optionally display a notification that the shadow file was updated.
+ if (conf.get (std::string ("shadow.notify"), false))
+ std::cout << "[Shadow file '" << shadowFile << "' updated]" << std::endl;
}
catch (std::string& error)
@@ -732,13 +758,14 @@ std::string runTaskCommand (
char** argv,
TDB& tdb,
Config& conf,
- bool gc /* = true */)
+ bool gc /* = true */,
+ bool shadow /* = true */)
{
std::vector args;
for (int i = 1; i < argc; ++i)
args.push_back (argv[i]);
- return runTaskCommand (args, tdb, conf, gc);
+ return runTaskCommand (args, tdb, conf, gc, shadow);
}
////////////////////////////////////////////////////////////////////////////////
@@ -746,12 +773,15 @@ std::string runTaskCommand (
std::vector & args,
TDB& tdb,
Config& conf,
- bool gc /* = false */)
+ bool gc /* = false */,
+ bool shadow /* = false */)
{
// If argc == 1 and the default.command configuration variable is set,
// then use that, otherwise stick with argc/argv.
std::string defaultCommand = conf.get ("default.command");
- if (args.size () == 0 && defaultCommand != "")
+ if ((args.size () == 0 ||
+ (args.size () == 1 && args[0].substr (0, 3) == "rc:")) &&
+ defaultCommand != "")
{
// Stuff the command line.
args.clear ();
@@ -759,41 +789,55 @@ std::string runTaskCommand (
std::cout << "[task " << defaultCommand << "]" << std::endl;
}
+ loadCustomReports (conf);
+
std::string command;
T task;
parse (args, command, task, conf);
- std::string out = "";
+ bool gcMod = false; // Change occurred by way of gc.
+ bool cmdMod = false; // Change occurred by way of command type.
+ std::string out;
- if (command == "" && task.getId ()) { handleModify (tdb, task, conf); }
- else if (command == "add") { handleAdd (tdb, task, conf); }
- else if (command == "done") { handleDone (tdb, task, conf); }
- else if (command == "export") { handleExport (tdb, task, conf); }
- else if (command == "projects") { out = handleProjects (tdb, task, conf); }
- else if (command == "tags") { out = handleTags (tdb, task, conf); }
- else if (command == "info") { out = handleInfo (tdb, task, conf); }
- else if (command == "undelete") { out = handleUndelete (tdb, task, conf); }
- else if (command == "delete") { out = handleDelete (tdb, task, conf); }
- else if (command == "start") { out = handleStart (tdb, task, conf); }
- else if (command == "undo") { out = handleUndo (tdb, task, conf); }
- else if (command == "stats") { out = handleReportStats (tdb, task, conf); }
- else if (command == "list") { if (gc) tdb.gc (); out = handleList (tdb, task, conf); }
- else if (command == "long") { if (gc) tdb.gc (); out = handleLongList (tdb, task, conf); }
- else if (command == "ls") { if (gc) tdb.gc (); out = handleSmallList (tdb, task, conf); }
- else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf); }
- else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf); }
- else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf); }
- else if (command == "history") { if (gc) tdb.gc (); out = handleReportHistory (tdb, task, conf); }
- else if (command == "ghistory") { if (gc) tdb.gc (); out = handleReportGHistory (tdb, task, conf); }
- else if (command == "calendar") { if (gc) tdb.gc (); out = handleReportCalendar (tdb, task, conf); }
- else if (command == "active") { if (gc) tdb.gc (); out = handleReportActive (tdb, task, conf); }
- else if (command == "overdue") { if (gc) tdb.gc (); out = handleReportOverdue (tdb, task, conf); }
- else if (command == "oldest") { if (gc) tdb.gc (); out = handleReportOldest (tdb, task, conf); }
- else if (command == "newest") { if (gc) tdb.gc (); out = handleReportNewest (tdb, task, conf); }
- else if (command == "colors") { out = handleColor ( conf); }
- else if (command == "version") { out = handleVersion ( conf); }
- else if (command == "help") { longUsage ( conf); }
- else { shortUsage ( conf); }
+ // Read-only commands with no side effects.
+ if (command == "export") { out = handleExport (tdb, task, conf); }
+ else if (command == "projects") { out = handleProjects (tdb, task, conf); }
+ else if (command == "tags") { out = handleTags (tdb, task, conf); }
+ else if (command == "info") { out = handleInfo (tdb, task, conf); }
+ else if (command == "stats") { out = handleReportStats (tdb, task, conf); }
+ else if (command == "history") { out = handleReportHistory (tdb, task, conf); }
+ else if (command == "ghistory") { out = handleReportGHistory (tdb, task, conf); }
+ else if (command == "calendar") { out = handleReportCalendar (tdb, task, conf); }
+ else if (command == "summary") { out = handleReportSummary (tdb, task, conf); }
+ else if (command == "colors") { out = handleColor ( conf); }
+ else if (command == "version") { out = handleVersion ( conf); }
+ else if (command == "help") { out = longUsage ( conf); }
+
+ // Commands that cause updates.
+ else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); }
+ else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); }
+ else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); }
+ else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); }
+ else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); }
+ else if (command == "start") { cmdMod = true; out = handleStart (tdb, task, conf); }
+ else if (command == "stop") { cmdMod = true; out = handleStop (tdb, task, conf); }
+ else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task, conf); }
+
+ // Command that display IDs and therefore need TDB::gc first.
+
+ else if (command == "completed") { if (gc) gcMod = tdb.gc (); out = handleCompleted (tdb, task, conf); }
+ else if (command == "next") { if (gc) gcMod = tdb.gc (); out = handleReportNext (tdb, task, conf); }
+ else if (command == "active") { if (gc) gcMod = tdb.gc (); out = handleReportActive (tdb, task, conf); }
+ else if (command == "overdue") { if (gc) gcMod = tdb.gc (); out = handleReportOverdue (tdb, task, conf); }
+ else if (isCustomReport (command)) { if (gc) gcMod = tdb.gc (); out = handleCustomReport (tdb, task, conf, command); }
+
+ // If the command is not recognized, display usage.
+ else { out = shortUsage (conf); }
+
+ // Only update the shadow file if such an update was not suppressed (shadow),
+ // and if an actual change occurred (gcMod || cmdMod).
+ if (shadow && (gcMod || cmdMod))
+ updateShadowFile (tdb, conf);
return out;
}
diff --git a/src/task.h b/src/task.h
index 6bd865b56..5ddd81f6f 100644
--- a/src/task.h
+++ b/src/task.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -57,6 +57,9 @@ for (typeof (c) *foreach_p = & (c); \
void parse (std::vector &, std::string&, T&, Config&);
bool validPriority (const std::string&);
bool validDate (std::string&, Config&);
+void loadCustomReports (Config&);
+bool isCustomReport (const std::string&);
+void allCustomReports (std::vector &);
// task.cpp
void gatherNextTasks (const TDB&, T&, Config&, std::vector &, std::vector &);
@@ -67,29 +70,27 @@ bool generateDueDates (T&, std::vector &);
Date getNextRecurrence (Date&, std::string&);
void updateRecurrenceMask (TDB&, std::vector &, T&);
void onChangeCallback ();
-std::string runTaskCommand (int, char**, TDB&, Config&, bool gc = true);
-std::string runTaskCommand (std::vector &, TDB&, Config&, bool gc = false);
+std::string runTaskCommand (int, char**, TDB&, Config&, bool gc = true, bool shadow = true);
+std::string runTaskCommand (std::vector &, TDB&, Config&, bool gc = false, bool shadow = false);
// command.cpp
-void handleAdd (TDB&, T&, Config&);
-void handleExport (TDB&, T&, Config&);
-void handleDone (TDB&, T&, Config&);
-void handleModify (TDB&, T&, Config&);
+std::string handleAdd (TDB&, T&, Config&);
+std::string handleExport (TDB&, T&, Config&);
+std::string handleDone (TDB&, T&, Config&);
+std::string handleModify (TDB&, T&, Config&);
std::string handleProjects (TDB&, T&, Config&);
std::string handleTags (TDB&, T&, Config&);
std::string handleUndelete (TDB&, T&, Config&);
std::string handleVersion (Config&);
std::string handleDelete (TDB&, T&, Config&);
std::string handleStart (TDB&, T&, Config&);
+std::string handleStop (TDB&, T&, Config&);
std::string handleUndo (TDB&, T&, Config&);
std::string handleColor (Config&);
// report.cpp
void filter (std::vector&, T&);
-std::string handleList (TDB&, T&, Config&);
std::string handleInfo (TDB&, T&, Config&);
-std::string handleLongList (TDB&, T&, Config&);
-std::string handleSmallList (TDB&, T&, Config&);
std::string handleCompleted (TDB&, T&, Config&);
std::string handleReportSummary (TDB&, T&, Config&);
std::string handleReportNext (TDB&, T&, Config&);
@@ -99,8 +100,10 @@ std::string handleReportCalendar (TDB&, T&, Config&);
std::string handleReportActive (TDB&, T&, Config&);
std::string handleReportOverdue (TDB&, T&, Config&);
std::string handleReportStats (TDB&, T&, Config&);
-std::string handleReportOldest (TDB&, T&, Config&);
-std::string handleReportNewest (TDB&, T&, Config&);
+
+std::string handleCustomReport (TDB&, T&, Config&, const std::string&);
+void validReportColumns (const std::vector &);
+void validSortColumns (const std::vector &, const std::vector &);
// util.cpp
bool confirm (const std::string&);
@@ -122,11 +125,20 @@ void formatTimeDeltaDays (std::string&, time_t);
std::string formatSeconds (time_t);
const std::string uuid ();
const char* optionalBlankLine (Config&);
-int convertDuration (std::string&);
+int convertDuration (const std::string&);
std::string expandPath (const std::string&);
+#ifdef SOLARIS
+ #define LOCK_SH 1
+ #define LOCK_EX 2
+ #define LOCK_NB 4
+ #define LOCK_UN 8
+
+ int flock (int, int);
+#endif
+
// rules.cpp
void initializeColorRules (Config&);
-void autoColorize (T&, Text::color&, Text::color&);
+void autoColorize (T&, Text::color&, Text::color&, Config&);
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tests/.gitignore b/src/tests/.gitignore
index 1445bb223..94ab5cd82 100644
--- a/src/tests/.gitignore
+++ b/src/tests/.gitignore
@@ -1,7 +1,6 @@
t.t
+t.benchmark.t
tdb.t
date.t
duration.t
-pending.data
-completed.data
diff --git a/src/tests/Makefile b/src/tests/Makefile
index c6c3ac3d8..b98b58bd4 100644
--- a/src/tests/Makefile
+++ b/src/tests/Makefile
@@ -1,4 +1,4 @@
-PROJECT = t.t tdb.t date.t duration.t
+PROJECT = t.t tdb.t date.t duration.t t.benchmark.t
CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti
LFLAGS = -L/usr/local/lib
OBJECTS = ../TDB.o ../T.o ../parse.o ../text.o ../Date.o ../util.o ../Config.o
@@ -29,3 +29,6 @@ date.t: date.t.o $(OBJECTS) test.o
duration.t: duration.t.o $(OBJECTS) test.o
g++ duration.t.o $(OBJECTS) test.o $(LFLAGS) -o duration.t
+t.benchmark.t: t.benchmark.t.o $(OBJECTS) test.o
+ g++ t.benchmark.t.o $(OBJECTS) test.o $(LFLAGS) -o t.benchmark.t
+
diff --git a/src/tests/abbreviation.t b/src/tests/abbreviation.t
new file mode 100755
index 000000000..4f798c0ee
--- /dev/null
+++ b/src/tests/abbreviation.t
@@ -0,0 +1,99 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 22;
+
+# Create the rc file.
+if (open my $fh, '>', 'abbrev.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'abbrev.rc', 'Created abbrev.rc');
+}
+
+# Test the priority attribute abbrevations.
+qx{../task rc:abbrev.rc add priority:H with};
+qx{../task rc:abbrev.rc add without};
+
+my $output = qx{../task rc:abbrev.rc list priority:H};
+like ($output, qr/\bwith\b/, 'priority:H with');
+unlike ($output, qr/\bwithout\b/, 'priority:H without');
+
+$output = qx{../task rc:abbrev.rc list priorit:H};
+like ($output, qr/\bwith\b/, 'priorit:H with');
+unlike ($output, qr/\bwithout\b/, 'priorit:H without');
+
+$output = qx{../task rc:abbrev.rc list priori:H};
+like ($output, qr/\bwith\b/, 'priori:H with');
+unlike ($output, qr/\bwithout\b/, 'priori:H without');
+
+$output = qx{../task rc:abbrev.rc list prior:H};
+like ($output, qr/\bwith\b/, 'prior:H with');
+unlike ($output, qr/\bwithout\b/, 'prior:H without');
+
+$output = qx{../task rc:abbrev.rc list prio:H};
+like ($output, qr/\bwith\b/, 'prio:H with');
+unlike ($output, qr/\bwithout\b/, 'prio:H without');
+
+$output = qx{../task rc:abbrev.rc list pri:H};
+like ($output, qr/\bwith\b/, 'pri:H with');
+unlike ($output, qr/\bwithout\b/, 'pri:H without');
+
+# Test the version command abbreviations.
+$output = qx{../task rc:abbrev.rc version};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version');
+
+$output = qx{../task rc:abbrev.rc versio};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'versio');
+
+$output = qx{../task rc:abbrev.rc versi};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'versi');
+
+$output = qx{../task rc:abbrev.rc vers};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'vers');
+
+$output = qx{../task rc:abbrev.rc ver};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'ver');
+
+$output = qx{../task rc:abbrev.rc ve};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 've');
+
+$output = qx{../task rc:abbrev.rc v};
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'v');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'abbrev.rc';
+ok (!-r 'abbrev.rc', 'Removed abbrev.rc');
+
+exit 0;
+
diff --git a/src/tests/add.t b/src/tests/add.t
new file mode 100755
index 000000000..db6f5c553
--- /dev/null
+++ b/src/tests/add.t
@@ -0,0 +1,72 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 14;
+
+# Create the rc file.
+if (open my $fh, '>', 'add.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'add.rc', 'Created add.rc');
+}
+
+# Test the add command.
+my $output = qx{../task rc:add.rc add This is a test; ../task rc:add.rc info 1};
+like ($output, qr/ID\s+1\n/, 'add ID');
+like ($output, qr/Description\s+This is a test\n/, 'add ID');
+like ($output, qr/Status\s+Pending\n/, 'add Pending');
+like ($output, qr/UUID\s+[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\n/, 'add UUID');
+
+# Test the /// modifier.
+$output = qx{../task rc:add.rc 1 /test/TEST/; ../task rc:add.rc 1 "/is //"; ../task rc:add.rc info 1};
+like ($output, qr/ID\s+1\n/, 'add ID');
+like ($output, qr/Status\s+Pending\n/, 'add Pending');
+like ($output, qr/Description\s+This a TEST\n/, 'add ID');
+
+# Test delete.
+$output = qx{../task rc:add.rc delete 1; ../task rc:add.rc info 1};
+like ($output, qr/ID\s+1\n/, 'add ID');
+like ($output, qr/Status\s+Deleted\n/, 'add Deleted');
+
+# Test undelete.
+$output = qx{../task rc:add.rc undelete 1; ../task rc:add.rc info 1};
+like ($output, qr/ID\s+1\n/, 'add ID');
+like ($output, qr/Status\s+Pending\n/, 'add Pending');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'add.rc';
+ok (!-r 'add.rc', 'Removed add.rc');
+
+exit 0;
+
diff --git a/src/tests/basic.t b/src/tests/basic.t
new file mode 100755
index 000000000..e369b773d
--- /dev/null
+++ b/src/tests/basic.t
@@ -0,0 +1,57 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 7;
+
+# Create the rc file.
+if (open my $fh, '>', 'basic.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'basic.rc', 'Created basic.rc');
+}
+
+# Test the usage command.
+my $output = qx{../task rc:basic.rc};
+like ($output, qr/Usage: task/, 'usage');
+like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'usage - url');
+
+# Test the version command.
+$output = qx{../task rc:basic.rc version};
+like ($output, qr/task \d+\.\d+\.\d+/, 'version - task version number');
+like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version - warranty');
+like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'version - url');
+
+# Cleanup.
+unlink 'basic.rc';
+ok (!-r 'basic.rc', 'Removed basic.rc');
+
+exit 0;
+
diff --git a/src/tests/benchmark.t b/src/tests/benchmark.t
new file mode 100755
index 000000000..73088a4a0
--- /dev/null
+++ b/src/tests/benchmark.t
@@ -0,0 +1,103 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 4;
+
+# Create the rc file.
+if (open my $fh, '>', 'bench.rc')
+{
+ print $fh "data.location=.\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'bench.rc', 'Created bench.rc');
+}
+
+# Do lots of things. Time it all.
+
+my @tags = qw(t_one t_two t_three t_four t_five t_six t_seven t_eight);
+my @projects = qw(p_one p_two p_three p_foud p_five p_six p_seven p_eight);
+my @priorities = qw(H M L);
+my $description = 'This is a medium-sized description with no special characters';
+
+# Start the clock.
+my $start = time ();
+my $cursor = $start;
+diag ("start=$start");
+
+# Make a mess.
+for my $i (1 .. 1000)
+{
+ my $project = $projects[rand % 8];
+ my $priority = $priorities[rand % 3];
+ my $tag = $tags[rand % 8];
+
+ qx{../task rc:bench.rc add project:$project priority:$priority +$tag $i $description};
+}
+diag ("1000 tasks added in " . (time () - $cursor) . " seconds");
+$cursor = time ();
+
+qx{../task rc:bench.rc /with/WITH/} for 1 .. 200;
+qx{../task rc:bench.rc done $_} for 201 .. 400;
+qx{../task rc:bench.rc start $_} for 401 .. 600;
+diag ("600 tasks altered in " . (time () - $cursor) . " seconds");
+$cursor = time ();
+
+# Report it all. Note that all Timer information is displayed.
+
+for (1 .. 100)
+{
+ diag (grep {/^Timer /} qx{../task rc:bench.rc ls});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc list});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc list priority:H});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc list +tag});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc list project_A});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc long});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc completed});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc history});
+ diag (grep {/^Timer /} qx{../task rc:bench.rc ghistory});
+}
+
+# Stop the clock.
+my $stop = time ();
+diag ("stop=$stop");
+diag ("total=" . ($stop - $start));
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'completed.data';
+ok (!-r 'completed.data', 'Removed completed.data');
+
+unlink 'bench.rc';
+ok (!-r 'bench.rc', 'Removed bench.rc');
+
+exit 0;
+
diff --git a/src/tests/benchmark.txt b/src/tests/benchmark.txt
new file mode 100644
index 000000000..c64d3369e
--- /dev/null
+++ b/src/tests/benchmark.txt
@@ -0,0 +1,40 @@
+3/8/2009
+ Before:
+ Table::render
+ 26.1792
+ 16.67
+ 18.9697
+ 28.6328
+ 1.86553
+ 0.00044
+ 0.000319
+ ---------
+ 92.317989
+
+ After Table::optimize removed:
+ Table::render
+ 0.146177
+ 0.145928
+ 0.184444
+ 0.014784
+ 0.000512
+ 0.000267
+ ---------
+ 0.492112
+
+ Speedup:
+ 92.317989 / 0.492112 = 187.6
+
+3/8/2009
+ New benchmark:
+ 1..4
+ ok 1 - Created bench.rc
+ # start=1236565862
+ # 1000 tasks added in 3 seconds
+ # 600 tasks altered in 28 seconds
+ # stop=1236566048
+ # total=186
+ ok 2 - Removed pending.data
+ ok 3 - Removed completed.data
+ ok 4 - Removed bench.rc
+
diff --git a/src/tests/bug.concat.t b/src/tests/bug.concat.t
new file mode 100755
index 000000000..74a82896f
--- /dev/null
+++ b/src/tests/bug.concat.t
@@ -0,0 +1,66 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'bug_concat.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'bug_concat.rc', 'Created bug_concat.rc');
+}
+
+# When a task is modified like this:
+#
+# % task 1 This is a new description
+#
+# The arguments are concatenated thus:
+#
+# Thisisanewdescription
+
+qx{../task rc:bug_concat.rc add This is the original text};
+
+my $output = qx{../task rc:bug_concat.rc info 1};
+like ($output, qr/Description\s+This is the original text\n/, 'original correct');
+
+qx{../task rc:bug_concat.rc 1 This is the modified text};
+$output = qx{../task rc:bug_concat.rc info 1};
+like ($output, qr/Description\s+This is the modified text\n/, 'modified correct');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'bug_concat.rc';
+ok (!-r 'bug_concat.rc', 'Removed bug_concat.rc');
+
+exit 0;
+
diff --git a/src/tests/bug.hang.t b/src/tests/bug.hang.t
new file mode 100755
index 000000000..dbe229919
--- /dev/null
+++ b/src/tests/bug.hang.t
@@ -0,0 +1,83 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'hang.rc')
+{
+ print $fh "data.location=.\n",
+ "shadow.file=shadow.txt\n",
+ "shadow.command=list\n";
+ close $fh;
+ ok (-r 'hang.rc', 'Created hang.rc');
+}
+
+=pod
+I found a bug in the current version of task. Using recur and a shadow file will
+lead to an infinite loop. To reproduce it, define a shadow file in the .taskrc,
+set a command for it that rebuilds the database, e.g. "list", and then add a
+task with a recurrence set, e.g. "task add due:today recur:1d infinite loop".
+Task will then loop forever and add the same recurring task until it runs out of
+memory. So I checked the source and I believe I found the cause.
+handleRecurrence() in task.cpp will modify the mask, but writes it only after it
+has added all new tasks. Adding the task will, however, invoke onChangeCallback,
+which starts the same process all over again.
+=cut
+
+eval
+{
+ $SIG{'ALRM'} = sub {die "alarm\n"};
+ alarm 10;
+ my $output = qx{../task rc:hang.rc list;
+ ../task rc:hang.rc add due:today recur:1d infinite loop;
+ ../task rc:hang.rc info 1};
+ alarm 0;
+
+ like ($output, qr/^Description\s+infinite loop\n/m, 'no hang');
+};
+
+if ($@ eq "alarm\n")
+{
+ fail ('task hang on add or recurring task, with shadow file, for 10s');
+}
+
+# Cleanup.
+unlink 'shadow.txt';
+ok (!-r 'shadow.txt', 'Removed shadow.txt');
+
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'hang.rc';
+ok (!-r 'hang.rc', 'Removed hang.rc');
+
+exit 0;
+
diff --git a/src/tests/bug.period.t b/src/tests/bug.period.t
new file mode 100755
index 000000000..dff6888ff
--- /dev/null
+++ b/src/tests/bug.period.t
@@ -0,0 +1,164 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 41;
+
+# Create the rc file.
+if (open my $fh, '>', 'period.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'period.rc', 'Created period.rc');
+}
+
+=pod
+http://github.com/pbeckingham/task/blob/857f813a24f7ce15fea9f2c28aadad84cb5c8847/src/task.cpp
+619 // If the period is an 'easy' one, add it to current, and we're done.
+620 int days = convertDuration (period);
+
+Date getNextRecurrence (Date& current, std::string& period)
+
+starting at line 509 special cases several possibilities for period, '\d\s?m'
+'monthly', 'quarterly', 'semiannual', 'bimonthly', 'biannual', 'biyearly'.
+Everything else falls through with period being passed to convertDuration.
+convertDuration doesn't know about 'daily' though so it seems to be returning 0.
+
+Confirmed:
+ getNextRecurrence convertDuration
+ ----------------- ---------------
+ daily
+ day
+ weekly
+ sennight
+ biweekly
+ fortnight
+ monthly monthly
+ quarterly quarterly
+ semiannual semiannual
+ bimonthly bimonthly
+ biannual biannual
+ biyearly biyearly
+ annual
+ yearly
+ *m *m
+ *q *q
+ *d
+ *w
+ *y
+=cut
+
+my $output = qx{../task rc:period.rc add daily due:tomorrow recur:daily};
+like ($output, qr/^$/, 'recur:daily');
+
+$output = qx{../task rc:period.rc add day due:tomorrow recur:day};
+like ($output, qr/^$/, 'recur:day');
+
+$output = qx{../task rc:period.rc add weekly due:tomorrow recur:weekly};
+like ($output, qr/^$/, 'recur:weekly');
+
+$output = qx{../task rc:period.rc add sennight due:tomorrow recur:sennight};
+like ($output, qr/^$/, 'recur:sennight');
+
+$output = qx{../task rc:period.rc add biweekly due:tomorrow recur:biweekly};
+like ($output, qr/^$/, 'recur:biweekly');
+
+$output = qx{../task rc:period.rc add fortnight due:tomorrow recur:fortnight};
+like ($output, qr/^$/, 'recur:fortnight');
+
+$output = qx{../task rc:period.rc add monthly due:tomorrow recur:monthly};
+like ($output, qr/^$/, 'recur:monthly');
+
+$output = qx{../task rc:period.rc add quarterly due:tomorrow recur:quarterly};
+like ($output, qr/^$/, 'recur:quarterly');
+
+$output = qx{../task rc:period.rc add semiannual due:tomorrow recur:semiannual};
+like ($output, qr/^$/, 'recur:semiannual');
+
+$output = qx{../task rc:period.rc add bimonthly due:tomorrow recur:bimonthly};
+like ($output, qr/^$/, 'recur:bimonthly');
+
+$output = qx{../task rc:period.rc add biannual due:tomorrow recur:biannual};
+like ($output, qr/^$/, 'recur:biannual');
+
+$output = qx{../task rc:period.rc add biyearly due:tomorrow recur:biyearly};
+like ($output, qr/^$/, 'recur:biyearly');
+
+$output = qx{../task rc:period.rc add annual due:tomorrow recur:annual};
+like ($output, qr/^$/, 'recur:annual');
+
+$output = qx{../task rc:period.rc add yearly due:tomorrow recur:yearly};
+like ($output, qr/^$/, 'recur:yearly');
+
+$output = qx{../task rc:period.rc add 2d due:tomorrow recur:2d};
+like ($output, qr/^$/, 'recur:2m');
+
+$output = qx{../task rc:period.rc add 2w due:tomorrow recur:2w};
+like ($output, qr/^$/, 'recur:2q');
+
+$output = qx{../task rc:period.rc add 2m due:tomorrow recur:2m};
+like ($output, qr/^$/, 'recur:2d');
+
+$output = qx{../task rc:period.rc add 2q due:tomorrow recur:2q};
+like ($output, qr/^$/, 'recur:2w');
+
+$output = qx{../task rc:period.rc add 2y due:tomorrow recur:2y};
+like ($output, qr/^$/, 'recur:2y');
+
+# Verify that the recurring task instances get created. One of each.
+$output = qx{../task rc:period.rc list};
+like ($output, qr/\bdaily\b/, 'verify daily');
+like ($output, qr/\bday\b/, 'verify day');
+like ($output, qr/\bweekly\b/, 'verify weekly');
+like ($output, qr/\bsennight\b/, 'verify sennight');
+like ($output, qr/\bbiweekly\b/, 'verify biweekly');
+like ($output, qr/\bfortnight\b/, 'verify fortnight');
+like ($output, qr/\bmonthly\b/, 'verify monthly');
+like ($output, qr/\bquarterly\b/, 'verify quarterly');
+like ($output, qr/\bsemiannual\b/, 'verify semiannual');
+like ($output, qr/\bbimonthly\b/, 'verify bimonthly');
+like ($output, qr/\bbiannual\b/, 'verify biannual');
+like ($output, qr/\bbiyearly\b/, 'verify biyearly');
+like ($output, qr/\bannual\b/, 'verify annual');
+like ($output, qr/\byearly\b/, 'verify yearly');
+like ($output, qr/\b2d\b/, 'verify 2d');
+like ($output, qr/\b2w\b/, 'verify 2w');
+like ($output, qr/\b2m\b/, 'verify 2m');
+like ($output, qr/\b2q\b/, 'verify 2q');
+like ($output, qr/\b2y\b/, 'verify 2y');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'period.rc';
+ok (!-r 'period.rc', 'Removed period.rc');
+
+exit 0;
+
diff --git a/src/tests/bug.sort.t b/src/tests/bug.sort.t
new file mode 100755
index 000000000..c1e4588f5
--- /dev/null
+++ b/src/tests/bug.sort.t
@@ -0,0 +1,61 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'bug_sort.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'bug_sort.rc', 'Created bug_sort.rc');
+}
+
+my $setup = "../task rc:bug_sort.rc add one;"
+ . "../task rc:bug_sort.rc add two;"
+ . "../task rc:bug_sort.rc add three recur:daily due:eom;";
+qx{$setup};
+
+my $output = qx{../task rc:bug_sort.rc list};
+like ($output, qr/three.*(?:one.*two|two.*one)/msi, 'list did not hang');
+
+qx{../task rc:bug_sort.rc 1 priority:H};
+$output = qx{../task rc:bug_sort.rc list};
+like ($output, qr/three.*one.*two/msi, 'list did not hang after pri:H on 1');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'bug_sort.rc';
+ok (!-r 'bug_sort.rc', 'Removed bug_sort.rc');
+
+exit 0;
+
diff --git a/src/tests/bug.summary.t b/src/tests/bug.summary.t
new file mode 100755
index 000000000..c98b58d03
--- /dev/null
+++ b/src/tests/bug.summary.t
@@ -0,0 +1,67 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'summary.rc')
+{
+ print $fh "data.location=.\n",
+ "confirmation=no\n";
+ close $fh;
+ ok (-r 'summary.rc', 'Created summary.rc');
+}
+
+# Add three tasks. Do 1, delete 1, leave 1 pending. Summary should depict a
+# 50% completion.
+qx{../task rc:summary.rc add project:A one};
+qx{../task rc:summary.rc add project:A two};
+qx{../task rc:summary.rc add project:A three};
+qx{../task rc:summary.rc do 1};
+qx{../task rc:summary.rc delete 2};
+my $output = qx{../task rc:summary.rc summary};
+like ($output, qr/A\s+1\s+-\s+50%/, 'summary correctly shows 50% before report');
+
+qx{../task rc:summary.rc list};
+$output = qx{../task rc:summary.rc summary};
+like ($output, qr/A\s+1\s+-\s+50%/, 'summary correctly shows 50% after report');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'completed.data';
+ok (!-r 'completed.data', 'Removed completed.data');
+
+unlink 'summary.rc';
+ok (!-r 'summary.rc', 'Removed summary.rc');
+
+exit 0;
+
diff --git a/src/tests/color.active.t b/src/tests/color.active.t
new file mode 100755
index 000000000..0ad678223
--- /dev/null
+++ b/src/tests/color.active.t
@@ -0,0 +1,60 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.active=red\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add nothing};
+qx{../task rc:color.rc add red};
+qx{../task rc:color.rc start 2};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.active');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.disable.t b/src/tests/color.disable.t
new file mode 100755
index 000000000..c4a0f4a58
--- /dev/null
+++ b/src/tests/color.disable.t
@@ -0,0 +1,58 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.pri.H=red\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add priority:H red};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/red/, 'color.disable - found red');
+unlike ($output, qr/\033\[31m/, 'color.disable - no color red');
+unlike ($output, qr/\033\[0m/, 'color.disable - no color reset');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.due.t b/src/tests/color.due.t
new file mode 100755
index 000000000..af1f0077e
--- /dev/null
+++ b/src/tests/color.due.t
@@ -0,0 +1,59 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.due=red\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add due:eoy nothing};
+qx{../task rc:color.rc add due:tomorrow red};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) \d{1,2}\/\d{1,2}\/\d{4} (?!>\033\[0m) .* nothing /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m/x, 'color.due');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.keyword.t b/src/tests/color.keyword.t
new file mode 100755
index 000000000..4e99e7400
--- /dev/null
+++ b/src/tests/color.keyword.t
@@ -0,0 +1,62 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.keyword.red=red\n",
+ "color.keyword.green=green\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add nothing};
+qx{../task rc:color.rc add red};
+qx{../task rc:color.rc add green};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.keyword.red');
+like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.keyword.green');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.overdue.t b/src/tests/color.overdue.t
new file mode 100755
index 000000000..9555a92cd
--- /dev/null
+++ b/src/tests/color.overdue.t
@@ -0,0 +1,59 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.overdue=red\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add due:tomorrow nothing};
+qx{../task rc:color.rc add due:yesterday red};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) \d{1,2}\/\d{1,2}\/\d{4} (?!>\033\[0m) .* nothing /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m/x, 'color.overdue');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.pri.t b/src/tests/color.pri.t
new file mode 100755
index 000000000..14a2ee42d
--- /dev/null
+++ b/src/tests/color.pri.t
@@ -0,0 +1,66 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 7;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.pri.H=red\n",
+ "color.pri.M=green\n",
+ "color.pri.L=blue\n",
+ "color.pri.none=yellow\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add priority:H red};
+qx{../task rc:color.rc add priority:M green};
+qx{../task rc:color.rc add priority:L blue};
+qx{../task rc:color.rc add yellow};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.pri.H');
+like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.pri.M');
+like ($output, qr/ \033\[34m .* blue .* \033\[0m /x, 'color.pri.L');
+like ($output, qr/ \033\[33m .* yellow .* \033\[0m /x, 'color.pri.none');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.project.t b/src/tests/color.project.t
new file mode 100755
index 000000000..2b90ec59c
--- /dev/null
+++ b/src/tests/color.project.t
@@ -0,0 +1,59 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.project.x=red\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add nothing};
+qx{../task rc:color.rc add project:x red};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.project.red');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.recurring.t b/src/tests/color.recurring.t
new file mode 100755
index 000000000..27990b1aa
--- /dev/null
+++ b/src/tests/color.recurring.t
@@ -0,0 +1,59 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.recurring=red\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add nothing};
+qx{../task rc:color.rc add due:tomorrow recur:1w red};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.recurring');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.tag.t b/src/tests/color.tag.t
new file mode 100755
index 000000000..da870e39e
--- /dev/null
+++ b/src/tests/color.tag.t
@@ -0,0 +1,62 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.tag.red=red\n",
+ "color.tag.green=green\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add nothing};
+qx{../task rc:color.rc add +red red};
+qx{../task rc:color.rc add +green green};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.tag.red');
+like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.tag.green');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/color.tagged.t b/src/tests/color.tagged.t
new file mode 100755
index 000000000..947a467e9
--- /dev/null
+++ b/src/tests/color.tagged.t
@@ -0,0 +1,59 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'color.rc')
+{
+ print $fh "data.location=.\n",
+ "color.tagged=red\n",
+ "_forcecolor=1\n";
+ close $fh;
+ ok (-r 'color.rc', 'Created color.rc');
+}
+
+# Test the add command.
+qx{../task rc:color.rc add nothing};
+qx{../task rc:color.rc add +tag red};
+my $output = qx{../task rc:color.rc list};
+
+like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none');
+like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.tagged');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'color.rc';
+ok (!-r 'color.rc', 'Removed color.rc');
+
+exit 0;
+
diff --git a/src/tests/completed.t b/src/tests/completed.t
new file mode 100755
index 000000000..a53c7e652
--- /dev/null
+++ b/src/tests/completed.t
@@ -0,0 +1,64 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'completed.rc')
+{
+ print $fh "data.location=.\n",
+ "confirmation=no\n";
+ close $fh;
+ ok (-r 'completed.rc', 'Created completed.rc');
+}
+
+# Add two tasks, mark 1 as done, the other as deleted.
+qx{../task rc:completed.rc add one};
+qx{../task rc:completed.rc add two};
+qx{../task rc:completed.rc 1 done};
+qx{../task rc:completed.rc 2 delete};
+
+# Generate completed report.
+my $output = qx{../task rc:completed.rc completed};
+like ($output, qr/one/, 'one -> completed');
+unlike ($output, qr/two/, 'two -> deleted');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'completed.data';
+ok (!-r 'completed.data', 'Removed completed.data');
+
+unlink 'completed.rc';
+ok (!-r 'completed.rc', 'Removed completed.rc');
+
+exit 0;
+
diff --git a/src/tests/config.obsolete.t b/src/tests/config.obsolete.t
new file mode 100755
index 000000000..7a5f34bc1
--- /dev/null
+++ b/src/tests/config.obsolete.t
@@ -0,0 +1,57 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'obsolete.rc')
+{
+ print $fh "data.location=.\n",
+ "foo=1\n";
+ close $fh;
+ ok (-r 'obsolete.rc', 'Created obsolete.rc');
+}
+
+# Test the add command.
+my $output = qx{../task rc:obsolete.rc version};
+
+like ($output, qr/Your .taskrc file contains these unrecognized variables:\n/,
+ 'unsupported configuration variable');
+like ($output, qr/ foo\n/, 'unsupported configuration variable');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'obsolete.rc';
+ok (!-r 'obsolete.rc', 'Removed obsolete.rc');
+
+exit 0;
+
diff --git a/src/tests/custom.columns.t b/src/tests/custom.columns.t
new file mode 100755
index 000000000..ab1927b65
--- /dev/null
+++ b/src/tests/custom.columns.t
@@ -0,0 +1,57 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 4;
+
+# Create the rc file.
+if (open my $fh, '>', 'custom.rc')
+{
+ print $fh "data.location=.\n",
+ "report.foo.description=DESC\n",
+ "report.foo.columns=id,foo,description\n",
+ "report.foo.sort=id+\n",
+ "report.foo.filter=project:A\n";
+ close $fh;
+ ok (-r 'custom.rc', 'Created custom.rc');
+}
+
+# Generate the usage screen, and locate the custom report on it.
+my $output = qx{../task rc:custom.rc foo 2>&1};
+like ($output, qr/Unrecognized column name: foo\n/, 'custom report spotted invalid column');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'custom.rc';
+ok (!-r 'custom.rc', 'Removed custom.rc');
+
+exit 0;
+
diff --git a/src/tests/custom.t b/src/tests/custom.t
new file mode 100755
index 000000000..5ca345aa0
--- /dev/null
+++ b/src/tests/custom.t
@@ -0,0 +1,63 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'custom.rc')
+{
+ print $fh "data.location=.\n",
+ "report.foo.description=DESC\n",
+ "report.foo.columns=id,description\n",
+ "report.foo.sort=id+\n",
+ "report.foo.filter=project:A\n";
+ close $fh;
+ ok (-r 'custom.rc', 'Created custom.rc');
+}
+
+# Generate the usage screen, and locate the custom report on it.
+my $output = qx{../task rc:custom.rc usage};
+like ($output, qr/task foo \[tags\] \[attrs\] desc\.\.\.\s+DESC\n/m, 'report.foo');
+
+qx{../task rc:custom.rc add project:A one};
+qx{../task rc:custom.rc add two};
+$output = qx{../task rc:custom.rc foo};
+like ($output, qr/one/, 'custom filter included');
+unlike ($output, qr/two/, 'custom filter excluded');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'custom.rc';
+ok (!-r 'custom.rc', 'Removed custom.rc');
+
+exit 0;
+
diff --git a/src/tests/date.t.cpp b/src/tests/date.t.cpp
index fe830f5aa..517313576 100644
--- a/src/tests/date.t.cpp
+++ b/src/tests/date.t.cpp
@@ -1,5 +1,27 @@
////////////////////////////////////////////////////////////////////////////////
-// Copyright 2005 - 2008, Paul Beckingham. All rights reserved.
+// task - a command line task list manager.
+//
+// Copyright 2006 - 2009, Paul Beckingham.
+// All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation; either version 2 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the
+//
+// Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor,
+// Boston, MA
+// 02110-1301
+// USA
//
////////////////////////////////////////////////////////////////////////////////
#include
@@ -9,7 +31,7 @@
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
- plan (100);
+ UnitTest t (100);
try
{
@@ -17,207 +39,207 @@ int main (int argc, char** argv)
Date yesterday;
yesterday -= 1;
- ok (yesterday <= now, "yesterday <= now");
- ok (yesterday < now, "yesterday < now");
- notok (yesterday == now, "!(yesterday == now)");
- ok (yesterday != now, "yesterday != now");
- ok (now >= yesterday, "now >= yesterday");
- ok (now > yesterday, "now > yesterday");
+ t.ok (yesterday <= now, "yesterday <= now");
+ t.ok (yesterday < now, "yesterday < now");
+ t.notok (yesterday == now, "!(yesterday == now)");
+ t.ok (yesterday != now, "yesterday != now");
+ t.ok (now >= yesterday, "now >= yesterday");
+ t.ok (now > yesterday, "now > yesterday");
// Loose comparisons.
Date left ("7/4/2008");
Date comp1 ("7/4/2008");
- ok (left.sameDay (comp1), "7/4/2008 is on the same day as 7/4/2008");
- ok (left.sameMonth (comp1), "7/4/2008 is in the same month as 7/4/2008");
- ok (left.sameYear (comp1), "7/4/2008 is in the same year as 7/4/2008");
+ t.ok (left.sameDay (comp1), "7/4/2008 is on the same day as 7/4/2008");
+ t.ok (left.sameMonth (comp1), "7/4/2008 is in the same month as 7/4/2008");
+ t.ok (left.sameYear (comp1), "7/4/2008 is in the same year as 7/4/2008");
Date comp2 ("7/5/2008");
- notok (left.sameDay (comp2), "7/4/2008 is not on the same day as 7/5/2008");
- ok (left.sameMonth (comp2), "7/4/2008 is in the same month as 7/5/2008");
- ok (left.sameYear (comp2), "7/4/2008 is in the same year as 7/5/2008");
+ t.notok (left.sameDay (comp2), "7/4/2008 is not on the same day as 7/5/2008");
+ t.ok (left.sameMonth (comp2), "7/4/2008 is in the same month as 7/5/2008");
+ t.ok (left.sameYear (comp2), "7/4/2008 is in the same year as 7/5/2008");
Date comp3 ("8/4/2008");
- notok (left.sameDay (comp3), "7/4/2008 is not on the same day as 8/4/2008");
- notok (left.sameMonth (comp3), "7/4/2008 is not in the same month as 8/4/2008");
- ok (left.sameYear (comp3), "7/4/2008 is in the same year as 8/4/2008");
+ t.notok (left.sameDay (comp3), "7/4/2008 is not on the same day as 8/4/2008");
+ t.notok (left.sameMonth (comp3), "7/4/2008 is not in the same month as 8/4/2008");
+ t.ok (left.sameYear (comp3), "7/4/2008 is in the same year as 8/4/2008");
Date comp4 ("7/4/2009");
- notok (left.sameDay (comp4), "7/4/2008 is not on the same day as 7/4/2009");
- notok (left.sameMonth (comp4), "7/4/2008 is not in the same month as 7/4/2009");
- notok (left.sameYear (comp4), "7/4/2008 is not in the same year as 7/4/2009");
+ t.notok (left.sameDay (comp4), "7/4/2008 is not on the same day as 7/4/2009");
+ t.notok (left.sameMonth (comp4), "7/4/2008 is not in the same month as 7/4/2009");
+ t.notok (left.sameYear (comp4), "7/4/2008 is not in the same year as 7/4/2009");
// Validity.
- ok (Date::valid (2, 29, 2008), "valid: 2/29/2008");
- notok (Date::valid (2, 29, 2007), "invalid: 2/29/2007");
+ t.ok (Date::valid (2, 29, 2008), "valid: 2/29/2008");
+ t.notok (Date::valid (2, 29, 2007), "invalid: 2/29/2007");
// Leap year.
- ok (Date::leapYear (2008), "2008 is a leap year");
- notok (Date::leapYear (2007), "2007 is not a leap year");
- ok (Date::leapYear (2000), "2000 is a leap year");
- ok (Date::leapYear (1900), "1900 is a leap year");
+ t.ok (Date::leapYear (2008), "2008 is a leap year");
+ t.notok (Date::leapYear (2007), "2007 is not a leap year");
+ t.ok (Date::leapYear (2000), "2000 is a leap year");
+ t.ok (Date::leapYear (1900), "1900 is a leap year");
// Days in month.
- is (Date::daysInMonth (2, 2008), 29, "29 days in February 2008");
- is (Date::daysInMonth (2, 2007), 28, "28 days in February 2007");
+ t.is (Date::daysInMonth (2, 2008), 29, "29 days in February 2008");
+ t.is (Date::daysInMonth (2, 2007), 28, "28 days in February 2007");
// Names.
- is (Date::monthName (1), "January", "1 = January");
- is (Date::monthName (2), "February", "2 = February");
- is (Date::monthName (3), "March", "3 = March");
- is (Date::monthName (4), "April", "4 = April");
- is (Date::monthName (5), "May", "5 = May");
- is (Date::monthName (6), "June", "6 = June");
- is (Date::monthName (7), "July", "7 = July");
- is (Date::monthName (8), "August", "8 = August");
- is (Date::monthName (9), "September", "9 = September");
- is (Date::monthName (10), "October", "10 = October");
- is (Date::monthName (11), "November", "11 = November");
- is (Date::monthName (12), "December", "12 = December");
+ t.is (Date::monthName (1), "January", "1 = January");
+ t.is (Date::monthName (2), "February", "2 = February");
+ t.is (Date::monthName (3), "March", "3 = March");
+ t.is (Date::monthName (4), "April", "4 = April");
+ t.is (Date::monthName (5), "May", "5 = May");
+ t.is (Date::monthName (6), "June", "6 = June");
+ t.is (Date::monthName (7), "July", "7 = July");
+ t.is (Date::monthName (8), "August", "8 = August");
+ t.is (Date::monthName (9), "September", "9 = September");
+ t.is (Date::monthName (10), "October", "10 = October");
+ t.is (Date::monthName (11), "November", "11 = November");
+ t.is (Date::monthName (12), "December", "12 = December");
- is (Date::dayName (0), "Sunday", "0 == Sunday");
- is (Date::dayName (1), "Monday", "1 == Monday");
- is (Date::dayName (2), "Tuesday", "2 == Tuesday");
- is (Date::dayName (3), "Wednesday", "3 == Wednesday");
- is (Date::dayName (4), "Thursday", "4 == Thursday");
- is (Date::dayName (5), "Friday", "5 == Friday");
- is (Date::dayName (6), "Saturday", "6 == Saturday");
+ t.is (Date::dayName (0), "Sunday", "0 == Sunday");
+ t.is (Date::dayName (1), "Monday", "1 == Monday");
+ t.is (Date::dayName (2), "Tuesday", "2 == Tuesday");
+ t.is (Date::dayName (3), "Wednesday", "3 == Wednesday");
+ t.is (Date::dayName (4), "Thursday", "4 == Thursday");
+ t.is (Date::dayName (5), "Friday", "5 == Friday");
+ t.is (Date::dayName (6), "Saturday", "6 == Saturday");
- is (Date::dayOfWeek ("SUNDAY"), 0, "SUNDAY == 0");
- is (Date::dayOfWeek ("sunday"), 0, "sunday == 0");
- is (Date::dayOfWeek ("Sunday"), 0, "Sunday == 0");
- is (Date::dayOfWeek ("Monday"), 1, "Monday == 1");
- is (Date::dayOfWeek ("Tuesday"), 2, "Tuesday == 2");
- is (Date::dayOfWeek ("Wednesday"), 3, "Wednesday == 3");
- is (Date::dayOfWeek ("Thursday"), 4, "Thursday == 4");
- is (Date::dayOfWeek ("Friday"), 5, "Friday == 5");
- is (Date::dayOfWeek ("Saturday"), 6, "Saturday == 6");
+ t.is (Date::dayOfWeek ("SUNDAY"), 0, "SUNDAY == 0");
+ t.is (Date::dayOfWeek ("sunday"), 0, "sunday == 0");
+ t.is (Date::dayOfWeek ("Sunday"), 0, "Sunday == 0");
+ t.is (Date::dayOfWeek ("Monday"), 1, "Monday == 1");
+ t.is (Date::dayOfWeek ("Tuesday"), 2, "Tuesday == 2");
+ t.is (Date::dayOfWeek ("Wednesday"), 3, "Wednesday == 3");
+ t.is (Date::dayOfWeek ("Thursday"), 4, "Thursday == 4");
+ t.is (Date::dayOfWeek ("Friday"), 5, "Friday == 5");
+ t.is (Date::dayOfWeek ("Saturday"), 6, "Saturday == 6");
Date happyNewYear (1, 1, 2008);
- is (happyNewYear.dayOfWeek (), 2, "1/1/2008 == Tuesday");
- is (happyNewYear.month (), 1, "1/1/2008 == January");
- is (happyNewYear.day (), 1, "1/1/2008 == 1");
- is (happyNewYear.year (), 2008, "1/1/2008 == 2008");
+ t.is (happyNewYear.dayOfWeek (), 2, "1/1/2008 == Tuesday");
+ t.is (happyNewYear.month (), 1, "1/1/2008 == January");
+ t.is (happyNewYear.day (), 1, "1/1/2008 == 1");
+ t.is (happyNewYear.year (), 2008, "1/1/2008 == 2008");
- is (now - yesterday, 1, "today - yesterday == 1");
+ t.is (now - yesterday, 1, "today - yesterday == 1");
- is (happyNewYear.toString (), "1/1/2008", "toString 1/1/2008");
+ t.is (happyNewYear.toString (), "1/1/2008", "toString 1/1/2008");
int m, d, y;
happyNewYear.toMDY (m, d, y);
- is (m, 1, "1/1/2008 == January");
- is (d, 1, "1/1/2008 == 1");
- is (y, 2008, "1/1/2008 == 2008");
+ t.is (m, 1, "1/1/2008 == January");
+ t.is (d, 1, "1/1/2008 == 1");
+ t.is (y, 2008, "1/1/2008 == 2008");
Date epoch (9, 8, 2001);
- ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000");
+ t.ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000");
epoch += 86400;
- ok ((int)epoch.toEpoch () > 1000000000, "9/9/2001 > 1,000,000,000");
+ t.ok ((int)epoch.toEpoch () > 1000000000, "9/9/2001 > 1,000,000,000");
Date fromEpoch (epoch.toEpoch ());
- is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)");
+ t.is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)");
// Date parsing.
Date fromString1 ("1/1/2008");
- is (fromString1.month (), 1, "ctor (std::string) -> m");
- is (fromString1.day (), 1, "ctor (std::string) -> d");
- is (fromString1.year (), 2008, "ctor (std::string) -> y");
+ t.is (fromString1.month (), 1, "ctor (std::string) -> m");
+ t.is (fromString1.day (), 1, "ctor (std::string) -> d");
+ t.is (fromString1.year (), 2008, "ctor (std::string) -> y");
Date fromString2 ("1/1/2008", "m/d/Y");
- is (fromString2.month (), 1, "ctor (std::string) -> m");
- is (fromString2.day (), 1, "ctor (std::string) -> d");
- is (fromString2.year (), 2008, "ctor (std::string) -> y");
+ t.is (fromString2.month (), 1, "ctor (std::string) -> m");
+ t.is (fromString2.day (), 1, "ctor (std::string) -> d");
+ t.is (fromString2.year (), 2008, "ctor (std::string) -> y");
Date fromString3 ("20080101", "YMD");
- is (fromString3.month (), 1, "ctor (std::string) -> m");
- is (fromString3.day (), 1, "ctor (std::string) -> d");
- is (fromString3.year (), 2008, "ctor (std::string) -> y");
+ t.is (fromString3.month (), 1, "ctor (std::string) -> m");
+ t.is (fromString3.day (), 1, "ctor (std::string) -> d");
+ t.is (fromString3.year (), 2008, "ctor (std::string) -> y");
Date fromString4 ("12/31/2007");
- is (fromString4.month (), 12, "ctor (std::string) -> m");
- is (fromString4.day (), 31, "ctor (std::string) -> d");
- is (fromString4.year (), 2007, "ctor (std::string) -> y");
+ t.is (fromString4.month (), 12, "ctor (std::string) -> m");
+ t.is (fromString4.day (), 31, "ctor (std::string) -> d");
+ t.is (fromString4.year (), 2007, "ctor (std::string) -> y");
Date fromString5 ("12/31/2007", "m/d/Y");
- is (fromString5.month (), 12, "ctor (std::string) -> m");
- is (fromString5.day (), 31, "ctor (std::string) -> d");
- is (fromString5.year (), 2007, "ctor (std::string) -> y");
+ t.is (fromString5.month (), 12, "ctor (std::string) -> m");
+ t.is (fromString5.day (), 31, "ctor (std::string) -> d");
+ t.is (fromString5.year (), 2007, "ctor (std::string) -> y");
Date fromString6 ("20071231", "YMD");
- is (fromString6.month (), 12, "ctor (std::string) -> m");
- is (fromString6.day (), 31, "ctor (std::string) -> d");
- is (fromString6.year (), 2007, "ctor (std::string) -> y");
+ t.is (fromString6.month (), 12, "ctor (std::string) -> m");
+ t.is (fromString6.day (), 31, "ctor (std::string) -> d");
+ t.is (fromString6.year (), 2007, "ctor (std::string) -> y");
Date fromString7 ("01/01/2008", "m/d/Y");
- is (fromString7.month (), 1, "ctor (std::string) -> m");
- is (fromString7.day (), 1, "ctor (std::string) -> d");
- is (fromString7.year (), 2008, "ctor (std::string) -> y");
+ t.is (fromString7.month (), 1, "ctor (std::string) -> m");
+ t.is (fromString7.day (), 1, "ctor (std::string) -> d");
+ t.is (fromString7.year (), 2008, "ctor (std::string) -> y");
// Relative dates.
Date r1 ("today");
- ok (r1.sameDay (now), "today = now");
+ t.ok (r1.sameDay (now), "today = now");
Date r2 ("tomorrow");
- ok (r2.sameDay (now + 86400), "tomorrow = now + 1d");
+ t.ok (r2.sameDay (now + 86400), "tomorrow = now + 1d");
Date r3 ("yesterday");
- ok (r3.sameDay (now - 86400), "yesterday = now - 1d");
+ t.ok (r3.sameDay (now - 86400), "yesterday = now - 1d");
Date r4 ("sunday");
if (now.dayOfWeek () >= 0)
- ok (r4.sameDay (now + (0 - now.dayOfWeek () + 7) * 86400), "next sunday");
+ t.ok (r4.sameDay (now + (0 - now.dayOfWeek () + 7) * 86400), "next sunday");
else
- ok (r4.sameDay (now + (0 - now.dayOfWeek ()) * 86400), "next sunday");;
+ t.ok (r4.sameDay (now + (0 - now.dayOfWeek ()) * 86400), "next sunday");;
Date r5 ("monday");
if (now.dayOfWeek () >= 1)
- ok (r5.sameDay (now + (1 - now.dayOfWeek () + 7) * 86400), "next monday");
+ t.ok (r5.sameDay (now + (1 - now.dayOfWeek () + 7) * 86400), "next monday");
else
- ok (r5.sameDay (now + (1 - now.dayOfWeek ()) * 86400), "next monday");;
+ t.ok (r5.sameDay (now + (1 - now.dayOfWeek ()) * 86400), "next monday");;
Date r6 ("tuesday");
if (now.dayOfWeek () >= 2)
- ok (r6.sameDay (now + (2 - now.dayOfWeek () + 7) * 86400), "next tuesday");
+ t.ok (r6.sameDay (now + (2 - now.dayOfWeek () + 7) * 86400), "next tuesday");
else
- ok (r6.sameDay (now + (2 - now.dayOfWeek ()) * 86400), "next tuesday");;
+ t.ok (r6.sameDay (now + (2 - now.dayOfWeek ()) * 86400), "next tuesday");;
Date r7 ("wednesday");
if (now.dayOfWeek () >= 3)
- ok (r7.sameDay (now + (3 - now.dayOfWeek () + 7) * 86400), "next wednesday");
+ t.ok (r7.sameDay (now + (3 - now.dayOfWeek () + 7) * 86400), "next wednesday");
else
- ok (r7.sameDay (now + (3 - now.dayOfWeek ()) * 86400), "next wednesday");;
+ t.ok (r7.sameDay (now + (3 - now.dayOfWeek ()) * 86400), "next wednesday");;
Date r8 ("thursday");
if (now.dayOfWeek () >= 4)
- ok (r8.sameDay (now + (4 - now.dayOfWeek () + 7) * 86400), "next thursday");
+ t.ok (r8.sameDay (now + (4 - now.dayOfWeek () + 7) * 86400), "next thursday");
else
- ok (r8.sameDay (now + (4 - now.dayOfWeek ()) * 86400), "next thursday");;
+ t.ok (r8.sameDay (now + (4 - now.dayOfWeek ()) * 86400), "next thursday");;
Date r9 ("friday");
if (now.dayOfWeek () >= 5)
- ok (r9.sameDay (now + (5 - now.dayOfWeek () + 7) * 86400), "next friday");
+ t.ok (r9.sameDay (now + (5 - now.dayOfWeek () + 7) * 86400), "next friday");
else
- ok (r9.sameDay (now + (5 - now.dayOfWeek ()) * 86400), "next friday");;
+ t.ok (r9.sameDay (now + (5 - now.dayOfWeek ()) * 86400), "next friday");;
Date r10 ("saturday");
if (now.dayOfWeek () >= 6)
- ok (r10.sameDay (now + (6 - now.dayOfWeek () + 7) * 86400), "next saturday");
+ t.ok (r10.sameDay (now + (6 - now.dayOfWeek () + 7) * 86400), "next saturday");
else
- ok (r10.sameDay (now + (6 - now.dayOfWeek ()) * 86400), "next saturday");;
+ t.ok (r10.sameDay (now + (6 - now.dayOfWeek ()) * 86400), "next saturday");;
Date r11 ("eow");
- ok (r11 < now + (8 * 86400), "eow < 7 days away");
+ t.ok (r11 < now + (8 * 86400), "eow < 7 days away");
Date r12 ("eom");
- ok (r12.sameMonth (now), "eom in same month as now");
+ t.ok (r12.sameMonth (now), "eom in same month as now");
Date r13 ("eoy");
- ok (r13.sameYear (now), "eoy in same year as now");
+ t.ok (r13.sameYear (now), "eoy in same year as now");
}
catch (std::string& e)
{
- fail ("Exception thrown.");
- diag (e);
+ t.fail ("Exception thrown.");
+ t.diag (e);
}
return 0;
diff --git a/src/tests/dateformat.t b/src/tests/dateformat.t
new file mode 100755
index 000000000..c08cdb884
--- /dev/null
+++ b/src/tests/dateformat.t
@@ -0,0 +1,72 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 8;
+
+# Create the rc file.
+if (open my $fh, '>', 'date1.rc')
+{
+ print $fh "data.location=.\n",
+ "dateformat=YMD\n";
+ close $fh;
+ ok (-r 'date1.rc', 'Created date1.rc');
+}
+
+if (open my $fh, '>', 'date2.rc')
+{
+ print $fh "data.location=.\n",
+ "dateformat=m/d/y\n";
+ close $fh;
+ ok (-r 'date2.rc', 'Created date2.rc');
+}
+
+qx{../task rc:date1.rc add foo due:20091231};
+my $output = qx{../task rc:date1.rc info 1};
+like ($output, qr/\b20091231\b/, 'date format YMD parsed');
+
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+qx{../task rc:date2.rc add foo due:12/1/09};
+$output = qx{../task rc:date2.rc info 1};
+like ($output, qr/\b12\/1\/09\b/, 'date format m/d/y parsed');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'date1.rc';
+ok (!-r 'date1.rc', 'Removed date1.rc');
+
+unlink 'date2.rc';
+ok (!-r 'date2.rc', 'Removed date2.rc');
+
+exit 0;
+
diff --git a/src/tests/default.t b/src/tests/default.t
new file mode 100755
index 000000000..6851b0063
--- /dev/null
+++ b/src/tests/default.t
@@ -0,0 +1,83 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 16;
+
+# Create the rc file.
+if (open my $fh, '>', 'default.rc')
+{
+ print $fh "data.location=.\n",
+ "default.command=list\n",
+ "default.project=PROJECT\n",
+ "default.priority=M\n";
+ close $fh;
+ ok (-r 'default.rc', 'Created default.rc');
+}
+
+# Set up a default command, project and priority.
+qx{../task rc:default.rc add all defaults};
+my $output = qx{../task rc:default.rc list};
+like ($output, qr/ all defaults/, 'task added');
+like ($output, qr/ PROJECT /, 'default project added');
+like ($output, qr/ M /, 'default priority added');
+unlink 'pending.data';
+
+qx{../task rc:default.rc add project:specific priority:L all specified};
+$output = qx{../task rc:default.rc list};
+like ($output, qr/ all specified/, 'task added');
+like ($output, qr/ specific /, 'project specified');
+like ($output, qr/ L /, 'priority specified');
+unlink 'pending.data';
+
+qx{../task rc:default.rc add project:specific project specified};
+$output = qx{../task rc:default.rc list};
+like ($output, qr/ project specified/, 'task added');
+like ($output, qr/ specific /, 'project specified');
+like ($output, qr/ M /, 'default priority added');
+unlink 'pending.data';
+
+qx{../task rc:default.rc add priority:L priority specified};
+$output = qx{../task rc:default.rc list};
+like ($output, qr/ priority specified/, 'task added');
+like ($output, qr/ PROJECT /, 'default project added');
+like ($output, qr/ L /, 'priority specified');
+
+$output = qx{../task rc:default.rc};
+like ($output, qr/1 PROJECT L .+ priority specified/, 'default command worked');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'default.rc';
+ok (!-r 'default.rc', 'Removed default.rc');
+
+exit 0;
+
diff --git a/src/tests/delete.t b/src/tests/delete.t
new file mode 100755
index 000000000..9f36f517b
--- /dev/null
+++ b/src/tests/delete.t
@@ -0,0 +1,77 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 16;
+
+# Create the rc file.
+if (open my $fh, '>', 'undelete.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'undelete.rc', 'Created undelete.rc');
+}
+
+# Add a task, delete it, undelete it.
+my $output = qx{../task rc:undelete.rc add one; ../task rc:undelete.rc info 1};
+ok (-r 'pending.data', 'pending.data created');
+like ($output, qr/Status\s+Pending\n/, 'Pending');
+
+$output = qx{../task rc:undelete.rc delete 1; ../task rc:undelete.rc info 1};
+like ($output, qr/Status\s+Deleted\n/, 'Deleted');
+ok (! -r 'completed.data', 'completed.data not created');
+
+$output = qx{../task rc:undelete.rc undelete 1; ../task rc:undelete.rc info 1};
+like ($output, qr/Status\s+Pending\n/, 'Pending');
+ok (! -r 'completed.data', 'completed.data not created');
+
+$output = qx{../task rc:undelete.rc delete 1; ../task rc:undelete.rc list};
+like ($output, qr/^No matches/, 'No matches');
+ok (-r 'completed.data', 'completed.data created');
+
+$output = qx{../task rc:undelete.rc undelete 1};
+like ($output, qr/reliably undeleted/, 'can only be reliable undeleted...');
+
+$output = qx{../task rc:undelete.rc info 1};
+like ($output, qr/No matches./, 'no matches');
+
+# Cleanup.
+ok (-r 'pending.data', 'Need to remove pending.data');
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+ok (-r 'completed.data', 'Need to remove completed.data');
+unlink 'completed.data';
+ok (!-r 'completed.data', 'Removed completed.data');
+
+unlink 'undelete.rc';
+ok (!-r 'undelete.rc', 'Removed undelete.rc');
+
+exit 0;
+
diff --git a/src/tests/due.t b/src/tests/due.t
new file mode 100755
index 000000000..b320c9563
--- /dev/null
+++ b/src/tests/due.t
@@ -0,0 +1,66 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'due.rc')
+{
+ print $fh "data.location=.\n",
+ "due=4\n",
+ "color=on\n",
+ "color.due=red\n",
+ "_forcecolor=on\n";
+ close $fh;
+ ok (-r 'due.rc', 'Created due.rc');
+}
+
+# Add a task that is almost due, and one that is just due.
+my ($d, $m, $y) = (localtime (time + 3 * 86_400))[3..5];
+my $just = sprintf ("%d/%02d/%d", $m + 1, $d, $y + 1900);
+
+($d, $m, $y) = (localtime (time + 5 * 86_400))[3..5];
+my $almost = sprintf ("%d/%02d/%d", $m + 1, $d, $y + 1900);
+
+qx{../task rc:due.rc add one due:$just};
+qx{../task rc:due.rc add two due:$almost};
+my $output = qx{../task rc:due.rc list};
+like ($output, qr/\[31m.+$just.+\[0m/, 'one marked due');
+like ($output, qr/\s+$almost\s+/, 'two not marked due');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'due.rc';
+ok (!-r 'due.rc', 'Removed due.rc');
+
+exit 0;
+
diff --git a/src/tests/duration.t.cpp b/src/tests/duration.t.cpp
index 584b94458..a50b6a11d 100644
--- a/src/tests/duration.t.cpp
+++ b/src/tests/duration.t.cpp
@@ -1,5 +1,27 @@
////////////////////////////////////////////////////////////////////////////////
-// Copyright 2005 - 2008, Paul Beckingham. All rights reserved.
+// task - a command line task list manager.
+//
+// Copyright 2006 - 2009, Paul Beckingham.
+// All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation; either version 2 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the
+//
+// Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor,
+// Boston, MA
+// 02110-1301
+// USA
//
////////////////////////////////////////////////////////////////////////////////
#include
@@ -16,27 +38,27 @@
// biannual, biyearly, annual, semiannual, yearly, Ny
int main (int argc, char** argv)
{
- plan (17);
+ UnitTest t (17);
std::string d;
- d = "daily"; is (convertDuration (d), 1, "duration daily = 1");
- d = "day"; is (convertDuration (d), 1, "duration day = 1");
- d = "0d"; is (convertDuration (d), 0, "duration 0d = 0");
- d = "1d"; is (convertDuration (d), 1, "duration 1d = 1");
- d = "7d"; is (convertDuration (d), 7, "duration 7d = 7");
- d = "10d"; is (convertDuration (d), 10, "duration 10d = 10");
- d = "100d"; is (convertDuration (d), 100, "duration 100d = 100");
+ d = "daily"; t.is (convertDuration (d), 1, "duration daily = 1");
+ d = "day"; t.is (convertDuration (d), 1, "duration day = 1");
+ d = "0d"; t.is (convertDuration (d), 0, "duration 0d = 0");
+ d = "1d"; t.is (convertDuration (d), 1, "duration 1d = 1");
+ d = "7d"; t.is (convertDuration (d), 7, "duration 7d = 7");
+ d = "10d"; t.is (convertDuration (d), 10, "duration 10d = 10");
+ d = "100d"; t.is (convertDuration (d), 100, "duration 100d = 100");
- d = "weekly"; is (convertDuration (d), 7, "duration weekly = 7");
- d = "sennight"; is (convertDuration (d), 7, "duration sennight = 7");
- d = "biweekly"; is (convertDuration (d), 14, "duration biweekly = 14");
- d = "fortnight"; is (convertDuration (d), 14, "duration fortnight = 14");
- d = "week"; is (convertDuration (d), 7, "duration week = 7");
- d = "0w"; is (convertDuration (d), 0, "duration 0w = 0");
- d = "1w"; is (convertDuration (d), 7, "duration 1w = 7");
- d = "7w"; is (convertDuration (d), 49, "duration 7w = 49");
- d = "10w"; is (convertDuration (d), 70, "duration 10w = 70");
- d = "100w"; is (convertDuration (d), 700, "duration 100w = 700");
+ d = "weekly"; t.is (convertDuration (d), 7, "duration weekly = 7");
+ d = "sennight"; t.is (convertDuration (d), 7, "duration sennight = 7");
+ d = "biweekly"; t.is (convertDuration (d), 14, "duration biweekly = 14");
+ d = "fortnight"; t.is (convertDuration (d), 14, "duration fortnight = 14");
+ d = "week"; t.is (convertDuration (d), 7, "duration week = 7");
+ d = "0w"; t.is (convertDuration (d), 0, "duration 0w = 0");
+ d = "1w"; t.is (convertDuration (d), 7, "duration 1w = 7");
+ d = "7w"; t.is (convertDuration (d), 49, "duration 7w = 49");
+ d = "10w"; t.is (convertDuration (d), 70, "duration 10w = 70");
+ d = "100w"; t.is (convertDuration (d), 700, "duration 100w = 700");
return 0;
}
diff --git a/src/tests/export.t b/src/tests/export.t
new file mode 100755
index 000000000..143a687da
--- /dev/null
+++ b/src/tests/export.t
@@ -0,0 +1,72 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 7;
+
+# Create the rc file.
+if (open my $fh, '>', 'export.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'export.rc', 'Created export.rc');
+}
+
+# Add two tasks, export, examine result.
+qx{../task rc:export.rc add priority:H project:A one};
+qx{../task rc:export.rc add +tag1 +tag2 two};
+qx{../task rc:export.rc export ./export.txt};
+
+my @lines;
+if (open my $fh, '<', './export.txt')
+{
+ @lines = <$fh>;
+ close $fh;
+}
+
+my $line1 = qr/'id','uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description'\n/;
+my $line2 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','',\d+,,,,,'A','H',,,'one'\n/;
+my $line3 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','tag1 tag2',\d+,,,,,,,,,'two'\n/;
+
+like ($lines[0], $line1, "export line one");
+like ($lines[1], $line2, "export line two");
+like ($lines[2], $line3, "export line three");
+
+# Cleanup.
+unlink 'export.txt';
+ok (!-r 'export.txt', 'Removed export.txt');
+
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'export.rc';
+ok (!-r 'export.rc', 'Removed export.rc');
+
+exit 0;
+
diff --git a/src/tests/filter.t b/src/tests/filter.t
new file mode 100755
index 000000000..5fd4e918b
--- /dev/null
+++ b/src/tests/filter.t
@@ -0,0 +1,193 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 108;
+
+# Create the rc file.
+if (open my $fh, '>', 'filter.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'filter.rc', 'Created filter.rc');
+}
+
+# Test the filters.
+qx{../task rc:filter.rc add project:A priority:H +tag one foo};
+qx{../task rc:filter.rc add project:A priority:H two};
+qx{../task rc:filter.rc add project:A three};
+qx{../task rc:filter.rc add priority:H four};
+qx{../task rc:filter.rc add +tag five};
+qx{../task rc:filter.rc add six foo};
+qx{../task rc:filter.rc add priority:L seven bar foo};
+
+my $output = qx{../task rc:filter.rc list};
+like ($output, qr/one/, 'a1');
+like ($output, qr/two/, 'a2');
+like ($output, qr/three/, 'a3');
+like ($output, qr/four/, 'a4');
+like ($output, qr/five/, 'a5');
+like ($output, qr/six/, 'a6');
+like ($output, qr/seven/, 'a7');
+
+$output = qx{../task rc:filter.rc list project:A};
+like ($output, qr/one/, 'b1');
+like ($output, qr/two/, 'b2');
+like ($output, qr/three/, 'b3');
+unlike ($output, qr/four/, 'b4');
+unlike ($output, qr/five/, 'b5');
+unlike ($output, qr/six/, 'b6');
+unlike ($output, qr/seven/, 'b7');
+
+$output = qx{../task rc:filter.rc list priority:H};
+like ($output, qr/one/, 'c1');
+like ($output, qr/two/, 'c2');
+unlike ($output, qr/three/, 'c3');
+like ($output, qr/four/, 'c4');
+unlike ($output, qr/five/, 'c5');
+unlike ($output, qr/six/, 'c6');
+unlike ($output, qr/seven/, 'c7');
+
+$output = qx{../task rc:filter.rc list priority:};
+unlike ($output, qr/one/, 'd1');
+unlike ($output, qr/two/, 'd2');
+like ($output, qr/three/, 'd3');
+unlike ($output, qr/four/, 'd4');
+like ($output, qr/five/, 'd5');
+like ($output, qr/six/, 'd6');
+unlike ($output, qr/seven/, 'd7');
+
+$output = qx{../task rc:filter.rc list foo};
+like ($output, qr/one/, 'e1');
+unlike ($output, qr/two/, 'e2');
+unlike ($output, qr/three/, 'e3');
+unlike ($output, qr/four/, 'e4');
+unlike ($output, qr/five/, 'e5');
+like ($output, qr/six/, 'e6');
+like ($output, qr/seven/, 'e7');
+
+$output = qx{../task rc:filter.rc list foo bar};
+unlike ($output, qr/one/, 'f1');
+unlike ($output, qr/two/, 'f2');
+unlike ($output, qr/three/, 'f3');
+unlike ($output, qr/four/, 'f4');
+unlike ($output, qr/five/, 'f5');
+unlike ($output, qr/six/, 'f6');
+like ($output, qr/seven/, 'f7');
+
+$output = qx{../task rc:filter.rc list +tag};
+like ($output, qr/one/, 'g1');
+unlike ($output, qr/two/, 'g2');
+unlike ($output, qr/three/, 'g3');
+unlike ($output, qr/four/, 'g4');
+like ($output, qr/five/, 'g5');
+unlike ($output, qr/six/, 'g6');
+unlike ($output, qr/seven/, 'g7');
+
+$output = qx{../task rc:filter.rc list project:A priority:H};
+like ($output, qr/one/, 'h1');
+like ($output, qr/two/, 'h2');
+unlike ($output, qr/three/, 'h3');
+unlike ($output, qr/four/, 'h4');
+unlike ($output, qr/five/, 'h5');
+unlike ($output, qr/six/, 'h6');
+unlike ($output, qr/seven/, 'h7');
+
+$output = qx{../task rc:filter.rc list project:A priority:};
+unlike ($output, qr/one/, 'i1');
+unlike ($output, qr/two/, 'i2');
+like ($output, qr/three/, 'i3');
+unlike ($output, qr/four/, 'i4');
+unlike ($output, qr/five/, 'i5');
+unlike ($output, qr/six/, 'i6');
+unlike ($output, qr/seven/, 'i7');
+
+$output = qx{../task rc:filter.rc list project:A foo};
+like ($output, qr/one/, 'j1');
+unlike ($output, qr/two/, 'j2');
+unlike ($output, qr/three/, 'j3');
+unlike ($output, qr/four/, 'j4');
+unlike ($output, qr/five/, 'j5');
+unlike ($output, qr/six/, 'j6');
+unlike ($output, qr/seven/, 'j7');
+
+$output = qx{../task rc:filter.rc list project:A +tag};
+like ($output, qr/one/, 'k1');
+unlike ($output, qr/two/, 'k2');
+unlike ($output, qr/three/, 'k3');
+unlike ($output, qr/four/, 'k4');
+unlike ($output, qr/five/, 'k5');
+unlike ($output, qr/six/, 'k6');
+unlike ($output, qr/seven/, 'k7');
+
+$output = qx{../task rc:filter.rc list project:A priority:H foo};
+like ($output, qr/one/, 'l1');
+unlike ($output, qr/two/, 'l2');
+unlike ($output, qr/three/, 'l3');
+unlike ($output, qr/four/, 'l4');
+unlike ($output, qr/five/, 'l5');
+unlike ($output, qr/six/, 'l6');
+unlike ($output, qr/seven/, 'l7');
+
+$output = qx{../task rc:filter.rc list project:A priority:H +tag};
+like ($output, qr/one/, 'm1');
+unlike ($output, qr/two/, 'm2');
+unlike ($output, qr/three/, 'm3');
+unlike ($output, qr/four/, 'm4');
+unlike ($output, qr/five/, 'm5');
+unlike ($output, qr/six/, 'm6');
+unlike ($output, qr/seven/, 'm7');
+
+$output = qx{../task rc:filter.rc list project:A priority:H foo +tag};
+like ($output, qr/one/, 'n1');
+unlike ($output, qr/two/, 'n2');
+unlike ($output, qr/three/, 'n3');
+unlike ($output, qr/four/, 'n4');
+unlike ($output, qr/five/, 'n5');
+unlike ($output, qr/six/, 'n6');
+unlike ($output, qr/seven/, 'n7');
+
+$output = qx{../task rc:filter.rc list project:A priority:H foo +tag baz};
+unlike ($output, qr/one/, 'n1');
+unlike ($output, qr/two/, 'n2');
+unlike ($output, qr/three/, 'n3');
+unlike ($output, qr/four/, 'n4');
+unlike ($output, qr/five/, 'n5');
+unlike ($output, qr/six/, 'n6');
+unlike ($output, qr/seven/, 'n7');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'filter.rc';
+ok (!-r 'filter.rc', 'Removed filter.rc');
+
+exit 0;
+
diff --git a/src/tests/in b/src/tests/in
deleted file mode 100755
index 761c1f877..000000000
--- a/src/tests/in
+++ /dev/null
@@ -1,15 +0,0 @@
-./task add monday due:monday
-./task add tuesday due:tuesday
-./task add wednesday due:wednesday
-./task add thursday due:thursday
-./task add friday due:friday
-./task add saturday due:saturday
-./task add sunday due:sunday
-./task add yesterday due:yesterday
-./task add today due:today
-./task add tomorrow due:tomorrow
-./task add eow due:eow
-./task add eom due:eom
-./task add eoy due:eoy
-./task add 21st due:21st
-
diff --git a/src/tests/nag.t b/src/tests/nag.t
new file mode 100755
index 000000000..d55aac456
--- /dev/null
+++ b/src/tests/nag.t
@@ -0,0 +1,65 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 9;
+
+# Create the rc file.
+if (open my $fh, '>', 'nag.rc')
+{
+ print $fh "data.location=.\n",
+ "nag=NAG\n";
+ close $fh;
+ ok (-r 'nag.rc', 'Created nag.rc');
+}
+
+my $setup = "../task rc:nag.rc add due:yesterday one;"
+ . "../task rc:nag.rc add due:tomorrow two;"
+ . "../task rc:nag.rc add priority:H three;"
+ . "../task rc:nag.rc add priority:M four;"
+ . "../task rc:nag.rc add priority:L five;"
+ . "../task rc:nag.rc add six;";
+qx{$setup};
+
+like (qx{../task rc:nag.rc do 6}, qr/NAG/, 'do pri: -> nag');
+like (qx{../task rc:nag.rc do 5}, qr/NAG/, 'do pri:L -> nag');
+like (qx{../task rc:nag.rc do 4}, qr/NAG/, 'do pri:M-> nag');
+like (qx{../task rc:nag.rc do 3}, qr/NAG/, 'do pri:H-> nag');
+like (qx{../task rc:nag.rc do 2}, qr/NAG/, 'do due:tomorrow -> nag');
+ok (qx{../task rc:nag.rc do 1} !~ qr/NAG/, 'do due:yesterday -> no nag');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'nag.rc';
+ok (!-r 'nag.rc', 'Removed nag.rc');
+
+exit 0;
+
diff --git a/src/tests/next.t b/src/tests/next.t
new file mode 100755
index 000000000..2130f6626
--- /dev/null
+++ b/src/tests/next.t
@@ -0,0 +1,61 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+# Create the rc file.
+if (open my $fh, '>', 'next.rc')
+{
+ print $fh "data.location=.\n",
+ "next=1\n";
+ close $fh;
+ ok (-r 'next.rc', 'Created next.rc');
+}
+
+# Add two tasks for each of two projects, then run next. There should be only
+# one task from each project shown.
+qx{../task rc:next.rc add project:A priority:H AH};
+qx{../task rc:next.rc add project:A priority:M AM};
+qx{../task rc:next.rc add project:B priority:H BH};
+qx{../task rc:next.rc add project:B Bnone};
+
+my $output = qx{../task rc:next.rc next};
+like ($output, qr/\s1\sA\s+H\s+-\sAH\n/, 'AH shown');
+like ($output, qr/\s3\sB\s+H\s+-\sBH\n/, 'BH shown');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'next.rc';
+ok (!-r 'next.rc', 'Removed next.rc');
+
+exit 0;
+
diff --git a/src/tests/oldest.t b/src/tests/oldest.t
new file mode 100755
index 000000000..395ed7adf
--- /dev/null
+++ b/src/tests/oldest.t
@@ -0,0 +1,89 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 25;
+
+# Create the rc file.
+if (open my $fh, '>', 'oldest.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'oldest.rc', 'Created oldest.rc');
+}
+
+# Add 11 tasks. Oldest should show 1-10, newest should show 2-11.
+diag ("Adding 11 tasks - takes 10 seconds");
+qx{../task rc:oldest.rc add one; sleep 1};
+qx{../task rc:oldest.rc add two; sleep 1};
+qx{../task rc:oldest.rc add three; sleep 1};
+qx{../task rc:oldest.rc add four; sleep 1};
+qx{../task rc:oldest.rc add five; sleep 1};
+qx{../task rc:oldest.rc add six; sleep 1};
+qx{../task rc:oldest.rc add seven; sleep 1};
+qx{../task rc:oldest.rc add eight; sleep 1};
+qx{../task rc:oldest.rc add nine; sleep 1};
+qx{../task rc:oldest.rc add ten; sleep 1};
+qx{../task rc:oldest.rc add eleven};
+
+my $output = qx{../task rc:oldest.rc oldest};
+like ($output, qr/one/, 'oldest: one');
+like ($output, qr/two/, 'oldest: two');
+like ($output, qr/three/, 'oldest: three');
+like ($output, qr/four/, 'oldest: four');
+like ($output, qr/five/, 'oldest: five');
+like ($output, qr/six/, 'oldest: six');
+like ($output, qr/seven/, 'oldest: seven');
+like ($output, qr/eight/, 'oldest: eight');
+like ($output, qr/nine/, 'oldest: nine');
+like ($output, qr/ten/, 'oldest: ten');
+unlike ($output, qr/eleven/, 'no: eleven');
+
+$output = qx{../task rc:oldest.rc newest};
+unlike ($output, qr/one/, 'no: one');
+like ($output, qr/two/, 'newest: two');
+like ($output, qr/three/, 'newest: three');
+like ($output, qr/four/, 'newest: four');
+like ($output, qr/five/, 'newest: five');
+like ($output, qr/six/, 'newest: six');
+like ($output, qr/seven/, 'newest: seven');
+like ($output, qr/eight/, 'newest: eight');
+like ($output, qr/nine/, 'newest: nine');
+like ($output, qr/ten/, 'newest: ten');
+like ($output, qr/eleven/, 'newest: eleven');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'oldest.rc';
+ok (!-r 'oldest.rc', 'Removed oldest.rc');
+
+exit 0;
+
diff --git a/src/tests/overdue.t b/src/tests/overdue.t
new file mode 100755
index 000000000..94229c438
--- /dev/null
+++ b/src/tests/overdue.t
@@ -0,0 +1,60 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'due.rc')
+{
+ print $fh "data.location=.\n",
+ "due=4\n";
+ close $fh;
+ ok (-r 'due.rc', 'Created due.rc');
+}
+
+# Add an overdue task, a due task, and a regular task. The "overdue" report
+# should list only the one task.
+qx{../task rc:due.rc add due:yesterday one};
+qx{../task rc:due.rc add due:tomorrow two};
+qx{../task rc:due.rc add due:eoy three};
+my $output = qx{../task rc:due.rc overdue};
+like ($output, qr/one/, 'overdue: task 1 shows up');
+unlike ($output, qr/two/, 'overdue: task 2 does not show up');
+unlike ($output, qr/three/, 'overdue: task 3 does not show up');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'due.rc';
+ok (!-r 'due.rc', 'Removed due.rc');
+
+exit 0;
+
diff --git a/src/tests/recur.t b/src/tests/recur.t
new file mode 100755
index 000000000..96a251bd2
--- /dev/null
+++ b/src/tests/recur.t
@@ -0,0 +1,67 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+# Create the rc file.
+if (open my $fh, '>', 'recur.rc')
+{
+ print $fh "data.location=.\n",
+ "report.asc.columns=id,recur,description\n",
+ "report.asc.sort=recur+\n",
+ "report.desc.columns=id,recur,description\n",
+ "report.desc.sort=recur-\n";
+ close $fh;
+ ok (-r 'recur.rc', 'Created recur.rc');
+}
+
+# Create a few recurring tasks, and test the sort order of the recur column.
+qx{../task rc:recur.rc add due:tomorrow recur:daily first};
+qx{../task rc:recur.rc add due:tomorrow recur:weekly second};
+qx{../task rc:recur.rc add due:tomorrow recur:3d third};
+
+my $output = qx{../task rc:recur.rc asc};
+like ($output, qr/first .* third .* second/msx, 'daily 3d weekly');
+
+$output = qx{../task rc:recur.rc desc};
+like ($output, qr/second .* third .* first/msx, 'weekly 3d daily');
+
+# Cleanup.
+unlink 'shadow.txt';
+ok (!-r 'shadow.txt', 'Removed shadow.txt');
+
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'recur.rc';
+ok (!-r 'recur.rc', 'Removed recur.rc');
+
+exit 0;
+
diff --git a/src/tests/run_all b/src/tests/run_all
new file mode 100755
index 000000000..9853f957f
--- /dev/null
+++ b/src/tests/run_all
@@ -0,0 +1,11 @@
+#! /bin/bash
+
+date > all.log
+
+for i in *.t
+do
+ ./$i >> all.log 2>&1
+done
+
+date >> all.log
+
diff --git a/src/tests/shadow.t b/src/tests/shadow.t
new file mode 100755
index 000000000..abea89205
--- /dev/null
+++ b/src/tests/shadow.t
@@ -0,0 +1,101 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 21;
+
+# Create the rc file.
+if (open my $fh, '>', 'shadow.rc')
+{
+ print $fh "data.location=.\n",
+ "shadow.file=./shadow.txt\n",
+ "shadow.command=stats\n",
+ "shadow.notify=on\n";
+ close $fh;
+ ok (-r 'shadow.rc', 'Created shadow.rc');
+}
+
+my $output = qx{../task rc:shadow.rc add one};
+like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on add');
+
+$output = qx{../task rc:shadow.rc list};
+unlike ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file not updated on list');
+
+$output = qx{../task rc:shadow.rc delete 1};
+like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on delete');
+
+$output = qx{../task rc:shadow.rc list};
+like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on list');
+
+# Inspect the shadow file.
+my $file = slurp ('./shadow.txt');
+like ($file, qr/Pending\s+0\n/, 'Pending 0');
+like ($file, qr/Recurring\s+0\n/, 'Recurring 0');
+like ($file, qr/Completed\s+0\n/, 'Completed 0');
+like ($file, qr/Deleted\s+1\n/, 'Deleted 1');
+like ($file, qr/Total\s+1\n/, 'Total 1');
+like ($file, qr/Task used for\s+-\n/, 'Task used for -');
+like ($file, qr/Task added every\s+-\n/, 'Task added every -');
+like ($file, qr/Task deleted every\s+-\n/, 'Task deleted every -');
+like ($file, qr/Average desc length\s+3 characters\n/, 'Average desc length 3 characters');
+like ($file, qr/Tasks tagged\s+0%\n/, 'Tasks tagged 0%');
+like ($file, qr/Unique tags\s+0\n/, 'Unique tags 0');
+like ($file, qr/Projects\s+0\n/, 'Projects 0');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'completed.data';
+ok (!-r 'completed.data', 'Removed completed.data');
+
+unlink 'shadow.rc';
+ok (!-r 'shadow.rc', 'Removed shadow.rc');
+
+unlink 'shadow.txt';
+ok (!-r 'shadow.txt', 'Removed shadow.txt');
+
+exit 0;
+
+################################################################################
+sub slurp
+{
+ my ($file) = @_;
+ local $/;
+
+ if (open my $fh, '<', $file)
+ {
+ my $contents = <$fh>;
+ close $fh;
+ return $contents;
+ }
+
+ '';
+}
+
diff --git a/src/tests/start.t b/src/tests/start.t
new file mode 100755
index 000000000..20b7d92bc
--- /dev/null
+++ b/src/tests/start.t
@@ -0,0 +1,73 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 12;
+
+# Create the rc file.
+if (open my $fh, '>', 'start.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'start.rc', 'Created start.rc');
+}
+
+# Test the add/start/stop commands.
+qx{../task rc:start.rc add one};
+qx{../task rc:start.rc add two};
+my $output = qx{../task rc:start.rc active};
+unlike ($output, qr/one/, 'one not active');
+unlike ($output, qr/two/, 'two not active');
+
+qx{../task rc:start.rc start 1};
+qx{../task rc:start.rc start 2};
+$output = qx{../task rc:start.rc active};
+like ($output, qr/one/, 'one active');
+like ($output, qr/two/, 'two active');
+
+qx{../task rc:start.rc stop 1};
+$output = qx{../task rc:start.rc active};
+unlike ($output, qr/one/, 'one not active');
+like ($output, qr/two/, 'two active');
+
+qx{../task rc:start.rc stop 2};
+$output = qx{../task rc:start.rc active};
+unlike ($output, qr/one/, 'one not active');
+unlike ($output, qr/two/, 'two not active');
+
+# Cleanup.
+ok (-r 'pending.data', 'Need to remove pending.data');
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'start.rc';
+ok (!-r 'start.rc', 'Removed start.rc');
+
+exit 0;
+
diff --git a/src/tests/subproject.t b/src/tests/subproject.t
new file mode 100755
index 000000000..04703a28a
--- /dev/null
+++ b/src/tests/subproject.t
@@ -0,0 +1,73 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 11;
+
+# Create the rc file.
+if (open my $fh, '>', 'sp.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'sp.rc', 'Created sp.rc');
+}
+
+my $setup = "../task rc:sp.rc add project:abc abc;"
+ . "../task rc:sp.rc add project:ab ab;"
+ . "../task rc:sp.rc add project:a a;"
+ . "../task rc:sp.rc add project:b b;";
+qx{$setup};
+
+my $output = qx{../task rc:sp.rc list project:b};
+like ($output, qr/\bb\s*$/m, 'abc,ab,a,b | b -> b');
+
+$output = qx{../task rc:sp.rc list project:a};
+like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -> abc');
+like ($output, qr/\bab\s*$/m, 'abc,ab,a,b | a -> ab');
+like ($output, qr/\ba\s*$/m, 'abc,ab,a,b | a -> a');
+
+$output = qx{../task rc:sp.rc list project:ab};
+like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -> abc');
+like ($output, qr/\bab\s*$/m, 'abc,ab,a,b | a -> ab');
+
+$output = qx{../task rc:sp.rc list project:abc};
+like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -> abc');
+
+$output = qx{../task rc:sp.rc list project:abcd};
+like ($output, qr/^No matches.$/, 'abc,ab,a,b | abcd -> nul');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'sp.rc';
+ok (!-r 'sp.rc', 'Removed sp.rc');
+
+exit 0;
+
diff --git a/src/tests/t.benchmark.t.cpp b/src/tests/t.benchmark.t.cpp
new file mode 100644
index 000000000..9adf4d578
--- /dev/null
+++ b/src/tests/t.benchmark.t.cpp
@@ -0,0 +1,69 @@
+////////////////////////////////////////////////////////////////////////////////
+// task - a command line task list manager.
+//
+// Copyright 2006 - 2009, Paul Beckingham.
+// All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation; either version 2 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the
+//
+// Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor,
+// Boston, MA
+// 02110-1301
+// USA
+//
+////////////////////////////////////////////////////////////////////////////////
+#include
+#include "../T.h"
+#include "../task.h"
+#include "test.h"
+
+////////////////////////////////////////////////////////////////////////////////
+int main (int argc, char** argv)
+{
+ UnitTest test (1);
+
+ std::string sample = "d346065c-7ef6-49af-ae77-19c1825807f5 "
+ "- "
+ "[bug performance solaris linux osx] "
+ "[due:1236142800 entry:1236177552 priority:H project:task-1.5.0 start:1236231761] "
+ "Profile task and identify performance bottlenecks";
+
+ // Start clock
+ test.diag ("start");
+ struct timeval start;
+ gettimeofday (&start, NULL);
+
+ for (int i = 0; i < 1000000; i++)
+ {
+ T t (sample);
+ }
+
+ // End clock
+ struct timeval end;
+ gettimeofday (&end, NULL);
+ test.diag ("end");
+
+ int diff = ((end.tv_sec * 1000000) + end.tv_usec) -
+ ((start.tv_sec * 1000000) + start.tv_usec);
+
+ char s[16];
+ sprintf (s, "%d.%06d", diff/1000000, diff%1000000);
+ test.pass (std::string ("1,000,000 T::parse calls in ") + s + "s");
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
diff --git a/src/tests/t.t.cpp b/src/tests/t.t.cpp
index f8d38e23b..ae76b18c6 100644
--- a/src/tests/t.t.cpp
+++ b/src/tests/t.t.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -31,34 +31,34 @@
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
- plan (5);
+ UnitTest test (5);
T t;
std::string s = t.compose ();
- is ((int)s.length (), 46, "T::T (); T::compose ()");
- diag (s);
+ test.is ((int)s.length (), 46, "T::T (); T::compose ()");
+ test.diag (s);
t.setStatus (T::completed);
s = t.compose ();
- is (s[37], '+', "T::setStatus (completed)");
- diag (s);
+ test.is (s[37], '+', "T::setStatus (completed)");
+ test.diag (s);
t.setStatus (T::deleted);
s = t.compose ();
- is (s[37], 'X', "T::setStatus (deleted)");
- diag (s);
+ test.is (s[37], 'X', "T::setStatus (deleted)");
+ test.diag (s);
t.setStatus (T::recurring);
s = t.compose ();
- is (s[37], 'r', "T::setStatus (recurring)");
- diag (s);
+ test.is (s[37], 'r', "T::setStatus (recurring)");
+ test.diag (s);
// Round trip test.
std::string sample = "00000000-0000-0000-0000-000000000000 - [] [] Sample";
T t2;
t2.parse (sample);
sample += "\n";
- is (t2.compose (), sample, "T::parse -> T::compose round trip");
+ test.is (t2.compose (), sample, "T::parse -> T::compose round trip");
return 0;
}
diff --git a/src/tests/tag.t b/src/tests/tag.t
new file mode 100755
index 000000000..f55ba0dbf
--- /dev/null
+++ b/src/tests/tag.t
@@ -0,0 +1,73 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 9;
+
+# Create the rc file.
+if (open my $fh, '>', 'tag.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'tag.rc', 'Created tag.rc');
+}
+
+# Add task with tags.
+my $output = qx{../task rc:tag.rc add +1 This +2 is a test +3; ../task rc:tag.rc info 1};
+like ($output, qr/^Tags\s+1 2 3\n/m, 'tags found');
+
+# Remove tags.
+$output = qx{../task rc:tag.rc 1 -3 -2 -1; ../task rc:tag.rc info 1};
+unlike ($output, qr/^Tags/m, '-3 -2 -1 tag removed');
+
+# Add tags.
+$output = qx{../task rc:tag.rc 1 +4 +5 +6; ../task rc:tag.rc info 1};
+like ($output, qr/^Tags\s+4 5 6\n/m, 'tags found');
+
+# Remove tags.
+$output = qx{../task rc:tag.rc 1 -4 -5 -6; ../task rc:tag.rc info 1};
+unlike ($output, qr/^Tags/m, '-4 -5 -6 tag removed');
+
+# Add and remove tags.
+$output = qx{../task rc:tag.rc 1 +duplicate -duplicate; ../task rc:tag.rc info 1};
+unlike ($output, qr/^Tags/m, '+duplicate -duplicate NOP');
+
+# Remove missing tag.
+$output = qx{../task rc:tag.rc 1 -missing; ../task rc:tag.rc info 1};
+unlike ($output, qr/^Tags/m, '-missing NOP');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'tag.rc';
+ok (!-r 'tag.rc', 'Removed tag.rc');
+
+exit 0;
+
diff --git a/src/tests/tdb.t.cpp b/src/tests/tdb.t.cpp
index 795f92334..c874c00fa 100644
--- a/src/tests/tdb.t.cpp
+++ b/src/tests/tdb.t.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -34,7 +34,7 @@
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
- plan (43);
+ UnitTest t (38);
try
{
@@ -46,14 +46,14 @@ int main (int argc, char** argv)
TDB tdb;
tdb.dataDirectory (".");
std::vector all;
- ok (!tdb.pendingT (all), "TDB::pendingT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (!tdb.completedT (all), "TDB::completedT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
- is ((int) all.size (), 0, "empty db");
+ t.ok (!tdb.pendingT (all), "TDB::pendingT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (!tdb.completedT (all), "TDB::completedT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
// Add a new task.
T t1;
@@ -61,86 +61,68 @@ int main (int argc, char** argv)
t1.setStatus (T::pending);
t1.setAttribute ("project", "p1");
t1.setDescription ("task 1");
- diag (t1.compose ());
- ok (tdb.addT (t1), "TDB::addT t1");
+ t.diag (t1.compose ());
+ t.ok (tdb.addT (t1), "TDB::addT t1");
// Verify as above.
- ok (tdb.pendingT (all), "TDB::pendingT read db");
- is ((int) all.size (), 1, "empty db");
- ok (tdb.allPendingT (all), "TDB::allPendingT read db");
- is ((int) all.size (), 1, "empty db");
- ok (!tdb.completedT (all), "TDB::completedT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
- is ((int) all.size (), 0, "empty db");
+ t.ok (tdb.pendingT (all), "TDB::pendingT read db");
+ t.is ((int) all.size (), 1, "empty db");
+ t.ok (tdb.allPendingT (all), "TDB::allPendingT read db");
+ t.is ((int) all.size (), 1, "empty db");
+ t.ok (!tdb.completedT (all), "TDB::completedT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
// TODO Modify task.
- fail ("modify");
- fail ("verify");
// Complete task.
- ok (tdb.completeT (t1), "TDB::completeT t1");;
- ok (!tdb.pendingT (all), "TDB::pendingT read db");
- is ((int) all.size (), 0, "empty db");
- ok (tdb.allPendingT (all), "TDB::allPendingT read db");
- is ((int) all.size (), 1, "empty db");
- ok (!tdb.completedT (all), "TDB::completedT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
- is ((int) all.size (), 0, "empty db");
+ t.ok (tdb.completeT (t1), "TDB::completeT t1");;
+ t.ok (tdb.pendingT (all), "TDB::pendingT read db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (tdb.allPendingT (all), "TDB::allPendingT read db");
+ t.is ((int) all.size (), 1, "empty db");
+ t.ok (!tdb.completedT (all), "TDB::completedT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
- is (tdb.gc (), 1, "TDB::gc");
- ok (!tdb.pendingT (all), "TDB::pendingT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db");
- is ((int) all.size (), 0, "empty db");
- ok (tdb.completedT (all), "TDB::completedT read db");
- is ((int) all.size (), 1, "empty db");
- ok (tdb.allCompletedT (all), "TDB::allCompletedT read db");
- is ((int) all.size (), 1, "empty db");
+ t.is (tdb.gc (), 1, "TDB::gc");
+ t.ok (tdb.pendingT (all), "TDB::pendingT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (tdb.allPendingT (all), "TDB::allPendingT read empty db");
+ t.is ((int) all.size (), 0, "empty db");
+ t.ok (tdb.completedT (all), "TDB::completedT read db");
+ t.is ((int) all.size (), 1, "empty db");
+ t.ok (tdb.allCompletedT (all), "TDB::allCompletedT read db");
+ t.is ((int) all.size (), 1, "empty db");
// Add a new task.
T t2;
- t2.setId (2);
+ t2.setId (1);
t2.setAttribute ("project", "p2");
t2.setDescription ("task 2");
- diag (t2.compose ());
- ok (tdb.addT (t2), "TDB::addT t2");
-
- fail ("verify");
+ t.ok (tdb.addT (t2), "TDB::addT t2");
// Delete task.
- ok (tdb.deleteT (t2), "TDB::deleteT t2");
-
- fail ("verify");
+ t.ok (tdb.deleteT (t2), "TDB::deleteT t2");
// GC the files.
- is (tdb.gc (), 1, "1 <- TDB::gc");
-
- // Read log file.
- std::vector entries;
- tdb.logRead (entries);
- std::vector ::iterator it;
- for (it = entries.begin (); it != entries.end (); ++it)
- diag (*it);
-
- // TODO Verify contents of above transactions.
- fail ("verify");
+ t.is (tdb.gc (), 1, "1 <- TDB::gc");
}
catch (std::string& error)
{
- diag (error);
+ t.diag (error);
return -1;
}
catch (...)
{
- diag ("Unknown error.");
+ t.diag ("Unknown error.");
return -2;
}
-
unlink ("./pending.data");
unlink ("./completed.data");
diff --git a/src/tests/test.cpp b/src/tests/test.cpp
index 1937e9230..4c8f201b3 100644
--- a/src/tests/test.cpp
+++ b/src/tests/test.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -25,81 +25,157 @@
//
////////////////////////////////////////////////////////////////////////////////
#include
+#include
#include
#include
-
-static int total = 0;
-static int counter = 0;
+#include "test.h"
///////////////////////////////////////////////////////////////////////////////
-static void check (void)
+UnitTest::UnitTest ()
+: mPlanned (0)
+, mCounter (0)
+, mPassed (0)
+, mFailed (0)
+, mSkipped (0)
{
- if (counter > total)
- std::cout << "# Warning: There are more tests than planned."
+}
+
+///////////////////////////////////////////////////////////////////////////////
+UnitTest::UnitTest (int planned)
+: mPlanned (planned)
+, mCounter (0)
+, mPassed (0)
+, mFailed (0)
+, mSkipped (0)
+{
+ std::cout << "1.." << mPlanned << std::endl;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+UnitTest::~UnitTest ()
+{
+ float percentPassed = 0.0;
+ if (mPlanned > 0)
+ percentPassed = (100.0 * mPassed) / max (mPlanned, mPassed + mFailed + mSkipped);
+
+ if (mCounter < mPlanned)
+ {
+ std::cout << "# Only "
+ << mCounter
+ << " tests, out of a planned "
+ << mPlanned
+ << " were run."
<< std::endl;
+ mSkipped += mPlanned - mCounter;
+ }
+
+ else if (mCounter > mPlanned)
+ std::cout << "# "
+ << mCounter
+ << " tests were run, but only "
+ << mPlanned
+ << " were planned."
+ << std::endl;
+
+ std::cout << "# "
+ << mPassed
+ << " passed, "
+ << mFailed
+ << " failed, "
+ << mSkipped
+ << " skipped. "
+ << std::setprecision (3) << percentPassed
+ << "% passed."
+ << std::endl;
}
///////////////////////////////////////////////////////////////////////////////
-void plan (int quantity)
+void UnitTest::plan (int planned)
{
- total = quantity;
- std::cout << "1.." << quantity << std::endl;
- check ();
+ mPlanned = planned;
+ mCounter = 0;
+ mPassed = 0;
+ mFailed = 0;
+ mSkipped = 0;
+
+ std::cout << "1.." << mPlanned << std::endl;
}
///////////////////////////////////////////////////////////////////////////////
-void ok (bool expression, const std::string& name)
+void UnitTest::planMore (int extra)
{
- ++counter;
+ mPlanned += extra;
+ std::cout << "1.." << mPlanned << std::endl;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void UnitTest::ok (bool expression, const std::string& name)
+{
+ ++mCounter;
if (expression)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void notok (bool expression, const std::string& name)
+void UnitTest::notok (bool expression, const std::string& name)
{
- ++counter;
+ ++mCounter;
if (!expression)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void is (bool actual, bool expected, const std::string& name)
+void UnitTest::is (bool actual, bool expected, const std::string& name)
{
- ++counter;
+ ++mCounter;
if (actual == expected)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl
@@ -109,22 +185,27 @@ void is (bool actual, bool expected, const std::string& name)
<< "# got: "
<< actual
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void is (size_t actual, size_t expected, const std::string& name)
+void UnitTest::is (size_t actual, size_t expected, const std::string& name)
{
- ++counter;
+ ++mCounter;
if (actual == expected)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl
@@ -134,22 +215,27 @@ void is (size_t actual, size_t expected, const std::string& name)
<< "# got: "
<< actual
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void is (int actual, int expected, const std::string& name)
+void UnitTest::is (int actual, int expected, const std::string& name)
{
- ++counter;
+ ++mCounter;
if (actual == expected)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl
@@ -159,22 +245,27 @@ void is (int actual, int expected, const std::string& name)
<< "# got: "
<< actual
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void is (double actual, double expected, const std::string& name)
+void UnitTest::is (double actual, double expected, const std::string& name)
{
- ++counter;
+ ++mCounter;
if (actual == expected)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl
@@ -184,22 +275,27 @@ void is (double actual, double expected, const std::string& name)
<< "# got: "
<< actual
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void is (char actual, char expected, const std::string& name)
+void UnitTest::is (char actual, char expected, const std::string& name)
{
- ++counter;
+ ++mCounter;
if (actual == expected)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl
@@ -209,25 +305,30 @@ void is (char actual, char expected, const std::string& name)
<< "# got: "
<< actual
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void is (
+void UnitTest::is (
const std::string& actual,
const std::string& expected,
const std::string& name)
{
- ++counter;
+ ++mCounter;
if (actual == expected)
+ {
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl;
+ }
else
+ {
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
<< " - "
<< name
<< std::endl
@@ -239,11 +340,46 @@ void is (
<< actual
<< "'"
<< std::endl;
- check ();
+ }
}
///////////////////////////////////////////////////////////////////////////////
-void diag (const std::string& text)
+void UnitTest::is (
+ const char* actual,
+ const char* expected,
+ const std::string& name)
+{
+ ++mCounter;
+ if (! strcmp (actual, expected))
+ {
+ ++mPassed;
+ std::cout << "ok "
+ << mCounter
+ << " - "
+ << name
+ << std::endl;
+ }
+ else
+ {
+ ++mFailed;
+ std::cout << "not ok "
+ << mCounter
+ << " - "
+ << name
+ << std::endl
+ << "# expected: '"
+ << expected
+ << "'"
+ << std::endl
+ << "# got: '"
+ << actual
+ << "'"
+ << std::endl;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void UnitTest::diag (const std::string& text)
{
std::string trimmed = trim (text, " \t\n\r\f");
@@ -251,22 +387,36 @@ void diag (const std::string& text)
}
///////////////////////////////////////////////////////////////////////////////
-void pass (const std::string& text)
+void UnitTest::pass (const std::string& text)
{
- ++counter;
+ ++mCounter;
+ ++mPassed;
std::cout << "ok "
- << counter
+ << mCounter
<< " "
<< text
<< std::endl;
}
///////////////////////////////////////////////////////////////////////////////
-void fail (const std::string& text)
+void UnitTest::fail (const std::string& text)
{
- ++counter;
+ ++mCounter;
+ ++mFailed;
std::cout << "not ok "
- << counter
+ << mCounter
+ << " "
+ << text
+ << std::endl;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void UnitTest::skip (const std::string& text)
+{
+ ++mCounter;
+ ++mSkipped;
+ std::cout << "skip "
+ << mCounter
<< " "
<< text
<< std::endl;
diff --git a/src/tests/test.h b/src/tests/test.h
index 36cfa2f67..b4bcf7f66 100644
--- a/src/tests/test.h
+++ b/src/tests/test.h
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -24,23 +24,42 @@
// USA
//
////////////////////////////////////////////////////////////////////////////////
-#ifndef INCLUDED_TEST
-#define INCLUDED_TEST
+#ifndef INCLUDED_UNITTEST
+#define INCLUDED_UNITTEST
#include
-void plan (int);
-void ok (bool, const std::string&);
-void notok (bool, const std::string&);
-void is (bool, bool, const std::string&);
-void is (int, int, const std::string&);
-void is (size_t, size_t, const std::string&);
-void is (double, double, const std::string&);
-void is (char, char, const std::string&);
-void is (const std::string&, const std::string&, const std::string&);
-void diag (const std::string&);
-void fail (const std::string&);
-void pass (const std::string&);
+class UnitTest
+{
+public:
+ UnitTest ();
+ UnitTest (int);
+ ~UnitTest ();
+
+ void plan (int);
+ void planMore (int);
+ void ok (bool, const std::string&);
+ void notok (bool, const std::string&);
+ void is (bool, bool, const std::string&);
+ void is (size_t, size_t, const std::string&);
+ void is (int, int, const std::string&);
+ void is (double, double, const std::string&);
+ void is (char, char, const std::string&);
+ void is (const std::string&, const std::string&, const std::string&);
+ void is (const char*, const char*, const std::string&);
+ void diag (const std::string&);
+ void pass (const std::string&);
+ void fail (const std::string&);
+ void skip (const std::string&);
+
+private:
+ int mPlanned;
+ int mCounter;
+ int mPassed;
+ int mFailed;
+ int mSkipped;
+};
#endif
+
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tests/undo.t b/src/tests/undo.t
new file mode 100755
index 000000000..ec023b31d
--- /dev/null
+++ b/src/tests/undo.t
@@ -0,0 +1,74 @@
+#! /usr/bin/perl
+################################################################################
+## task - a command line task list manager.
+##
+## Copyright 2006 - 2009, Paul Beckingham.
+## All rights reserved.
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 2 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, write to the
+##
+## Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor,
+## Boston, MA
+## 02110-1301
+## USA
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 15;
+
+# Create the rc file.
+if (open my $fh, '>', 'undo.rc')
+{
+ print $fh "data.location=.\n";
+ close $fh;
+ ok (-r 'undo.rc', 'Created undo.rc');
+}
+
+# Test the add/do/undo commands.
+my $output = qx{../task rc:undo.rc add one; ../task rc:undo.rc info 1};
+ok (-r 'pending.data', 'pending.data created');
+like ($output, qr/Status\s+Pending\n/, 'Pending');
+
+$output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc info 1};
+ok (! -r 'completed.data', 'completed.data not created');
+like ($output, qr/Status\s+Completed\n/, 'Completed');
+
+$output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1};
+ok (! -r 'completed.data', 'completed.data not created');
+like ($output, qr/Status\s+Pending\n/, 'Pending');
+
+$output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc list};
+like ($output, qr/^No matches/, 'No matches');
+
+$output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1};
+like ($output, qr/Task 1 not found/, 'task not found');
+like ($output, qr/reliably undone/, 'can only be reliable undone...');
+
+# Cleanup.
+ok (-r 'pending.data', 'Need to remove pending.data');
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+ok (-r 'completed.data', 'Need to remove completed.data');
+unlink 'completed.data';
+ok (!-r 'completed.data', 'Removed completed.data');
+
+unlink 'undo.rc';
+ok (!-r 'undo.rc', 'Removed undo.rc');
+
+exit 0;
+
diff --git a/src/text.cpp b/src/text.cpp
index 066d84f86..f18fec8e4 100644
--- a/src/text.cpp
+++ b/src/text.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -49,33 +49,43 @@ void wrapText (
}
////////////////////////////////////////////////////////////////////////////////
-void split (std::vector& results, const std::string& input, const char delimiter)
+void split (
+ std::vector& results,
+ const std::string& input,
+ const char delimiter)
{
- std::string temp = input;
+ results.clear ();
+ std::string::size_type start = 0;
std::string::size_type i;
- while ((i = temp.find (delimiter)) != std::string::npos)
+ while ((i = input.find (delimiter, start)) != std::string::npos)
{
- std::string token = temp.substr (0, i);
- results.push_back (token);
- temp.erase (0, i + 1);
+ results.push_back (input.substr (start, i - start));
+ start = i + 1;
}
- if (temp.length ()) results.push_back (temp);
+ if (input.length ())
+ results.push_back (input.substr (start, std::string::npos));
}
////////////////////////////////////////////////////////////////////////////////
-void split (std::vector& results, const std::string& input, const std::string& delimiter)
+void split (
+ std::vector& results,
+ const std::string& input,
+ const std::string& delimiter)
{
- std::string temp = input;
+ results.clear ();
+ std::string::size_type length = delimiter.length ();
+
+ std::string::size_type start = 0;
std::string::size_type i;
- while ((i = temp.find (delimiter)) != std::string::npos)
+ while ((i = input.find (delimiter, start)) != std::string::npos)
{
- std::string token = temp.substr (0, i);
- results.push_back (token);
- temp.erase (0, i + delimiter.length ());
+ results.push_back (input.substr (start, i - start));
+ start = i + length;
}
- if (temp.length ()) results.push_back (temp);
+ if (input.length ())
+ results.push_back (input.substr (start, std::string::npos));
}
////////////////////////////////////////////////////////////////////////////////
@@ -290,7 +300,7 @@ std::string upperCase (const std::string& input)
{
std::string output = input;
for (int i = 0; i < (int) input.length (); ++i)
- if (::isupper (input[i]))
+ if (::islower (input[i]))
output[i] = ::toupper (input[i]);
return output;
diff --git a/src/util.cpp b/src/util.cpp
index 8717619da..a114c9a0b 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
-// Copyright 2006 - 2008, Paul Beckingham.
+// Copyright 2006 - 2009, Paul Beckingham.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@@ -29,10 +29,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include "Date.h"
#include "Table.h"
#include "task.h"
@@ -185,6 +187,7 @@ static char randomHexDigit ()
{
static char digits[] = "0123456789abcdef";
#ifdef HAVE_RANDOM
+ // random is better than rand.
return digits[random () % 16];
#else
return digits[rand () % 16];
@@ -239,9 +242,9 @@ const std::string uuid ()
////////////////////////////////////////////////////////////////////////////////
// Recognize the following constructs, and return the number of days represented
-int convertDuration (std::string& input)
+int convertDuration (const std::string& input)
{
- input = lowerCase (input);
+ std::string lower_input = lowerCase (input);
Date today;
std::vector supported;
@@ -261,7 +264,7 @@ int convertDuration (std::string& input)
supported.push_back ("yearly");
std::vector matches;
- if (autoComplete (input, supported, matches) == 1)
+ if (autoComplete (lower_input, supported, matches) == 1)
{
std::string found = matches[0];
@@ -277,19 +280,18 @@ int convertDuration (std::string& input)
}
// Support \d+ d|w|m|q|y
-
else
{
// Verify all digits followed by d, w, m, q, or y.
- unsigned int length = input.length ();
+ unsigned int length = lower_input.length ();
for (unsigned int i = 0; i < length; ++i)
{
- if (! isdigit (input[i]) &&
+ if (! isdigit (lower_input[i]) &&
i == length - 1)
{
- int number = ::atoi (input.substr (0, i).c_str ());
+ int number = ::atoi (lower_input.substr (0, i).c_str ());
- switch (input[length - 1])
+ switch (lower_input[length - 1])
{
case 'd': return number * 1; break;
case 'w': return number * 7; break;
@@ -331,3 +333,37 @@ std::string expandPath (const std::string& in)
}
////////////////////////////////////////////////////////////////////////////////
+// On Solaris no flock function exists.
+#ifdef SOLARIS
+int flock (int fd, int operation)
+{
+ struct flock fl;
+
+ switch (operation & ~LOCK_NB)
+ {
+ case LOCK_SH:
+ fl.l_type = F_RDLCK;
+ break;
+
+ case LOCK_EX:
+ fl.l_type = F_WRLCK;
+ break;
+
+ case LOCK_UN:
+ fl.l_type = F_UNLCK;
+ break;
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ fl.l_whence = 0;
+ fl.l_start = 0;
+ fl.l_len = 0;
+
+ return fcntl (fd, (operation & LOCK_NB) ? F_SETLK : F_SETLKW, &fl);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////