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

    - + - + - +
    Source:task-1.4.3.tar.gztask-1.5.0.tar.gz
    Mac OS X 10.5 (Leopard) Intel-only:task-1.4.3.pkgtask-1.5.0.pkg
    Debian package: (Thanks to Richard Querin): task_1.4.3-1_i386.debtask_1.5.0-1_i386.deb
    -

    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 @@

    Command Usage

    -
    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 + +////////////////////////////////////////////////////////////////////////////////