diff --git a/src/Config.cpp b/src/Config.cpp new file mode 100644 index 000000000..4cf08d31a --- /dev/null +++ b/src/Config.cpp @@ -0,0 +1,191 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2005 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include "task.h" +#include "Config.h" + +//////////////////////////////////////////////////////////////////////////////// +Config::Config () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Config::Config (const std::string& file) +{ + load (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. +bool Config::load (const std::string& file) +{ + std::ifstream in; + in.open (file.c_str (), std::ifstream::in); + if (in.good ()) + { + std::string line; + while (getline (in, line)) + { + // Remove comments. + unsigned int pound = line.find ("#"); + if (pound != std::string::npos) + line = line.substr (0, pound); + + line = trim (line, " \t"); + + // Skip empty lines. + if (line.length () > 0) + { + unsigned int equal = line.find ("="); + if (equal != std::string::npos) + { + std::string key = trim (line.substr (0, equal), " \t"); + std::string value = trim (line.substr (equal+1, line.length () - equal), " \t"); + (*this)[key] = value; + } + } + } + + in.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Return the configuration value given the specified key. +const std::string& Config::get (const char* key) +{ + return this->get (std::string (key)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Return the configuration value given the specified key. If a default_value +// is present, it will be the returned value in the event of a missing key. +const std::string& Config::get ( + const char* key, + const char* default_value) +{ + return this->get (std::string (key), std::string (default_value)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Return the configuration value given the specified key. +const std::string& Config::get (const std::string& key) +{ + return (*this)[key]; +} + +//////////////////////////////////////////////////////////////////////////////// +// Return the configuration value given the specified key. If a default_value +// is present, it will be the returned value in the event of a missing key. +const std::string& Config::get ( + const std::string& key, + const std::string& default_value) +{ + if ((*this).find (key) != (*this).end ()) + return (*this)[key]; + + return default_value; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Config::get (const std::string& key, bool default_value) +{ + if ((*this).find (key) != (*this).end ()) + { + std::string value = lowerCase ((*this)[key]); + + if (value == "t" || + value == "true" || + value == "1" || + value == "yes" || + value == "on") + return true; + + return false; + } + + return default_value; +} + +//////////////////////////////////////////////////////////////////////////////// +int Config::get (const std::string& key, const int default_value) +{ + if ((*this).find (key) != (*this).end ()) + return ::atoi ((*this)[key].c_str ()); + + return default_value; +} + +//////////////////////////////////////////////////////////////////////////////// +double Config::get (const std::string& key, const double default_value) +{ + if ((*this).find (key) != (*this).end ()) + return ::atof ((*this)[key].c_str ()); + + return default_value; +} + +//////////////////////////////////////////////////////////////////////////////// +void Config::set (const std::string& key, const int value) +{ + char v[24]; + sprintf (v, "%d", value); + (*this)[key] = v; +} + +//////////////////////////////////////////////////////////////////////////////// +void Config::set (const std::string& key, const double value) +{ + char v[32]; + sprintf (v, "%f", value); + (*this)[key] = v; +} + +//////////////////////////////////////////////////////////////////////////////// +void Config::set (const std::string& key, const std::string& value) +{ + (*this)[key] = value; +} + +//////////////////////////////////////////////////////////////////////////////// +// The vector form of Config::get assumes the single value is comma-separated, +// and splits accordingly. +void Config::get ( + const std::string& key, + std::vector & values) +{ + values.clear (); + split (values, (*this)[key], ','); +} + +//////////////////////////////////////////////////////////////////////////////// +// The vector form of Config::set joins the values together with commas, and +// stores the single value. +void Config::set ( + const std::string& key, + const std::vector & values) +{ + std::string conjoined; + join (conjoined, ",", values); + (*this)[key] = conjoined; +} + +//////////////////////////////////////////////////////////////////////////////// +// Provide a vector of all configuration keys. +void Config::all (std::vector& items) +{ + foreach (i, *this) + items.push_back (i->first); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Config.h b/src/Config.h new file mode 100644 index 000000000..8d9be30cc --- /dev/null +++ b/src/Config.h @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2005 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_CONFIG +#define INCLUDED_CONFIG + +#include +#include +#include + +class Config : public std::map +{ +public: + Config (); + Config (const std::string&); + + bool load (const std::string&); + const std::string& get (const char*); + const std::string& get (const char*, const char*); + const std::string& get (const std::string&); + const std::string& get (const std::string&, const std::string&); + bool get (const std::string&, bool); + int get (const std::string&, const int); + double get (const std::string&, const double); + void get (const std::string&, std::vector &); + void set (const std::string&, const int); + void set (const std::string&, const double); + void set (const std::string&, const std::string&); + void set (const std::string&, const std::vector &); + void all (std::vector &); +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Date.cpp b/src/Date.cpp new file mode 100644 index 000000000..3a2cbbc0e --- /dev/null +++ b/src/Date.cpp @@ -0,0 +1,303 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2005 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include "task.h" +#include "Date.h" + +//////////////////////////////////////////////////////////////////////////////// +// Defaults to "now". +Date::Date () +{ + mT = time (NULL); +} + +//////////////////////////////////////////////////////////////////////////////// +Date::Date (const time_t t) +{ + mT = t; +} + +//////////////////////////////////////////////////////////////////////////////// +Date::Date (const int m, const int d, const int y) +{ + // Error if not valid. + struct tm t = {0}; + t.tm_mday = d; + t.tm_mon = m - 1; + t.tm_year = y - 1900; + + mT = mktime (&t); +} + +//////////////////////////////////////////////////////////////////////////////// +Date::Date (const std::string& mdy) +{ + unsigned int firstSlash = mdy.find ("/"); + unsigned int secondSlash = mdy.find ("/", firstSlash + 1); + if (firstSlash != std::string::npos && + secondSlash != std::string::npos) + { + int m = ::atoi (mdy.substr (0, firstSlash ).c_str ()); + int d = ::atoi (mdy.substr (firstSlash + 1, secondSlash - firstSlash).c_str ()); + int y = ::atoi (mdy.substr (secondSlash + 1, std::string::npos ).c_str ()); + if (!valid (m, d, y)) + throw std::string ("\"") + mdy + "\" is not a valid date."; + + // Duplicate Date::Date (const int, const int, const int); + struct tm t = {0}; + t.tm_mday = d; + t.tm_mon = m - 1; + t.tm_year = y - 1900; + + mT = mktime (&t); + } + else + throw std::string ("\"") + mdy + "\" is not a valid date."; +} + +//////////////////////////////////////////////////////////////////////////////// +Date::Date (const Date& rhs) +{ + mT = rhs.mT; +} + +//////////////////////////////////////////////////////////////////////////////// +Date::~Date () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +time_t Date::toEpoch () +{ + return mT; +} + +//////////////////////////////////////////////////////////////////////////////// +void Date::toEpoch (time_t& epoch) +{ + epoch = mT; +} + +//////////////////////////////////////////////////////////////////////////////// +void Date::toMDY (int& m, int& d, int& y) +{ + struct tm* t = localtime (&mT); + + m = t->tm_mon + 1; + d = t->tm_mday; + y = t->tm_year + 1900; +} + +//////////////////////////////////////////////////////////////////////////////// +void Date::toString (std::string& output) +{ + output = toString (); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Date::toString (void) +{ + int m, d, y; + toMDY (m, d, y); + + char formatted [11]; + sprintf (formatted, "%d/%d/%d", m, d, y); + return std::string (formatted); +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::valid (const int m, const int d, const int y) +{ + // Check that the year is valid. + if (y < 0) + return false; + + // Check that the month is valid. + if (m < 1 || m > 12) + return false; + + // Finally check that the days fall within the acceptable range for this + // month, and whether or not this is a leap year. + if (d < 1 || d > Date::daysInMonth (m, y)) + return false; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::leapYear (int year) +{ + bool ly = false; + + if (!(year % 4)) ly = true; + else if (!(year % 400)) ly = true; + else if (!(year % 100)) ly = false; + + return ly; +} + +//////////////////////////////////////////////////////////////////////////////// +int Date::daysInMonth (int month, int year) +{ + static int days[2][12] = + { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} + }; + + return days[Date::leapYear (year) ? 1 : 0][month - 1]; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Date::monthName (int month) +{ + static char* months[12] = + { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + }; + + assert (month > 0); + assert (month <= 12); + return months[month -1]; +} + +//////////////////////////////////////////////////////////////////////////////// +void Date::dayName (int dow, std::string& name) +{ + static char* days[7] = + { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + }; + + name = days[dow]; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Date::dayName (int dow) +{ + static char* days[7] = + { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + }; + + return days[dow]; +} + +//////////////////////////////////////////////////////////////////////////////// +int Date::dayOfWeek () +{ + struct tm* t = localtime (&mT); + return t->tm_wday; +} + +//////////////////////////////////////////////////////////////////////////////// +int Date::month () +{ + struct tm* t = localtime (&mT); + return t->tm_mon + 1; +} + +//////////////////////////////////////////////////////////////////////////////// +int Date::day () +{ + struct tm* t = localtime (&mT); + return t->tm_mday; +} + +//////////////////////////////////////////////////////////////////////////////// +int Date::year () +{ + struct tm* t = localtime (&mT); + return t->tm_year + 1900; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::operator== (const Date& rhs) +{ + return rhs.mT == mT; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::operator!= (const Date& rhs) +{ + return rhs.mT != mT; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::operator< (const Date& rhs) +{ + return mT < rhs.mT; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::operator> (const Date& rhs) +{ + return mT > rhs.mT; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::operator<= (const Date& rhs) +{ + return mT <= rhs.mT; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Date::operator>= (const Date& rhs) +{ + return mT >= rhs.mT; +} + +//////////////////////////////////////////////////////////////////////////////// +Date Date::operator+ (const int delta) +{ + return Date::Date (mT + delta); +} + +//////////////////////////////////////////////////////////////////////////////// +Date& Date::operator+= (const int delta) +{ + mT += (time_t) delta; + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +Date& Date::operator-= (const int delta) +{ + mT -= (time_t) delta; + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +time_t Date::operator- (const Date& rhs) +{ + return mT - rhs.mT; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Date.h b/src/Date.h new file mode 100644 index 000000000..376ad9093 --- /dev/null +++ b/src/Date.h @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2005 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_DATE +#define INCLUDED_DATE + +#include + +class Date; + +class Date +{ +public: + Date (); + Date (time_t); + Date (const int, const int, const int); + Date (const std::string&); + Date (const Date&); + virtual ~Date (); + + void toEpoch (time_t&); + time_t toEpoch (); + void toMDY (int&, int&, int&); + void toString (std::string&); + std::string toString (void); + static bool valid (const int, const int, const int); + + static bool leapYear (int); + static int daysInMonth (int, int); + static std::string monthName (int); + static void dayName (int, std::string&); + static std::string dayName (int); + int dayOfWeek (); + + int month (); + int day (); + int year (); + + bool operator== (const Date&); + bool operator!= (const Date&); + bool operator< (const Date&); + bool operator> (const Date&); + bool operator<= (const Date&); + bool operator>= (const Date&); + + Date operator+ (const int); + Date& operator+= (const int); + Date& operator-= (const int); + + time_t operator- (const Date&); + +protected: + time_t mT; +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000..490c943bb --- /dev/null +++ b/src/Makefile @@ -0,0 +1,429 @@ +# Makefile.in generated by automake 1.10 from Makefile.am. +# src/Makefile. Generated from Makefile.in by configure. + +# 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. + + + + +pkgdatadir = $(datadir)/task +pkglibdir = $(libdir)/task +pkgincludedir = $(includedir)/task +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 = : +bin_PROGRAMS = task$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +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) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/auto.h +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) +am_task_OBJECTS = Config.$(OBJEXT) Date.$(OBJEXT) T.$(OBJEXT) \ + TDB.$(OBJEXT) Table.$(OBJEXT) color.$(OBJEXT) parse.$(OBJEXT) \ + task.$(OBJEXT) util.$(OBJEXT) text.$(OBJEXT) rules.$(OBJEXT) +task_OBJECTS = $(am_task_OBJECTS) +task_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I. -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(task_SOURCES) +DIST_SOURCES = $(task_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = ${SHELL} /Users/paul/work/task_rel/missing --run aclocal-1.10 +AMTAR = ${SHELL} /Users/paul/work/task_rel/missing --run tar +AUTOCONF = ${SHELL} /Users/paul/work/task_rel/missing --run autoconf +AUTOHEADER = ${SHELL} /Users/paul/work/task_rel/missing --run autoheader +AUTOMAKE = ${SHELL} /Users/paul/work/task_rel/missing --run automake-1.10 +AWK = awk +CC = gcc +CCDEPMODE = depmode=gcc3 +CFLAGS = -g -O2 +CPPFLAGS = +CXX = g++ +CXXCPP = g++ -E +CXXDEPMODE = depmode=gcc3 +CXXFLAGS = -g -O2 +CYGPATH_W = echo +DEFS = -DHAVE_CONFIG_H +DEPDIR = .deps +ECHO_C = \c +ECHO_N = +ECHO_T = +EGREP = /usr/bin/grep -E +EXEEXT = +GREP = /usr/bin/grep +INSTALL = /usr/bin/install -c +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_PROGRAM = ${INSTALL} +INSTALL_SCRIPT = ${INSTALL} +INSTALL_STRIP_PROGRAM = $(install_sh) -c -s +LDFLAGS = +LIBOBJS = ${LIBOBJDIR}mktime$U.o +LIBS = -lncurses +LTLIBOBJS = ${LIBOBJDIR}mktime$U.lo +MAKEINFO = ${SHELL} /Users/paul/work/task_rel/missing --run makeinfo +MKDIR_P = .././install-sh -c -d +OBJEXT = o +PACKAGE = task +PACKAGE_BUGREPORT = bugs@beckingham.net +PACKAGE_NAME = task +PACKAGE_STRING = task 0.9.0 +PACKAGE_TARNAME = task +PACKAGE_VERSION = 0.9.0 +PATH_SEPARATOR = : +SET_MAKE = +SHELL = /bin/sh +STRIP = +VERSION = 0.9.0 +abs_builddir = /Users/paul/work/task_rel/src +abs_srcdir = /Users/paul/work/task_rel/src +abs_top_builddir = /Users/paul/work/task_rel +abs_top_srcdir = /Users/paul/work/task_rel +ac_ct_CC = gcc +ac_ct_CXX = g++ +am__include = include +am__leading_dot = . +am__quote = +am__tar = ${AMTAR} chof - "$$tardir" +am__untar = ${AMTAR} xf - +bindir = ${exec_prefix}/bin +build_alias = +builddir = . +datadir = ${datarootdir} +datarootdir = ${prefix}/share +docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} +dvidir = ${docdir} +exec_prefix = ${prefix} +host_alias = +htmldir = ${docdir} +includedir = ${prefix}/include +infodir = ${datarootdir}/info +install_sh = $(SHELL) /Users/paul/work/task_rel/install-sh +libdir = ${exec_prefix}/lib +libexecdir = ${exec_prefix}/libexec +localedir = ${datarootdir}/locale +localstatedir = ${prefix}/var +mandir = ${datarootdir}/man +mkdir_p = $(top_builddir)/./install-sh -c -d +oldincludedir = /usr/include +pdfdir = ${docdir} +prefix = /usr/local +program_transform_name = s,x,x, +psdir = ${docdir} +sbindir = ${exec_prefix}/sbin +sharedstatedir = ${prefix}/com +srcdir = . +subdirs = src +sysconfdir = ${prefix}/etc +target_alias = +top_builddir = .. +top_srcdir = .. +task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp color.cpp parse.cpp task.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h color.h stlmacros.h task.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +task$(EXEEXT): $(task_OBJECTS) $(task_DEPENDENCIES) + @rm -f task$(EXEEXT) + $(CXXLINK) $(task_OBJECTS) $(task_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +include ./$(DEPDIR)/Config.Po +include ./$(DEPDIR)/Date.Po +include ./$(DEPDIR)/T.Po +include ./$(DEPDIR)/TDB.Po +include ./$(DEPDIR)/Table.Po +include ./$(DEPDIR)/color.Po +include ./$(DEPDIR)/parse.Po +include ./$(DEPDIR)/rules.Po +include ./$(DEPDIR)/task.Po +include ./$(DEPDIR)/text.Po +include ./$(DEPDIR)/util.Po + +.cpp.o: + $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< + mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +# source='$<' object='$@' libtool=no \ +# DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) \ +# $(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: + $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` + mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +# source='$<' object='$@' libtool=no \ +# DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) \ +# $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +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: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + 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; }'`; \ + 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: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + 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; }'`; \ + 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) + @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 +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +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-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-info: install-info-am + +install-man: + +install-pdf: install-pdf-am + +install-ps: install-ps-am + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + 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 maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + +# 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/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..611286339 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,2 @@ +bin_PROGRAMS = task +task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp color.cpp parse.cpp task.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h color.h stlmacros.h task.h diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 000000000..cbb597d9a --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,429 @@ +# 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 = : +bin_PROGRAMS = task$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +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) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/auto.h +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) +am_task_OBJECTS = Config.$(OBJEXT) Date.$(OBJEXT) T.$(OBJEXT) \ + TDB.$(OBJEXT) Table.$(OBJEXT) color.$(OBJEXT) parse.$(OBJEXT) \ + task.$(OBJEXT) util.$(OBJEXT) text.$(OBJEXT) rules.$(OBJEXT) +task_OBJECTS = $(am_task_OBJECTS) +task_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(task_SOURCES) +DIST_SOURCES = $(task_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +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@ +subdirs = @subdirs@ +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 color.cpp parse.cpp task.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h color.h stlmacros.h task.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +task$(EXEEXT): $(task_OBJECTS) $(task_DEPENDENCIES) + @rm -f task$(EXEEXT) + $(CXXLINK) $(task_OBJECTS) $(task_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Date.Po@am__quote@ +@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)/color.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rules.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/task.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/text.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Po@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +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: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + 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; }'`; \ + 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: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + 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; }'`; \ + 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) + @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 +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +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-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-info: install-info-am + +install-man: + +install-pdf: install-pdf-am + +install-ps: install-ps-am + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + 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 maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + +# 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/src/T.cpp b/src/T.cpp new file mode 100644 index 000000000..4ea22deb3 --- /dev/null +++ b/src/T.cpp @@ -0,0 +1,510 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include "task.h" +#include "T.h" + +//////////////////////////////////////////////////////////////////////////////// +// Default +T::T () +{ + mUUID = uuid (); + mStatus = pending; + mId = 0; + mTags.clear (); + mAttributes.clear (); + mDescription = ""; +} + +//////////////////////////////////////////////////////////////////////////////// +// Initialize by parsing storage format +T::T (const std::string& line) +{ + parse (line); +} + +//////////////////////////////////////////////////////////////////////////////// +T::T (const T& other) +{ + mStatus = other.mStatus; + mUUID = other.mUUID; + mId = other.mId; + mDescription = other.mDescription; + mTags = other.mTags; + mRemoveTags = other.mRemoveTags; + mAttributes = other.mAttributes; +} + +//////////////////////////////////////////////////////////////////////////////// +T& T::operator= (const T& other) +{ + if (this != &other) + { + mStatus = other.mStatus; + mUUID = other.mUUID; + mId = other.mId; + mDescription = other.mDescription; + mTags = other.mTags; + mRemoveTags = other.mRemoveTags; + mAttributes = other.mAttributes; + } + + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +T::~T () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +bool T::hasTag (const std::string& tag) const +{ + std::vector ::const_iterator it = find (mTags.begin (), mTags.end (), tag); + if (it != mTags.end ()) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// SPECIAL METHOD - DO NOT REMOVE +void T::getRemoveTags (std::vector& all) +{ + all = mRemoveTags; +} + +//////////////////////////////////////////////////////////////////////////////// +// SPECIAL METHOD - DO NOT REMOVE +void T::addRemoveTag (const std::string& tag) +{ + if (tag.find (' ') != std::string::npos) + throw std::string ("T::addRemoveTag - tags may not contain spaces"); + + mRemoveTags.push_back (tag); +} + +//////////////////////////////////////////////////////////////////////////////// +void T::getTags (std::vector& all) const +{ + all = mTags; +} + +//////////////////////////////////////////////////////////////////////////////// +void T::addTag (const std::string& tag) +{ + if (tag.find (' ') != std::string::npos) + throw std::string ("T::addTag - tags may not contain spaces"); + + if (tag[0] == '+') + { + if (! hasTag (tag.substr (1, std::string::npos))) + mTags.push_back (tag.substr (1, std::string::npos)); + } + else + { + if (! hasTag (tag)) + mTags.push_back (tag); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void T::addTags (const std::vector & tags) +{ + for (unsigned int i = 0; i < tags.size (); ++i) + { + if (tags[i].find (' ') != std::string::npos) + throw std::string ("T::addTags - tags may not contain spaces"); + + if (tags[i][0] == '+') + { + if (! hasTag (tags[i].substr (1, std::string::npos))) + mTags.push_back (tags[i].substr (1, std::string::npos)); + } + else + { + if (! hasTag (tags[i])) + mTags.push_back (tags[i]); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void T::removeTag (const std::string& tag) +{ + std::vector copy; + for (unsigned int i = 0; i < mTags.size (); ++i) + if (mTags[i] != tag) + copy.push_back (mTags[i]); + + mTags = copy; +} + +//////////////////////////////////////////////////////////////////////////////// +void T::removeTags () +{ + mTags.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +void T::getAttributes (std::map& all) +{ + all = mAttributes; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string T::getAttribute (const std::string& name) +{ + if (mAttributes.find (name) != mAttributes.end ()) + return mAttributes[name]; + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +void T::setAttribute (const std::string& name, const std::string& value) +{ + if (name.find (' ') != std::string::npos) + throw std::string ("An attribute name may not contain spaces"); + + if (value.find (' ') != std::string::npos) + throw std::string ("An attribute value may not contain spaces"); + + mAttributes[name] = value; +} + +//////////////////////////////////////////////////////////////////////////////// +void T::setAttributes (const std::map & attributes) +{ + foreach (i, attributes) + { + if (i->first.find (' ') != std::string::npos) + throw std::string ("An attribute name may not contain spaces"); + + if (i->second.find (' ') != std::string::npos) + throw std::string ("An attribute value may not contain spaces"); + + mAttributes[i->first] = i->second; + } +} + +//////////////////////////////////////////////////////////////////////////////// +void T::removeAttributes () +{ + mAttributes.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +void T::removeAttribute (const std::string& name) +{ + std::map copy = mAttributes; + mAttributes.clear (); + foreach (i, copy) + if (i->first != name) + mAttributes[i->first] = i->second; +} + +//////////////////////////////////////////////////////////////////////////////// +void T::getSubstitution (std::string& from, std::string& to) const +{ + from = mFrom; + to = mTo; +} + +//////////////////////////////////////////////////////////////////////////////// +void T::setSubstitution (const std::string& from, const std::string& to) +{ + mFrom = from; + mTo = to; +} + +//////////////////////////////////////////////////////////////////////////////// +// uuid status [tags] [attributes] description +// +// uuid \x{8}-\x{4}-\x{4}-\x{4}-\x{12} +// status - O X +// tags \w+ \s ... +// attributes \w+:\w+ \s ... +// description .+ +// +const std::string T::compose () const +{ + // UUID + std::string line = mUUID + ' '; + + // Status + if (mStatus == pending) line += "- ["; + else if (mStatus == completed) line += "+ ["; + else if (mStatus == deleted) line += "X ["; + + // Tags + for (unsigned int i = 0; i < mTags.size (); ++i) + { + line += (i > 0 ? " " : ""); + line += mTags[i]; + } + + line += "] ["; + + // Attributes + int count = 0; + foreach (i, mAttributes) + { + std::string converted = i->second; + + // Date attributes may need conversion to epoch. + if (i->first == "due" || + i->first == "start" || + i->first == "entry" || + i->first == "end") + { + if (i->second.find ("/") != std::string::npos) + validDate (converted); + } + + line += (count > 0 ? " " : ""); + line += i->first + ":" + converted; + + ++count; + } + + line += "] "; + + // Description + line += mDescription; + line += "\n"; + + if (line.length () > T_LINE_MAX) + throw std::string ("Line too long"); + + return line; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string T::composeCSV () +{ + // UUID + std::string line = "'" + mUUID + "',"; + + // Status + if (mStatus == pending) line += "'pending',"; + else if (mStatus == completed) line += "'completed',"; + else if (mStatus == deleted) line += "'deleted',"; + + // Tags + line += "'"; + for (unsigned int i = 0; i < mTags.size (); ++i) + { + line += (i > 0 ? " " : ""); + line += mTags[i]; + } + + line += "',"; + std::string value = mAttributes["entry"]; + line += value + ","; + + value = mAttributes["start"]; + if (value != "") + line += value; + line += ","; + + value = mAttributes["due"]; + if (value != "") + line += value; + line += ","; + + value = mAttributes["end"]; + if (value != "") + line += value; + line += ","; + + value = mAttributes["project"]; + if (value != "") + line += "'" + value + "'"; + line += ","; + + value = mAttributes["priority"]; + if (value != "") + line += "'" + value + "'"; + line += ","; + + value = mAttributes["fg"]; + if (value != "") + line += "'" + value + "'"; + line += ","; + + value = mAttributes["bg"]; + if (value != "") + line += "'" + value + "'"; + line += ","; + + line += "'" + mDescription + "'\n"; + + return line; +} + +//////////////////////////////////////////////////////////////////////////////// +// Read all file formats, write only the latest. +void T::parse (const std::string& line) +{ + switch (determineVersion (line)) + { + // File format version 1, from 2006.11.27 - 2007.12.31 + case 1: + { + // Generate a UUID for forward support. + mUUID = uuid (); + + if (line.length () > 6) // ^\[\]\s\[\]\n + { + if (line[0] == 'X') + setStatus (deleted); + + unsigned int openTagBracket = line.find ("["); + unsigned int closeTagBracket = line.find ("]", openTagBracket); + if (openTagBracket != std::string::npos && + closeTagBracket != std::string::npos) + { + unsigned int openAttrBracket = line.find ("[", closeTagBracket); + unsigned int closeAttrBracket = line.find ("]", openAttrBracket); + if (openAttrBracket != std::string::npos && + closeAttrBracket != std::string::npos) + { + std::string tags = line.substr ( + openTagBracket + 1, closeTagBracket - openTagBracket - 1); + std::vector rawTags; + split (mTags, tags, ' '); + + std::string attributes = line.substr ( + openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1); + std::vector pairs; + split (pairs, attributes, ' '); + for (unsigned int i = 0; i < pairs.size (); ++i) + { + std::vector pair; + split (pair, pairs[i], ':'); + if (pair[1] != "") + mAttributes[pair[0]] = pair[1]; + } + + mDescription = line.substr (closeAttrBracket + 2, std::string::npos); + } + else + throw std::string ("Missing attribute brackets"); + } + else + throw std::string ("Missing tag brackets"); + } + else + throw std::string ("Line too short"); + } + break; + + // File format version 2, from 2008.1.1 + case 2: + { + if (line.length () > 46) // ^.{36} . \[\] \[\] \n + { + mUUID = line.substr (0, 36); + + mStatus = line[37] == '+' ? completed + : line[37] == 'X' ? deleted + : pending; + + unsigned int openTagBracket = line.find ("["); + unsigned int closeTagBracket = line.find ("]", openTagBracket); + if (openTagBracket != std::string::npos && + closeTagBracket != std::string::npos) + { + unsigned int openAttrBracket = line.find ("[", closeTagBracket); + unsigned int closeAttrBracket = line.find ("]", openAttrBracket); + if (openAttrBracket != std::string::npos && + closeAttrBracket != std::string::npos) + { + std::string tags = line.substr ( + openTagBracket + 1, closeTagBracket - openTagBracket - 1); + std::vector rawTags; + split (mTags, tags, ' '); + + std::string attributes = line.substr ( + openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1); + std::vector pairs; + split (pairs, attributes, ' '); + for (unsigned int i = 0; i < pairs.size (); ++i) + { + std::vector pair; + split (pair, pairs[i], ':'); + if (pair[1] != "") + mAttributes[pair[0]] = pair[1]; + } + + mDescription = line.substr (closeAttrBracket + 2, std::string::npos); + } + else + throw std::string ("Missing attribute brackets"); + } + else + throw std::string ("Missing tag brackets"); + } + else + throw std::string ("Line too short"); + } + break; + + default: + throw std::string (); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// If this code is inaccurate, data corruption ensues. +int T::determineVersion (const std::string& line) +{ + // Version 1 looks like: + // + // [tags] [attributes] description\n + // X [tags] [attributes] description\n + // + // Scan for the first character being either the bracket or X. + if (line[0] == '[' || + line[0] == 'X') + return 1; + + // Version 2 looks like: + // + // uuid status [tags] [attributes] description\n + // + // Where uuid looks like: + // + // 27755d92-c5e9-4c21-bd8e-c3dd9e6d3cf7 + // + // Scan for the hyphens in the uuid, the following space, and a valid status + // character. + if (line[8] == '-' && + line[13] == '-' && + line[18] == '-' && + line[23] == '-' && + line[36] == ' ' && + (line[37] == '-' || line[37] == '+' || line[37] == 'X')) + return 2; + + // Version 3? + // + // Fortunately, with the hindsight that will come with version 3, the + // identifying characteristics of 1 and 2 may be modified such that if 3 has + // a UUID followed by a status, then there is still a way to differentiate + // between 2 and 3. + // + // The danger is that a version 2 binary reads and misinterprets a version 2 + // file. This is why it is a good idea to rely on an explicit version + // declaration rather than chance positioning. + + // Zero means 'no idea'. + return 0; +} + diff --git a/src/T.h b/src/T.h new file mode 100644 index 000000000..0432eb699 --- /dev/null +++ b/src/T.h @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2007, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_T +#define INCLUDED_T + +#include +#include +#include + +// Length of longest line. +#define T_LINE_MAX 8192 + +class T +{ +public: + enum status {pending, completed, deleted}; + + T (); // Default constructor + T (const std::string&); // Initialize by parsing storage format + T (const T&); // Copy constructor + T& operator= (const T&); // Assignment operator + ~T (); // Destructor + + std::string getUUID () const { return mUUID; } + void setUUID (const std::string& uuid) { mUUID = uuid; } + + int getId () const { return mId; } + void setId (int id) { mId = id; } + + status getStatus () const { return mStatus; } + void setStatus (status s) { mStatus = s; } + + const std::string getDescription () const { return mDescription; } + void setDescription (const std::string& description) { mDescription = description; } + + void getSubstitution (std::string&, std::string&) const; + void setSubstitution (const std::string&, const std::string&); + + bool hasTag (const std::string&) const; + + void getRemoveTags (std::vector&); // SPECIAL + void addRemoveTag (const std::string&); // SPECIAL + + void getTags (std::vector&) const; + void addTag (const std::string&); + void addTags (const std::vector &); + void removeTag (const std::string&); + void removeTags (); + void getAttributes (std::map&); + const std::string getAttribute (const std::string&); + void setAttribute (const std::string&, const std::string&); + void setAttributes (const std::map &); + void removeAttribute (const std::string&); + void removeAttributes (); + + const std::string compose () const; + const std::string composeCSV (); + void parse (const std::string&); + +private: + int determineVersion (const std::string&); + +private: + status mStatus; + std::string mUUID; + int mId; + std::string mDescription; + std::vector mTags; + std::vector mRemoveTags; + std::map mAttributes; + + std::string mFrom; + std::string mTo; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/TDB.cpp b/src/TDB.cpp new file mode 100644 index 000000000..071b4f101 --- /dev/null +++ b/src/TDB.cpp @@ -0,0 +1,467 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2007, 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include + +#include "task.h" +#include "TDB.h" + +//////////////////////////////////////////////////////////////////////////////// +TDB::TDB () +: mPendingFile ("") +, mCompletedFile ("") +, mLogFile ("") +{ +} + +//////////////////////////////////////////////////////////////////////////////// +TDB::~TDB () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB::dataDirectory (const std::string& directory) +{ + if (! access (directory.c_str (), F_OK)) + { + mPendingFile = directory + "/pending.data"; + mCompletedFile = directory + "/completed.data"; + mLogFile = directory + "/command.log"; + } + else + { + std::string error = "Directory '"; + error += directory; + error += "' does not exist, or is not readable and writable."; + throw error; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Combine allPendingT with allCompletedT. +// Note: this method is O(N1) + O(N2), where N2 is not bounded. +bool TDB::allT (std::vector & all) const +{ + all.clear (); + + // Retrieve all the pending records. + std::vector allp; + if (allPendingT (allp)) + { + std::vector ::iterator i; + for (i = allp.begin (); i != allp.end (); ++i) + all.push_back (*i); + + // Retrieve all the completed records. + std::vector allc; + if (allCompletedT (allc)) + { + for (i = allc.begin (); i != allc.end (); ++i) + all.push_back (*i); + + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Only accesses to the pending file result in Tasks that have assigned ids. +bool TDB::pendingT (std::vector & all) const +{ + all.clear (); + + std::vector lines; + if (readLockedFile (mPendingFile, lines)) + { + int id = 1; + + std::vector ::iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + T t (*it); + t.setId (id++); + if (t.getStatus () == T::pending) + all.push_back (t); + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Only accesses to the pending file result in Tasks that have assigned ids. +bool TDB::allPendingT (std::vector & all) const +{ + all.clear (); + + std::vector lines; + if (readLockedFile (mPendingFile, lines)) + { + int id = 1; + + std::vector ::iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + T t (*it); + t.setId (id++); + all.push_back (t); + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::completedT (std::vector & all) const +{ + all.clear (); + + std::vector lines; + if (readLockedFile (mCompletedFile, lines)) + { + std::vector ::iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + T t (*it); + if (t.getStatus () != T::deleted) + all.push_back (t); + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::allCompletedT (std::vector & all) const +{ + all.clear (); + + std::vector lines; + if (readLockedFile (mCompletedFile, lines)) + { + std::vector ::iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + T t (*it); + all.push_back (t); + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::deleteT (const T& t) const +{ + T task (t); + + std::vector all; + allPendingT (all); + + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + if (task.getId () == it->getId ()) + { + it->setStatus (T::deleted); + + char endTime[16]; + sprintf (endTime, "%u", (unsigned int) time (NULL)); + it->setAttribute ("end", endTime); + + return overwritePending (all); + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::completeT (const T& t) const +{ + T task (t); + + std::vector all; + allPendingT (all); + + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + if (task.getId () == it->getId ()) + { + it->setStatus (T::completed); + + char endTime[16]; + sprintf (endTime, "%u", (unsigned int) time (NULL)); + it->setAttribute ("end", endTime); + + return overwritePending (all); + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::addT (const T& t) const +{ + T task (t); + + std::vector tags; + task.getTags (tags); + + // TODO This logic smells funny. + // +tag or -tag are both considered valid tags to add to a new pending task. + // Generating an error here would not be friendly. + for (unsigned int i = 0; i < tags.size (); ++i) + { + if (tags[i][0] == '-' || tags[i][0] == '+') + { + task.removeTag (tags[i]); + task.addTag (tags[i].substr (1, std::string::npos)); + } + } + + if (task.getStatus () == T::pending) + return writePending (task); + + return writeCompleted (task); +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::modifyT (const T& t) const +{ + T modified (t); + + std::vector all; + allPendingT (all); + + std::vector pending; + + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + if (it->getId () == t.getId ()) + { + modified.setUUID (it->getUUID ()); + pending.push_back (modified); + } + else + pending.push_back (*it); + } + + return overwritePending (pending); +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::logRead (std::vector & entries) const +{ + entries.clear (); + return readLockedFile (mLogFile, entries); +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::logCommand (int argc, char** argv) const +{ + // Get time info. + time_t now; + time (&now); + struct tm* t = localtime (&now); + + // Generate timestamp. + char timestamp[20]; + sprintf (timestamp, "%04d-%02d-%02d %02d:%02d:%02d", + t->tm_year + 1900, + t->tm_mon + 1, + t->tm_mday, + t->tm_hour, + t->tm_min, + t->tm_sec); + + std::string command = timestamp; + command += " \""; + for (int i = 0; i < argc; ++i) + command += std::string (i ? " " : "") + argv[i]; + command += "\"\n"; + + if (! access (mLogFile.c_str (), F_OK | W_OK)) + { + FILE* out; + if ((out = fopen (mLogFile.c_str (), "a"))) + { +#ifdef HAVE_FLOCK + int retry = 0; + while (flock (fileno (out), LOCK_EX) && ++retry <= 3) + delay (0.25); +#endif + + fprintf (out, command.c_str ()); + + fclose (out); + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::lock (FILE* file) const +{ +#ifdef HAVE_FLOCK + return flock (fileno (file), LOCK_EX) ? false : true; +#else + return true; +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::overwritePending (std::vector & all) const +{ + // Write a single task to the pending file + 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 + + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + fprintf (out, it->compose ().c_str ()); + + fclose (out); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::writePending (const T& t) const +{ + // Write a single task to the pending file + 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 + + fprintf (out, t.compose ().c_str ()); + + fclose (out); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::writeCompleted (const T& t) const +{ + // Write a single task to the pending file + 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 + + fprintf (out, t.compose ().c_str ()); + + fclose (out); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool TDB::readLockedFile ( + const std::string& file, + std::vector & contents) const +{ + contents.clear (); + + if (! access (file.c_str (), F_OK | R_OK)) + { + 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 + + char line[T_LINE_MAX]; + while (fgets (line, T_LINE_MAX, in)) + { + int length = ::strlen (line); + if (length > 1) + { + line[length - 1] = '\0'; // Kill \n + contents.push_back (line); + } + } + + fclose (in); + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +int TDB::gc () const +{ + int count = 0; + + // Read everything from the pending file. + std::vector all; + allPendingT (all); + + // A list of the truly pending tasks. + std::vector pending; + + std::vector::iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + // Some tasks stay in the pending file. + if (it->getStatus () == T::pending) + pending.push_back (*it); + + // Others are transferred to the completed file. + else + { + writeCompleted (*it); + ++count; + } + } + + // Dump all clean tasks into pending. + overwritePending (pending); + return count; +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/TDB.h b/src/TDB.h new file mode 100644 index 000000000..ba8cd9bc9 --- /dev/null +++ b/src/TDB.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2007, 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_TDB +#define INCLUDED_TDB + +#include +#include +#include "T.h" + +class TDB +{ +public: + TDB (); + ~TDB (); + + void dataDirectory (const std::string&); + bool allT (std::vector &) const; + bool pendingT (std::vector &) const; + bool allPendingT (std::vector &) const; + bool completedT (std::vector &) const; + bool allCompletedT (std::vector &) const; + bool deleteT (const T&) const; + bool completeT (const T&) const; + bool addT (const T&) const; + bool modifyT (const T&) const; + bool logRead (std::vector &) const; + bool logCommand (int, char**) const; + int gc () const; + +private: + bool lock (FILE*) const; + bool overwritePending (std::vector &) const; + bool writePending (const T&) const; + bool writeCompleted (const T&) const; + bool readLockedFile (const std::string&, std::vector &) const; + +private: + std::string mPendingFile; + std::string mCompletedFile; + std::string mLogFile; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Table.cpp b/src/Table.cpp new file mode 100644 index 000000000..e2921abdb --- /dev/null +++ b/src/Table.cpp @@ -0,0 +1,1064 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. +// All rights reserved. +// +// +// Attributes Table Row Column Cell +// ---------------------------------------------------- +// foreground color Y Y Y Y +// background color Y Y Y Y +// padding Y Y Y Y +// wrap Y Y Y Y +// width Y - Y Y +// height - Y - Y +// justification Y Y Y Y +// +// Precedence +// If attributes conflict, the precedence order is: +// cell +// row +// column +// table +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include "Table.h" +#include "Date.h" +#include "stlmacros.h" +#include "task.h" + +//////////////////////////////////////////////////////////////////////////////// +Table::Table () + : mRows (0) + , mIntraPadding (1) + , mTablePadding (0) + , mTableWidth (0) + , mSuppressWS (false) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Table::~Table () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setTableColor (Text::color fg, Text::color bg) +{ + mFg["table"] = fg; + mBg["table"] = bg; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setTableFg (Text::color c) +{ + mFg["table"] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setTableBg (Text::color c) +{ + mBg["table"] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setTablePadding (int padding) +{ + mTablePadding = padding; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setTableIntraPadding (int padding) +{ + mIntraPadding = padding; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setTableWidth (int width) +{ + assert (width > 0); + mTableWidth = width; +} + +//////////////////////////////////////////////////////////////////////////////// +int Table::addColumn (const std::string& col) +{ + mSpecifiedWidth.push_back (minimum); + mMaxDataWidth.push_back (col.length ()); + mCalculatedWidth.push_back (0); + mColumnPadding.push_back (0); + + mColumns.push_back (col); + return mColumns.size () - 1; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnColor (int column, Text::color fg, Text::color bg) +{ + char id[12]; + sprintf (id, "col:%d", column); + mFg[id] = fg; + mBg[id] = bg; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnFg (int column, Text::color c) +{ + char id[12]; + sprintf (id, "col:%d", column); + mFg[id] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnBg (int column, Text::color c) +{ + char id[12]; + sprintf (id, "col:%d", column); + mBg[id] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnUnderline (int column) +{ + char id[12]; + sprintf (id, "col:%d", column); + mUnderline[id] = Text::underline; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnPadding (int column, int padding) +{ + mColumnPadding[column] = padding; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnWidth (int column, int width) +{ + assert (width > 0); + mSpecifiedWidth[column] = width; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnWidth (int column, sizing s) +{ + mSpecifiedWidth[column] = (int) s; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnCommify (int column) +{ + mCommify[column] = true; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setColumnJustification (int column, just j) +{ + char id[12]; + sprintf (id, "col:%d", column); + mJustification[id] = j; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::sortOn (int column, order o) +{ + mSortColumns.push_back (column); + mSortOrder[column] = o; +} + +//////////////////////////////////////////////////////////////////////////////// +int Table::addRow () +{ + return mRows++; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setRowColor (int row, Text::color fg, Text::color bg) +{ + char id[12]; + sprintf (id, "row:%d", row); + mFg[id] = fg; + mBg[id] = bg; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setRowFg (int row, Text::color c) +{ + char id[12]; + sprintf (id, "row:%d", row); + mFg[id] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setRowBg (int row, Text::color c) +{ + char id[12]; + sprintf (id, "row:%d", row); + mBg[id] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::addCell (int row, int col, const std::string& data) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + + int length = 0; + + if (mSuppressWS) + { + std::string data2; + if (mCommify.find (col) != mCommify.end ()) + data2 = commify (data); + else + data2 = data; + + clean (data2); + length = data2.length (); + mData[id] = data2; + } + else + { + if (mCommify.find (col) != mCommify.end ()) + mData[id] = commify (data); + else + mData[id] = data; + + length = data.length (); + } + + // Automatically maintain max width. + mMaxDataWidth[col] = max (mMaxDataWidth[col], length); +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::addCell (int row, int col, char data) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + + char value[2]; + sprintf (value, "%c", data); + + mData[id] = value; + + // Automatically maintain max width. + mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value)); +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::addCell (int row, int col, int data) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + + char value[12]; + sprintf (value, "%d", data); + + if (mCommify.find (col) != mCommify.end ()) + mData[id] = commify (value); + else + mData[id] = value; + + // Automatically maintain max width. + mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value)); +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::addCell (int row, int col, float data) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + + char value[24]; + sprintf (value, "%.2f", data); + + if (mCommify.find (col) != mCommify.end ()) + mData[id] = commify (value); + else + mData[id] = value; + + // Automatically maintain max width. + mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value)); +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::addCell (int row, int col, double data) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + + char value[24]; + sprintf (value, "%.6f", data); + + if (mCommify.find (col) != mCommify.end ()) + mData[id] = commify (value); + else + mData[id] = value; + + // Automatically maintain max width. + mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value)); +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setCellColor (int row, int col, Text::color fg, Text::color bg) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + mFg[id] = fg; + mBg[id] = bg; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setCellFg (int row, int col, Text::color c) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + mFg[id] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::setCellBg (int row, int col, Text::color c) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + mBg[id] = c; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Table::getCell (int row, int col) +{ + char id[24]; + sprintf (id, "cell:%d,%d", row, col); + return mData[id]; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::color Table::getFg (int row, int col) +{ + char idCell[24]; + sprintf (idCell, "cell:%d,%d", row, col); + if (mFg.find (idCell) != mFg.end ()) + return mFg[idCell]; + + char idRow[12]; + sprintf (idRow, "row:%d", row); + if (mFg.find (idRow) != mFg.end ()) + return mFg[idRow]; + + char idCol[12]; + sprintf (idCol, "col:%d", col); + if (mFg.find (idCol) != mFg.end ()) + return mFg[idCol]; + + if (mFg.find ("table") != mFg.end ()) + return mFg["table"]; + + return Text::nocolor; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::color Table::getHeaderFg (int col) +{ + char idCol[12]; + sprintf (idCol, "col:%d", col); + + return mFg.find (idCol) != mFg.end () ? mFg[idCol] + : mFg.find ("table") != mFg.end () ? mFg["table"] + : Text::nocolor; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::color Table::getBg (int row, int col) +{ + char idCell[24]; + sprintf (idCell, "cell:%d,%d", row, col); + if (mBg.find (idCell) != mBg.end ()) + return mBg[idCell]; + + char idRow[12]; + sprintf (idRow, "row:%d", row); + if (mBg.find (idRow) != mBg.end ()) + return mBg[idRow]; + + char idCol[12]; + sprintf (idCol, "col:%d", col); + if (mBg.find (idCol) != mBg.end ()) + return mBg[idCol]; + + if (mBg.find ("table") != mBg.end ()) + return mBg["table"]; + + return Text::nocolor; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::color Table::getHeaderBg (int col) +{ + char idCol[12]; + sprintf (idCol, "col:%d", col); + + return mBg.find (idCol) != mBg.end () ? mBg[idCol] + : mBg.find ("table") != mBg.end () ? mBg["table"] + : Text::nocolor; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::attr Table::getHeaderUnderline (int col) +{ + char idCol[12]; + sprintf (idCol, "col:%d", col); + + return mUnderline.find (idCol) != mUnderline.end () ? Text::underline + : Text::normal; +} + +//////////////////////////////////////////////////////////////////////////////// +int Table::getIntraPadding () +{ + return mIntraPadding; +} + +//////////////////////////////////////////////////////////////////////////////// +int Table::getPadding (int col) +{ + return max (mColumnPadding[col], mTablePadding); +} + +//////////////////////////////////////////////////////////////////////////////// +// Using mSpecifiedWidth, mMaxDataWidth, generate mCalculatedWidth. +void Table::calculateColumnWidths () +{ + // Ideal case: either no table width is specified, or everything fits without + // wrapping into mTableWidth. + std::vector ideal = mMaxDataWidth; + int width = 0; + int countFlexible = 0; + for (unsigned int c = 0; c < mColumns.size (); ++c) + { + if (mSpecifiedWidth[c] == flexible) + ++countFlexible; + + else if (mSpecifiedWidth[c] > 0) + ideal[c] = mSpecifiedWidth[c]; + + width += mColumnPadding[c] + + ideal[c] + + mColumnPadding[c] + + (c > 0 ? mIntraPadding : 0); + } + + if (!mTableWidth || width < mTableWidth) + { + mCalculatedWidth = ideal; + return; + } + + // Try again, with available space divided among the flexible columns. + if (countFlexible) + { + ideal = mMaxDataWidth; + width = 0; + for (unsigned int c = 0; c < mColumns.size (); ++c) + { + if (mSpecifiedWidth[c] > 0) + ideal[c] = mSpecifiedWidth[c]; + else if (mSpecifiedWidth[c] == flexible) + { + ideal[c] = 0; + } + + width += mColumnPadding[c] + + ideal[c] + + mColumnPadding[c] + + (c > 0 ? mIntraPadding : 0); + } + + int available = mTableWidth - width; + if (width < mTableWidth) // if there is room to wiggle in + { + int shared = available / countFlexible; + int remainder = available % countFlexible; + + int lastFlexible = mColumns.size () - 1; + for (unsigned int c = 0; c < mColumns.size (); ++c) + { + if (mSpecifiedWidth[c] == flexible) + { + lastFlexible = c; + ideal[c] += shared; + } + } + + // Remainder goes to last column. + ideal[lastFlexible] += remainder; + mCalculatedWidth = ideal; + return; + } +// else +// std::cout << "# insufficient room, considering only flexible columns." << std::endl; + } + + // Try again, treating minimum columns as flexible. +// std::cout << "# no flexible columns. Now what?" << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +Table::just Table::getJustification (int row, int col) +{ + char idCell[24]; + sprintf (idCell, "cell:%d,%d", row, col); + if (mJustification.find (idCell) != mJustification.end ()) + return mJustification[idCell]; + + char idRow[12]; + sprintf (idRow, "row:%d", row); + if (mJustification.find (idRow) != mJustification.end ()) + return mJustification[idRow]; + + char idCol[12]; + sprintf (idCol, "col:%d", col); + if (mJustification.find (idCol) != mJustification.end ()) + return mJustification[idCol]; + + return left; +} + +//////////////////////////////////////////////////////////////////////////////// +Table::just Table::getHeaderJustification (int col) +{ + char idCol[12]; + sprintf (idCol, "col:%d", col); + + return mJustification.find (idCol) != mJustification.end () ? mJustification[idCol] + : mJustification.find ("table") != mJustification.end () ? mJustification["table"] + : left; +} + +//////////////////////////////////////////////////////////////////////////////// +// data One Data to be rendered +// width 8 Max data width for column/specified width +// padding 1 Extra padding around data +// intraPadding 0 Extra padding between columns only +// justification right Alignment withing padding +// +// Returns: +// " One " +// One data +// ^ ^ padding +// ^ intraPadding +// ^^^^^^^^ width +// ^ ^ fg/bg +// +const std::string Table::formatHeader ( + int col, + int width, + int padding) +{ + assert (width > 0); + + Text::color fg = getHeaderFg (col); + Text::color bg = getHeaderBg (col); + std::string data = mColumns[col]; + Text::attr decoration = getHeaderUnderline (col); + + std::string colorOn = ""; + std::string pad = ""; + std::string intraPad = ""; + std::string preJust = ""; + std::string attrOn = ""; + std::string attrOff = ""; + std::string postJust = ""; + std::string colorOff = ""; + + if (fg != Text::nocolor) + { + char c[12]; + sprintf (c, "\033[%dm", fg + 29); + colorOn += c; + } + + if (bg != Text::nocolor) + { + char c[12]; + sprintf (c, "\033[%dm", bg + 39); + colorOn += c; + } + + if (colorOn != "") + colorOff += "\033[0m"; + + if (decoration == Text::underline) + { + attrOn = "\033[4m"; + attrOff = "\033[0m"; + } + + for (int i = 0; i < padding; ++i) + pad += " "; + + // Place the value within the available space - justify. + int gap = width - data.length (); + + for (int i = 0; i < gap; ++i) + postJust += " "; + + if (col < (signed) mColumns.size () - 1) + for (int i = 0; i < getIntraPadding (); ++i) + intraPad += " "; + + return colorOn + + attrOn + + pad + + preJust + + data + + postJust + + pad + + attrOff + + intraPad + + colorOff; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::formatCell ( + int row, + int col, + int width, + int padding, + std::vector & lines, + std::string& blank) +{ + assert (width > 0); + + Text::color fg = getFg (row, col); + Text::color bg = getBg (row, col); + just justification = getJustification (row, col); + std::string data = getCell (row, col); + + std::string colorOn = ""; + std::string pad = ""; + std::string intraPad = ""; + std::string colorOff = ""; + + if (fg != Text::nocolor) + { + char c[12]; + sprintf (c, "\033[%dm", fg + 29); + colorOn += c; + } + + if (bg != Text::nocolor) + { + char c[12]; + sprintf (c, "\033[%dm", bg + 39); + colorOn += c; + } + + if (fg != Text::nocolor || bg != Text::nocolor) + colorOff += "\033[0m"; + + for (int i = 0; i < padding; ++i) + pad += " "; + + if (col < (signed) mColumns.size () - 1) + for (int i = 0; i < getIntraPadding (); ++i) + intraPad += " "; + + // Break the text into chunks of width characters. + std::string preJust; + std::string postJust; + std::vector chunks; + wrapText (chunks, data, width); + for (unsigned int chunk = 0; chunk < chunks.size (); ++chunk) + { + // Place the data within the available space - justify. + int gap = width - chunks[chunk].length (); + + preJust = ""; + postJust = ""; + + if (justification == left) + for (int i = 0; i < gap; ++i) + postJust += " "; + + else if (justification == right) + for (int i = 0; i < gap; ++i) + preJust += " "; + + else if (justification == center) + { + for (int i = 0; i < gap / 2; ++i) + preJust += " "; + + for (unsigned int i = 0; i < gap - preJust.length (); ++i) + postJust += " "; + } + + lines.push_back ( + colorOn + + pad + + preJust + + chunks[chunk] + + postJust + + pad + + intraPad + + colorOff); + } + + // The blank is used to vertically pad cells that have blank lines. + pad = ""; + for (int i = 0; i < width; ++i) + pad += " "; + + blank = + colorOn + + pad + + intraPad + + colorOff; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string Table::formatCell ( + int row, + int col, + int width, + int padding) +{ + assert (width > 0); + + Text::color fg = getFg (row, col); + Text::color bg = getBg (row, col); + just justification = getJustification (row, col); + std::string data = getCell (row, col); + + std::string colorOn = ""; + std::string pad = ""; + std::string intraPad = ""; + std::string preJust = ""; + std::string postJust = ""; + std::string colorOff = ""; + + if (fg != Text::nocolor) + { + char c[12]; + sprintf (c, "\033[%dm", fg + 29); + colorOn += c; + } + + if (bg != Text::nocolor) + { + char c[12]; + sprintf (c, "\033[%dm", bg + 39); + colorOn += c; + } + + if (fg != Text::nocolor || bg != Text::nocolor) + colorOff += "\033[0m"; + + for (int i = 0; i < padding; ++i) + pad += " "; + + // Place the data within the available space - justify. + int gap = width - data.length (); + + if (justification == left) + { + for (int i = 0; i < gap; ++i) + postJust += " "; + } + else if (justification == right) + { + for (int i = 0; i < gap; ++i) + preJust += " "; + } + else if (justification == center) + { + for (int i = 0; i < gap / 2; ++i) + preJust += " "; + + for (unsigned int i = 0; i < gap - preJust.length (); ++i) + postJust += " "; + } + + if (col < (signed) mColumns.size () - 1) + for (int i = 0; i < getIntraPadding (); ++i) + intraPad += " "; + + return colorOn + + pad + + preJust + + data + + postJust + + pad + + intraPad + + colorOff; +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::suppressWS () +{ + mSuppressWS = true; +} + +//////////////////////////////////////////////////////////////////////////////// +int Table::rowCount () +{ + return mRows; +} + +//////////////////////////////////////////////////////////////////////////////// +int Table::columnCount () +{ + return mColumns.size (); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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 +// +void Table::optimize (std::string& output) +{ +/* + TODO Unoptimized length. + int start = output.length (); +*/ + + // \s\n -> \n + unsigned int i = 0; + while ((i = output.find (" \n")) != std::string::npos) + { + output = output.substr (0, i) + + output.substr (i + 1, std::string::npos); + } + +/* + TODO This code displays the % reduction of the optimize function. + std::cout << int ((100 * (start - output.length ()) / start)) + << "%" << std::endl; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// Combsort11, with O(n log n) average, O(n log n) worst case performance. +#define SWAP \ + { \ + int temp = order[r]; \ + order[r] = order[r + gap]; \ + order[r + gap] = temp; \ + swaps = 1; \ + } +void Table::sort (std::vector & order) +{ + int gap = order.size (); + int swaps = 1; + + while (gap > 1 || swaps != 0) + { + if (gap > 1) + { + gap = (int) ((float)gap / 1.3); + if (gap == 10 or gap == 9) + gap = 11; + } + + int r = 0; + swaps = 0; + + while (r + gap < (int) order.size ()) + { + bool keepScanning = true; + for (unsigned int c = 0; keepScanning && c < mSortColumns.size (); ++c) + { + keepScanning = false; + + char left[16]; + sprintf (left, "cell:%d,%d", order[r], mSortColumns[c]); + + char right[16]; + sprintf (right, "cell:%d,%d", order[r + gap], mSortColumns[c]); + + if (mData[left] != mData[right]) + { + switch (mSortOrder[mSortColumns[c]]) + { + case ascendingNumeric: + if (::atoi (mData[left].c_str ()) > ::atoi (mData[right].c_str ())) + SWAP + break; + + case descendingNumeric: + if (::atoi (mData[left].c_str ()) < ::atoi (mData[right].c_str ())) + SWAP + break; + + case ascendingCharacter: + if (mData[left] > mData[right]) + SWAP + break; + + case descendingCharacter: + if (mData[left] < mData[right]) + SWAP + break; + + case ascendingDate: + { + if (mData[left] != "" && mData[right] == "") + break; + + else if (mData[left] == "" && mData[right] != "") + SWAP + + else + { + Date dl (mData[left]); + Date dr (mData[right]); + if (dl > dr) + SWAP + } + } + break; + + case descendingDate: + { + if (mData[left] != "" && mData[right] == "") + break; + + else if (mData[left] == "" && mData[right] != "") + SWAP + + else + { + Date dl (mData[left]); + Date dr (mData[right]); + if (dl < dr) + SWAP + } + } + break; + + case ascendingPriority: + if ((mData[left] == "" && mData[right] != "") || + (mData[left] == "M" && mData[right] == "L") || + (mData[left] == "H" && (mData[right] == "L" || mData[right] == "M"))) + SWAP + break; + + case descendingPriority: + if ((mData[left] == "" && mData[right] != "") || + (mData[left] == "L" && (mData[right] == "M" || mData[right] == "H")) || + (mData[left] == "M" && mData[right] == "H")) + SWAP + break; + } + + break; + } + else + keepScanning = true; + } + + ++r; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void Table::clean (std::string& value) +{ + unsigned int start = 0; + unsigned int pos; + while ((pos = value.find ('\t', start)) != std::string::npos) + { + value.replace (pos, 1, " "); + start = pos; // Not pos + 1, because we have a destructive operation, and + // this is ultimately safer. + } + + while ((pos = value.find ('\r', start)) != std::string::npos) + { + value.replace (pos, 1, " "); + start = pos; + } + + while ((pos = value.find ('\n', start)) != std::string::npos) + { + value.replace (pos, 1, " "); + start = pos; + } +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string Table::render () +{ + calculateColumnWidths (); + + // Print column headers in column order. + std::string output; + for (unsigned int col = 0; col < mColumns.size (); ++col) + output += formatHeader ( + col, + mCalculatedWidth[col], + mColumnPadding[col]); + + output += "\n"; + + // Determine row order, according to sort options. + std::vector order; + for (int row = 0; row < mRows; ++row) + order.push_back (row); + + // Only sort if necessary. + if (mSortColumns.size ()) + sort (order); + + // Print all rows. + for (int row = 0; row < mRows; ++row) + { + std::vector > columns; + std::vector blanks; + + unsigned int maxHeight = 0; + for (unsigned int col = 0; col < mColumns.size (); ++col) + { + std::vector lines; + std::string blank; + formatCell ( + order[row], + col, + mCalculatedWidth[col], + mColumnPadding[col], + lines, + blank); + + columns.push_back (lines); + blanks.push_back (blank); + + maxHeight = max (maxHeight, columns[col].size ()); + } + + if (maxHeight) + { + for (unsigned int lines = 0; lines < maxHeight; ++lines) + { + for (unsigned int col = 0; col < mColumns.size (); ++col) + if (lines < columns[col].size ()) + output += columns[col][lines]; + else + output += blanks[col]; + + output += "\n"; + } + } + else + output += "\n"; + } + + // Eliminate redundant color codes. + optimize (output); + return output; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Table.h b/src/Table.h new file mode 100644 index 000000000..d49d70e4f --- /dev/null +++ b/src/Table.h @@ -0,0 +1,115 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. +// All rights reserved. +// +// TODO Implement height +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_TABLE +#define INCLUDED_TABLE + +#include +#include +#include +#include "color.h" + +class Table +{ +public: + enum just {left, center, right}; + enum order {ascendingNumeric, ascendingCharacter, ascendingPriority, + ascendingDate, descendingNumeric, descendingCharacter, + descendingPriority, descendingDate}; + enum sizing {minimum = -1, flexible = 0}; + + Table (); + virtual ~Table (); + + void setTableColor (Text::color, Text::color); + void setTableFg (Text::color); + void setTableBg (Text::color); + void setTablePadding (int); + void setTableIntraPadding (int); + void setTableWidth (int); + + int addColumn (const std::string&); + void setColumnColor (int, Text::color, Text::color); + void setColumnFg (int, Text::color); + void setColumnBg (int, Text::color); + void setColumnUnderline (int); + void setColumnPadding (int, int); + void setColumnWidth (int, int); + void setColumnWidth (int, sizing); + void setColumnJustification (int, just); + void setColumnCommify (int); + void sortOn (int, order); + + int addRow (); + void setRowColor (int, Text::color, Text::color); + void setRowFg (int, Text::color); + void setRowBg (int, Text::color); + + void addCell (int, int, const std::string&); + void addCell (int, int, char); + void addCell (int, int, int); + void addCell (int, int, float); + void addCell (int, int, double); + void setCellColor (int, int, Text::color, Text::color); + void setCellFg (int, int, Text::color); + void setCellBg (int, int, Text::color); + + void suppressWS (); + + int rowCount (); + int columnCount (); + const std::string render (); + +private: + std::string getCell (int, int); + Text::color getFg (int, int); + Text::color getHeaderFg (int); + Text::color getBg (int, int); + Text::color getHeaderBg (int); + Text::attr getHeaderUnderline (int); + int getPadding (int); + int getIntraPadding (); + void calculateColumnWidths (); + just getJustification (int, int); + just getHeaderJustification (int); + const std::string formatHeader (int, int, int); + const std::string formatCell (int, int, int, int); + void formatCell (int, int, int, int, std::vector &, std::string&); + void optimize (std::string&); + void sort (std::vector &); + void clean (std::string&); + +private: + std::vector mColumns; + int mRows; + int mIntraPadding; + std::map mFg; + std::map mBg; + std::map mUnderline; + + // Padding... + int mTablePadding; + std::vector mColumnPadding; + + // Width... + int mTableWidth; + std::vector mSpecifiedWidth; + std::vector mMaxDataWidth; + std::vector mCalculatedWidth; + + std::map mJustification; + std::map mCommify; + std::map mData; + std::vector mSortColumns; + std::map mSortOrder; + + // Misc... + bool mSuppressWS; +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/color.cpp b/src/color.cpp new file mode 100644 index 000000000..075586001 --- /dev/null +++ b/src/color.cpp @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include "color.h" + +//////////////////////////////////////////////////////////////////////////////// +std::string Text::colorName (Text::color c) +{ + switch (c) + { + case black: return "black"; + case red: return "red"; + case green: return "green"; + case yellow: return "yellow"; + case blue: return "blue"; + case magenta: return "magenta"; + case cyan: return "cyan"; + case white: return "white"; + case nocolor: return ""; + } + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::color Text::colorCode (const std::string& c) +{ + if (c == "black") return black; + if (c == "red") return red; + if (c == "green") return green; + if (c == "yellow") return yellow; + if (c == "blue") return blue; + if (c == "magenta") return magenta; + if (c == "cyan") return cyan; + if (c == "white") return white; + + return nocolor; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Text::attrName (Text::attr a) +{ + switch (a) + { + case underline: return "underline"; + case normal: return ""; + } + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +Text::attr Text::attrCode (const std::string& a) +{ + if (a == "underline") return underline; + + return normal; +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/color.h b/src/color.h new file mode 100644 index 000000000..ce8ab7111 --- /dev/null +++ b/src/color.h @@ -0,0 +1,23 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2008, Paul Beckingham. +// All rights reserved. +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_COLOR +#define INCLUDED_COLOR + +namespace Text +{ + enum color {nocolor = 0, black, red, green, yellow, blue, magenta, cyan, white}; + enum attr {normal = 0, underline}; + + std::string colorName (Text::color); + Text::color colorCode (const std::string&); + + std::string attrName (Text::attr); + Text::attr attrCode (const std::string&); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/library.h b/src/library.h new file mode 100644 index 000000000..92376814e --- /dev/null +++ b/src/library.h @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2004 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_LIBRARY +#define INCLUDED_LIBRARY + +#include +#include +#include + +#include "stlmacros.h" + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +// text.cpp +void wrapText (std::vector &, const std::string&, const int); +std::string trimLeft (const std::string& in, const std::string& t = " "); +std::string trimRight (const std::string& in, const std::string& t = " "); +std::string trim (const std::string& in, const std::string& t = " "); +std::wstring trimLeft (const std::wstring& in, const std::wstring& t = L" "); // UNICODE safe +std::wstring trimRight (const std::wstring& in, const std::wstring& t = L" "); // UNICODE safe +std::wstring trim (const std::wstring& in, const std::wstring& t = L" "); // UNICODE safe +void extractParagraphs (const std::string&, std::vector&); +void extractLine (std::string&, std::string&, int); +void split (std::vector&, const std::string&, const char); +void split (std::vector&, const std::string&, const std::string&); +void join (std::string&, const std::string&, const std::vector&); +std::string commify (const std::string&); +std::string lowerCase (const std::string&); + +// misc.cpp +void delay (float); + +// list.cpp +int autoComplete (const std::string&, const std::vector&, std::vector&); + +// units.cpp +void formatTimeDeltaDays (std::string&, time_t); +std::string formatSeconds (time_t); + +// uuid.cpp +const std::string uuid (); + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/parse.cpp b/src/parse.cpp new file mode 100644 index 000000000..480457448 --- /dev/null +++ b/src/parse.cpp @@ -0,0 +1,329 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include + +#include "Date.h" +#include "task.h" +#include "T.h" + +//////////////////////////////////////////////////////////////////////////////// +static char* colors[] = +{ + "black", + "blue", + "red", + "green", + "magenta", + "cyan", + "yellow", + "white", + "", +}; + +static char* attributes[] = +{ + "project", + "priority", + "fg", + "bg", + "due", + "entry", + "start", + "end", + "", +}; + +static char* commands[] = +{ + "active", + "add", + "calendar", + "completed", + "delete", + "done", + "export", + "history", + "info", + "list", + "long", + "ls", + "next", + "overdue", + "projects", + "start", + "stats", + "summary", + "tags", + "usage", + "version", + "", +}; + +void guess (const std::string& type, char** list, std::string& candidate) +{ + std::vector options; + for (int i = 0; list[i][0]; ++i) + options.push_back (list[i]); + + 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 + "'"; + + else + { + std::string error = "Ambiguous "; + error += type; + error += " '"; + error += candidate; + error += "' - could be either of "; + for (unsigned int i = 0; i < matches.size (); ++i) + { + if (i) + error += ", "; + error += matches[i]; + } + + throw error; + } +} + +//////////////////////////////////////////////////////////////////////////////// +static bool isCommand (const std::string& candidate) +{ + std::vector options; + for (int i = 0; commands[i][0]; ++i) + options.push_back (commands[i]); + + std::vector matches; + autoComplete (candidate, options, matches); + if (0 == matches.size ()) + return false; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool validDate (std::string& date) +{ + unsigned int firstSlash = date.find ("/"); + unsigned int secondSlash = date.find ("/", firstSlash + 1); + if (firstSlash != std::string::npos && + secondSlash != std::string::npos) + { + int m = ::atoi (date.substr (0, firstSlash ).c_str ()); + int d = ::atoi (date.substr (firstSlash + 1, secondSlash - firstSlash).c_str ()); + int y = ::atoi (date.substr (secondSlash + 1, std::string::npos ).c_str ()); + if (!Date::valid (m, d, y)) + throw std::string ("\"") + date + "\" is not a valid date."; + + // Convert to epoch form. + Date dt (m, d, y); + time_t t; + dt.toEpoch (t); + char converted[12]; + sprintf (converted, "%u", (unsigned int) t); + date = converted; + } + else + throw std::string ("Badly formed date - use the MM/DD/YYYY format"); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validPriority (std::string& input) +{ + if (input != "H" && + input != "M" && + input != "L" && + input != "") + throw std::string ("\"") + + input + + "\" is not a valid priority. Use H, M, L or leave blank."; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validAttribute (std::string& name, std::string& value) +{ + guess ("attribute", attributes, name); + + if ((name == "fg" || name == "bg") && value != "") + guess ("color", colors, value); + + else if (name == "due" && value != "") + validDate (value); + + else if (name == "priority") + { + for (std::string::iterator i = value.begin (); i != value.end (); ++i) + *i = ::toupper (*i); + + return validPriority (value); + } + + else if (name == "entry" || + name == "start" || + name == "end") + throw std::string ("\"") + + name + + "\" is not an attribute you may modify directly."; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validId (const std::string& input) +{ + for (unsigned int i = 0; i < input.length (); ++i) + if (!::isdigit (input[i])) + return false; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validTag (std::string& input) +{ + if ((input[0] == '-' || input[0] == '+') && + input.length () > 1) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validDescription (const std::string& input) +{ + if (input.length () > 0) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validCommand (std::string& input) +{ + guess ("command", commands, input); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool validSubstitution ( + std::string& input, + std::string& from, + std::string& to) +{ + unsigned int first = input.find ('/'); + if (first != std::string::npos) + { + unsigned int second = input.find ('/', first + 1); + if (second != std::string::npos) + { + unsigned int third = input.find ('/', second + 1); + if (third != std::string::npos) + { + if (first == 0 && + first < second && + second < third && + third == input.length () - 1) + { + from = input.substr (first + 1, second - first - 1); + to = input.substr (second + 1, third - second - 1); + return true; + } + } + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Token Distinguishing characteristic +// ------- ----------------------------- +// command first positional +// id \d+ +// description default, accumulate +// substitution /\w+/\w*/ +// tags [-+]\w+ +// attributes \w+:.+ +// +void parse ( + std::vector & args, + std::string& command, + T& task) +{ + command = ""; + + std::string descCandidate = ""; + for (unsigned int i = 0; i < args.size (); ++i) + { + std::string arg (args[i]); + unsigned int colon; // Pointer to colon in argument. + std::string from; + std::string to; + + // An id is the first argument found that contains all digits. + if (command != "add" && // "add" doesn't require an ID + task.getId () == 0 && + validId (arg)) + task.setId (::atoi (arg.c_str ())); + + // Tags begin with + or - and contain arbitrary text. + else if (validTag (arg)) + { + if (arg[0] == '+') + task.addTag (arg.substr (1, std::string::npos)); + else if (arg[0] == '-') + task.addRemoveTag (arg.substr (1, std::string::npos)); + } + + // Attributes contain a constant string followed by a colon, followed by a + // value. + else if ((colon = arg.find (":")) != std::string::npos) + { + std::string name = arg.substr (0, colon); + std::string value = arg.substr (colon + 1, std::string::npos); + + if (validAttribute (name, value)) + task.setAttribute (name, value); + } + + // Substitution of description text. + else if (validSubstitution (arg, from, to)) + { + task.setSubstitution (from, to); + } + + // Command. + else if (command == "") + { + if (!isCommand (arg)) + descCandidate += std::string (arg) + " "; + else if (validCommand (arg)) + command = arg; + } + + // Anything else is just considered description. + else + descCandidate += std::string (arg) + " "; + } + + if (validDescription (descCandidate)) + task.setDescription (descCandidate); +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/rules.cpp b/src/rules.cpp new file mode 100644 index 000000000..95b1ce59a --- /dev/null +++ b/src/rules.cpp @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include "Config.h" +#include "Table.h" +#include "Date.h" +#include "T.h" +#include "task.h" + +static std::map gsFg; +static std::map gsBg; + +//////////////////////////////////////////////////////////////////////////////// +// There are three supported variants: +// 1) "fg" +// 2) "on bg" +// 3) "fg on bg" +static void parseColorRule ( + const std::string& rule, + Text::color& fg, + Text::color& bg) +{ + fg = Text::nocolor; + bg = Text::nocolor; + bool error = false; + + std::vector words; + split (words, rule, ' '); + switch (words.size ()) + { + case 1: // "fg" - no spaces. + fg = Text::colorCode (words[0]); + break; + + case 2: // "on bg" - one space, "on" before. + if (words[0] == "on") + bg = Text::colorCode (words[1]); + else + error = true; + break; + + case 3: // "fg on bg" - two spaces, "on" between them. + if (words[1] == "on") + { + fg = Text::colorCode (words[0]); + bg = Text::colorCode (words[2]); + } + else + error = true; + break; + + case 0: + default: + error = true; + break; + } + + if (error) + std::cout << "Malformed color rule '" << rule << "'" << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void initializeColorRules (Config& conf) +{ + std::vector ruleNames; + conf.all (ruleNames); + foreach (it, ruleNames) + { + if (it->substr (0, 6) == "color.") + { + Text::color fg; + Text::color bg; + parseColorRule (conf.get (*it), fg, bg); + gsFg[*it] = fg; + gsBg[*it] = bg; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void autoColorize (T& task, Text::color& fg, Text::color& bg) +{ + fg = Text::nocolor; + bg = Text::nocolor; + + // Colorization of the tagged. + if (gsFg["color.tagged"] != Text::nocolor || + gsBg["color.tagged"] != Text::nocolor) + { + std::vector tags; + task.getTags (tags); + if (tags.size ()) + { + fg = gsFg["color.tagged"]; + bg = gsBg["color.tagged"]; + } + } + + // Colorization of the low priority. + if (gsFg["color.pri.L"] != Text::nocolor || + gsBg["color.pri.L"] != Text::nocolor) + { + if (task.getAttribute ("priority") == "L") + { + fg = gsFg["color.pri.L"]; + bg = gsBg["color.pri.L"]; + } + } + + // Colorization of the medium priority. + if (gsFg["color.pri.M"] != Text::nocolor || + gsBg["color.pri.M"] != Text::nocolor) + { + if (task.getAttribute ("priority") == "M") + { + fg = gsFg["color.pri.M"]; + bg = gsBg["color.pri.M"]; + } + } + + // Colorization of the high priority. + if (gsFg["color.pri.H"] != Text::nocolor || + gsBg["color.pri.H"] != Text::nocolor) + { + if (task.getAttribute ("priority") == "H") + { + fg = gsFg["color.pri.H"]; + bg = gsBg["color.pri.H"]; + } + } + + // Colorization of the priority-less. + if (gsFg["color.pri.none"] != Text::nocolor || + gsBg["color.pri.none"] != Text::nocolor) + { + if (task.getAttribute ("priority") == "") + { + fg = gsFg["color.pri.none"]; + bg = gsBg["color.pri.none"]; + } + } + + // Colorization of the active. + if (gsFg["color.active"] != Text::nocolor || + gsBg["color.active"] != Text::nocolor) + { + if (task.getAttribute ("start") != "") + { + fg = gsFg["color.active"]; + bg = gsBg["color.active"]; + } + } + + // 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"]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/stlmacros.h b/src/stlmacros.h new file mode 100644 index 000000000..71ec54316 --- /dev/null +++ b/src/stlmacros.h @@ -0,0 +1,18 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_STLMACROS +#define INCLUDED_STLMACROS + +#define foreach(i, c) \ +for (typeof (c) *foreach_p = & (c); \ + foreach_p; \ + foreach_p = 0) \ + for (typeof (foreach_p->begin()) i = foreach_p->begin(); \ + i != foreach_p->end(); \ + ++i) + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/task.cpp b/src/task.cpp new file mode 100644 index 000000000..859b27f9d --- /dev/null +++ b/src/task.cpp @@ -0,0 +1,2682 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008 Paul Beckingham. +// All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include + +#include "Config.h" +#include "Date.h" +#include "Table.h" +#include "stlmacros.h" +#include "TDB.h" +#include "T.h" +#include "task.h" + +#ifdef HAVE_LIBNCURSES +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// +void usage (Config& conf) +{ + Table table; + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + table.addColumn (""); + table.addColumn (""); + table.addColumn (""); + + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + table.setColumnJustification (2, Table::left); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::minimum); + table.setColumnWidth (2, Table::flexible); + table.setTableWidth (width); + + int row = table.addRow (); + table.addCell (row, 0, "Usage:"); + table.addCell (row, 1, "task"); + + row = table.addRow (); + table.addCell (row, 1, "task add [tags] [attributes] description..."); + table.addCell (row, 2, "Adds a new task"); + + row = table.addRow (); + table.addCell (row, 1, "task list [tags] [attributes] description..."); + table.addCell (row, 2, "Lists all tasks matching the specified criteria"); + + row = table.addRow (); + table.addCell (row, 1, "task long [tags] [attributes] description..."); + table.addCell (row, 2, "Lists all task, all data, matching the specified criteria"); + + row = table.addRow (); + table.addCell (row, 1, "task ls [tags] [attributes] description..."); + table.addCell (row, 2, "Minimal listing of all tasks matching the specified criteria"); + + row = table.addRow (); + table.addCell (row, 1, "task completed [tags] [attributes] description..."); + table.addCell (row, 2, "Chronological listing of all completed tasks matching the specified criteria"); + + row = table.addRow (); + table.addCell (row, 1, "task ID [tags] [attributes] [description...]"); + table.addCell (row, 2, "Modifies the existing task with provided arguments"); + + row = table.addRow (); + table.addCell (row, 1, "task ID /from/to/"); + table.addCell (row, 2, "Perform the substitution on the description, for fixing mistakes"); + + row = table.addRow (); + table.addCell (row, 1, "task delete ID"); + table.addCell (row, 2, "Deletes the specified task"); + + row = table.addRow (); + table.addCell (row, 1, "task info ID"); + table.addCell (row, 2, "Shows all data, metadata for specified task"); + + row = table.addRow (); + table.addCell (row, 1, "task start ID"); + table.addCell (row, 2, "Marks specified task as started, starts the clock ticking"); + + row = table.addRow (); + table.addCell (row, 1, "task done ID"); + table.addCell (row, 2, "Marks the specified task as completed"); + + row = table.addRow (); + table.addCell (row, 1, "task projects"); + table.addCell (row, 2, "Shows a list of all project names used, and how many tasks are in each"); + + row = table.addRow (); + table.addCell (row, 1, "task tags"); + table.addCell (row, 2, "Shows a list of all tags used"); + + row = table.addRow (); + table.addCell (row, 1, "task summary"); + table.addCell (row, 2, "Shows a report of task status by project"); + + row = table.addRow (); + table.addCell (row, 1, "task history"); + table.addCell (row, 2, "Shows a report of task history, by month"); + + row = table.addRow (); + table.addCell (row, 1, "task next"); + table.addCell (row, 2, "Shows the most important tasks for each project"); + + row = table.addRow (); + table.addCell (row, 1, "task calendar"); + table.addCell (row, 2, "Shows a monthly calendar, with due tasks marked"); + + row = table.addRow (); + table.addCell (row, 1, "task active"); + table.addCell (row, 2, "Shows all task that are started, but not completed"); + + row = table.addRow (); + 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 stats"); + table.addCell (row, 2, "Shows task database statistics"); + + row = table.addRow (); + table.addCell (row, 1, "task usage"); + table.addCell (row, 2, "Shows task command usage frequency"); + + row = table.addRow (); + table.addCell (row, 1, "task export"); + table.addCell (row, 2, "Exports all tasks as a CSV file"); + + row = table.addRow (); + table.addCell (row, 1, "task version"); + table.addCell (row, 2, "Shows the task version number"); + + std::cout << table.render () + << 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" + << " fg: Foreground color" << "\n" + << " bg: Background color" << "\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; +} + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char** argv) +{ +// std::set_terminate (__gnu_cxx::__verbose_terminate_handler); + + try + { + // Load the config file from the home directory. + Config conf; + struct passwd* pw = getpwuid (getuid ()); + if (!pw) + throw std::string ("Could not read home directory from passwd file."); + + std::string home = pw->pw_dir; + home += "/.taskrc"; + conf.load (home); + + TDB tdb; + tdb.dataDirectory (conf.get ("data.location")); + + // Log commands, if desired. + if (conf.get ("command.logging") == "on") + tdb.logCommand (argc, argv); + + // Parse the command line. + std::vector args; + for (int i = 1; i < argc; ++i) + args.push_back (argv[i]); + + std::string command; + T task; + parse (args, command, task); + + if (command == "add") handleAdd (tdb, task, conf); + else if (command == "projects") handleProjects (tdb, task, conf); + else if (command == "tags") handleTags (tdb, task, conf); + else if (command == "list") handleList (tdb, task, conf); + else if (command == "info") handleInfo (tdb, task, conf); + else if (command == "long") handleLongList (tdb, task, conf); + else if (command == "ls") handleSmallList (tdb, task, conf); + else if (command == "completed") handleCompleted (tdb, task, conf); + else if (command == "delete") handleDelete (tdb, task, conf); + else if (command == "start") handleStart (tdb, task, conf); + else if (command == "done") handleDone (tdb, task, conf); + else if (command == "export") handleExport (tdb, task, conf); + else if (command == "version") handleVersion ( conf); + else if (command == "summary") handleReportSummary (tdb, task, conf); + else if (command == "next") handleReportNext (tdb, task, conf); + else if (command == "history") handleReportHistory (tdb, task, conf); + else if (command == "calendar") handleReportCalendar (tdb, task, conf); + else if (command == "active") handleReportActive (tdb, task, conf); + else if (command == "overdue") handleReportOverdue (tdb, task, conf); + else if (command == "stats") handleReportStats (tdb, task, conf); + else if (command == "usage") handleReportUsage (tdb, task, conf); + else if (command == "" && task.getId ()) handleModify (tdb, task, conf); + else usage (conf); + } + + catch (std::string& error) + { + std::cout << error << std::endl; + return -1; + } + + catch (...) + { + std::cout << "Unknown error." << std::endl; + return -2; + } + +// return 0; + exit (0); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string epochToString (const std::string& epoch) +{ + char formatted[12] = {0}; + + if (epoch.length () && epoch.find ("/") == std::string::npos) + { + Date dt (::atoi (epoch.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + sprintf (formatted, "%d/%d/%04d", m, d, y); + } + + return formatted; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleAdd (const TDB& tdb, T& task, Config& conf) +{ + char entryTime[16]; + sprintf (entryTime, "%u", (unsigned int) time (NULL)); + task.setAttribute ("entry", entryTime); + + if (task.getDescription () == "") +// std::cout << "Cannot add a blank task" << std::endl; + throw std::string ("Cannot add a blank task."); + + if (!tdb.addT (task)) + throw std::string ("Could not create new task."); +} + +//////////////////////////////////////////////////////////////////////////////// +void handleProjects (const TDB& tdb, T& task, Config& conf) +{ + // Get all the tasks, including deleted ones. + std::vector tasks; + tdb.pendingT (tasks); + + // Scan all the tasks for their project name, building a map using project + // names as keys. + std::map unique; + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T task (tasks[i]); + unique[task.getAttribute ("project")] += 1; + } + + // Render a list of project names from the map. + Table table; + table.addColumn ("Project"); + table.addColumn ("Tasks"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + + table.setColumnJustification (1, Table::right); + + foreach (i, unique) + { + int row = table.addRow (); + table.addCell (row, 0, i->first); + table.addCell (row, 1, i->second); + } + + std::cout << std::endl + << table.render () + << std::endl + << unique.size () + << (unique.size () == 1 ? " project" : " projects") + << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleTags (const TDB& tdb, T& task, Config& conf) +{ + // Get all the tasks. + std::vector tasks; + tdb.pendingT (tasks); + + // Scan all the tasks for their project name, building a map using project + // names as keys. + std::map unique; + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T task (tasks[i]); + + std::vector tags; + task.getTags (tags); + + for (unsigned int t = 0; t < tags.size (); ++t) + unique[tags[t]] = ""; + } + + // Render a list of tag names from the map. + foreach (i, unique) + std::cout << i->first << std::endl; + + if (unique.size ()) + std::cout << std::endl + << unique.size () + << (unique.size () == 1 ? " tag" : " tags") + << std::endl; + else + std::cout << "No tags." + << std::endl; + +} + +//////////////////////////////////////////////////////////////////////////////// +// Successively apply filters based on the task object built from the command +// line. Tasks that match all the specified criteria are listed. +void handleList (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + /* int count = */ tdb.gc (); + + // Get the pending tasks. + std::vector tasks; + tdb.pendingT (tasks); + + initializeColorRules (conf); + + // 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"); + table.addColumn ("Age"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + table.setColumnUnderline (5); + table.setColumnUnderline (6); + + 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); + table.setColumnWidth (6, Table::flexible); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (3, Table::right); + table.setColumnJustification (5, Table::right); + + table.sortOn (3, Table::ascendingDate); + table.sortOn (2, Table::descendingPriority); + table.sortOn (1, Table::ascendingCharacter); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + // Iterate over each task, and apply selection criteria. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + + // Apply description filter. + unsigned int matches = 0; + for (unsigned int w = 0; w < descWords.size (); ++w) + if (refTask.getDescription ().find (descWords[w]) != std::string::npos) + ++matches; + + if (matches == descWords.size ()) + { + // Apply attribute filter. + matches = 0; + foreach (a, attrList) + if (a->second == refTask.getAttribute (a->first)) + ++matches; + + if (matches == attrList.size ()) + { + // Apply tag filter. + matches = 0; + for (unsigned int t = 0; t < tagList.size (); ++t) + if (refTask.hasTag (tagList[t])) + ++matches; + + if (matches == tagList.size ()) + { + // Now format the matching task. + bool imminent = false; + bool overdue = false; + Date now; + std::string due = refTask.getAttribute ("due"); + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + due = formatted; + + overdue = (dt < now) ? true : false; + now += 7 * 86400; + imminent = dt < now ? true : false; + } + + std::string active; + if (refTask.getAttribute ("start") != "") + active = "*"; + + std::string age; + std::string created = refTask.getAttribute ("entry"); + if (created.length () && created.find ("/") == std::string::npos) + { + 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, (int) i + 1); + table.addCell (row, 1, refTask.getAttribute ("project")); + table.addCell (row, 2, refTask.getAttribute ("priority")); + table.addCell (row, 3, due); + table.addCell (row, 4, active); + table.addCell (row, 5, age); + table.addCell (row, 6, 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 ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No matches." + << std::endl; + +/* + if (count) + std::cout << std::endl + << "[gc: " + << count + << " transferred]" + << std::endl; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// 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. +void handleSmallList (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + /* int count = */ tdb.gc (); + + // Get the pending tasks. + std::vector tasks; + tdb.pendingT (tasks); + + initializeColorRules (conf); + + // Create a table for output. + Table table; + table.setTableWidth (width); + table.addColumn ("ID"); + table.addColumn ("Project"); + table.addColumn ("Pri"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + + 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); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + // Iterate over each task, and apply selection criteria. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + + // Apply description filter. + unsigned int matches = 0; + for (unsigned int w = 0; w < descWords.size (); ++w) + if (refTask.getDescription ().find (descWords[w]) != std::string::npos) + ++matches; + + if (matches == descWords.size ()) + { + // Apply attribute filter. + matches = 0; + foreach (a, attrList) + if (a->second == refTask.getAttribute (a->first)) + ++matches; + + if (matches == attrList.size ()) + { + // Apply tag filter. + matches = 0; + for (unsigned int t = 0; t < tagList.size (); ++t) + if (refTask.hasTag (tagList[t])) + ++matches; + + if (matches == tagList.size ()) + { + // Now format the matching task. + bool imminent = false; + bool overdue = false; + Date now; + std::string due = refTask.getAttribute ("due"); + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + due = formatted; + + overdue = (dt < now) ? true : false; + now += 7 * 86400; + imminent = dt < now ? true : false; + } + + std::string active; + if (refTask.getAttribute ("start") != "") + active = "*"; + + std::string age; + std::string created = refTask.getAttribute ("entry"); + if (created.length () && created.find ("/") == std::string::npos) + { + 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, (int) i + 1); + 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 ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No matches." + << std::endl; + +/* + if (count) + std::cout << std::endl + << "[gc: " + << count + << " transferred]" + << std::endl; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// Successively apply filters based on the task object built from the command +// line. Tasks that match all the specified criteria are listed. +void handleCompleted (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + /* int count = */ tdb.gc (); + + // Get the pending tasks. + std::vector tasks; + tdb.completedT (tasks); + + initializeColorRules (conf); + + // Create a table for output. + Table table; + table.setTableWidth (width); + table.addColumn ("Done"); + table.addColumn ("Project"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::minimum); + table.setColumnWidth (2, Table::flexible); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (1, Table::left); + table.setColumnJustification (2, Table::left); + + table.sortOn (0, Table::ascendingDate); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + // Iterate over each task, and apply selection criteria. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + + // Apply description filter. + unsigned int matches = 0; + for (unsigned int w = 0; w < descWords.size (); ++w) + if (refTask.getDescription ().find (descWords[w]) != std::string::npos) + ++matches; + + if (matches == descWords.size ()) + { + // Apply attribute filter. + matches = 0; + foreach (a, attrList) + if (a->second == refTask.getAttribute (a->first)) + ++matches; + + if (matches == attrList.size ()) + { + // Apply tag filter. + matches = 0; + for (unsigned int t = 0; t < tagList.size (); ++t) + if (refTask.hasTag (tagList[t])) + ++matches; + + if (matches == tagList.size ()) + { + // Now format the matching task. + Date end (::atoi (refTask.getAttribute ("end").c_str ())); + + // All criteria match, so add refTask to the output table. + int row = table.addRow (); + + table.addCell (row, 0, end.toString ()); + table.addCell (row, 1, refTask.getAttribute ("project")); + table.addCell (row, 2, 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 (table.rowCount ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No matches." + << std::endl; + +/* + if (count) + std::cout << std::endl + << "[gc: " + << count + << " transferred]" + << std::endl; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// Display all information for the given task. +void handleInfo (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 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); + + Table table; + table.setTableWidth (width); + + table.addColumn ("Name"); + table.addColumn ("Value"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::minimum); + + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + + // Find the task. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + + if (refTask.getId () == task.getId ()) + { + Date now; + + int row = table.addRow (); + table.addCell (row, 0, "ID"); + table.addCell (row, 1, refTask.getId ()); + + row = table.addRow (); + table.addCell (row, 0, "Status"); + table.addCell (row, 1, ( refTask.getStatus () == T::pending ? "Pending" + : refTask.getStatus () == T::completed ? "Completed" + : refTask.getStatus () == T::deleted ? "Deleted" + : "")); + + row = table.addRow (); + table.addCell (row, 0, "Description"); + table.addCell (row, 1, refTask.getDescription ()); + + if (refTask.getAttribute ("project") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Project"); + table.addCell (row, 1, refTask.getAttribute ("project")); + } + + if (refTask.getAttribute ("priority") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Priority"); + table.addCell (row, 1, refTask.getAttribute ("priority")); + } + + // due (colored) + bool imminent = false; + bool overdue = false; + std::string due = refTask.getAttribute ("due"); + if (due != "") + { + row = table.addRow (); + table.addCell (row, 0, "Due"); + table.addCell (row, 1, epochToString (due)); + + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + + overdue = (dt < now) ? true : false; + now += 7 * 86400; + imminent = dt < now ? true : false; + + if (conf.get ("color", true)) + { + if (overdue) + table.setCellFg (row, 1, Text::red); + else if (imminent) + table.setCellFg (row, 1, Text::yellow); + } + } + } + + // start + if (refTask.getAttribute ("start") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Start"); + table.addCell (row, 1, epochToString (refTask.getAttribute ("start"))); + } + + // end + if (refTask.getAttribute ("end") != "") + { + row = table.addRow (); + table.addCell (row, 0, "End"); + table.addCell (row, 1, epochToString (refTask.getAttribute ("end"))); + } + + // tags ... + std::vector tags; + refTask.getTags (tags); + if (tags.size ()) + { + std::string allTags; + join (allTags, " ", tags); + + row = table.addRow (); + table.addCell (row, 0, "Tags"); + table.addCell (row, 1, allTags); + } + + row = table.addRow (); + table.addCell (row, 0, "UUID"); + table.addCell (row, 1, refTask.getUUID ()); + + row = table.addRow (); + table.addCell (row, 0, "Entered"); + std::string entry = epochToString (refTask.getAttribute ("entry")); + + std::string age; + std::string created = refTask.getAttribute ("entry"); + if (created.length () && created.find ("/") == std::string::npos) + { + Date dt (::atoi (created.c_str ())); + formatTimeDeltaDays (age, (time_t) (now - dt)); + } + + table.addCell (row, 1, entry + " (" + age + ")"); + } + } + + if (table.rowCount ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No matches." << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +// Successively apply filters based on the task object built from the command +// line. Tasks that match all the specified criteria are listed. +void handleLongList (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + /* int count = */ tdb.gc (); + + // Get all the tasks. + std::vector tasks; + tdb.pendingT (tasks); + + initializeColorRules (conf); + + // Create a table for output. + Table table; + table.setTableWidth (width); + table.addColumn ("ID"); + table.addColumn ("Project"); + table.addColumn ("Pri"); + table.addColumn ("Entry"); + table.addColumn ("Start"); + table.addColumn ("Due"); + table.addColumn ("Age"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + table.setColumnUnderline (5); + table.setColumnUnderline (6); + table.setColumnUnderline (7); + + 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); + table.setColumnWidth (6, Table::minimum); + table.setColumnWidth (7, Table::flexible); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (3, Table::right); + table.setColumnJustification (4, Table::right); + table.setColumnJustification (5, Table::right); + table.setColumnJustification (6, Table::right); + + table.sortOn (5, Table::ascendingDate); + table.sortOn (2, Table::descendingPriority); + table.sortOn (1, Table::ascendingCharacter); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + // Iterate over each task, and apply selection criteria. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + + // Apply description filter. + unsigned int matches = 0; + for (unsigned int w = 0; w < descWords.size (); ++w) + if (refTask.getDescription ().find (descWords[w]) != std::string::npos) + ++matches; + + if (matches == descWords.size ()) + { + // Apply attribute filter. + matches = 0; + foreach (a, attrList) + if (a->second == refTask.getAttribute (a->first)) + ++matches; + + if (matches == attrList.size ()) + { + // Apply tag filter. + matches = 0; + for (unsigned int t = 0; t < tagList.size (); ++t) + if (refTask.hasTag (tagList[t])) + ++matches; + + if (matches == tagList.size ()) + { + Date now; + + std::string started = refTask.getAttribute ("start"); + if (started.length () && started.find ("/") == std::string::npos) + { + Date dt (::atoi (started.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + started = formatted; + } + + std::string entered = refTask.getAttribute ("entry"); + if (entered.length () && entered.find ("/") == std::string::npos) + { + Date dt (::atoi (entered.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + entered = formatted; + } + + // Now format the matching task. + bool imminent = false; + bool overdue = false; + std::string due = refTask.getAttribute ("due"); + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + due = formatted; + + overdue = (dt < now) ? true : false; + now += 7 * 86400; + imminent = dt < now ? true : false; + } + + std::string age; + std::string created = refTask.getAttribute ("entry"); + if (created.length () && created.find ("/") == std::string::npos) + { + 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, (int) i + 1); + 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); + table.addCell (row, 6, age); + table.addCell (row, 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 ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No matches." << std::endl; + +/* + if (count) + std::cout << std::endl + << "[gc: " + << count + << " transferred]" + << std::endl; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// Project Tasks Avg Age Status +// A 12 13d XXXXXXXX------ +// B 109 3d 12h XX------------ +void handleReportSummary (const TDB& tdb, T& task, Config& conf) +{ + // Generate unique list of project names. + std::map allProjects; + std::vector pending; + tdb.pendingT (pending); + for (unsigned int i = 0; i < pending.size (); ++i) + { + T task (pending[i]); + allProjects[task.getAttribute ("project")] = false; + } + + std::vector completed; + tdb.completedT (completed); + for (unsigned int i = 0; i < completed.size (); ++i) + { + T task (completed[i]); + allProjects[task.getAttribute ("project")] = false; + } + + // Initialize counts, sum. + std::map countPending; + std::map countCompleted; + std::map sumEntry; + std::map counter; + time_t now = time (NULL); + + foreach (i, allProjects) + { + countPending [i->first] = 0; + countCompleted [i->first] = 0; + sumEntry [i->first] = 0.0; + counter [i->first] = 0; + } + + // Count the pending tasks. + for (unsigned int i = 0; i < pending.size (); ++i) + { + T task (pending[i]); + std::string project = task.getAttribute ("project"); + ++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; + ++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); + } + + // Create a table for output. + Table table; + table.addColumn ("Project"); + table.addColumn ("Remaining"); + table.addColumn ("Avg age"); + table.addColumn ("Complete"); + table.addColumn ("0% 100%"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + + table.setColumnJustification (1, Table::right); + table.setColumnJustification (2, Table::right); + table.setColumnJustification (3, Table::right); + + table.sortOn (0, Table::ascendingCharacter); + + int barWidth = 30; + foreach (i, allProjects) + { + if (countPending[i->first] > 0) + { + int row = table.addRow (); + table.addCell (row, 0, (i->first == "" ? "(none)" : i->first)); + table.addCell (row, 1, countPending[i->first]); + if (counter[i->first]) + { + std::string age; + formatTimeDeltaDays (age, (time_t) (sumEntry[i->first] / counter[i->first])); + table.addCell (row, 2, age); + } + + int c = countCompleted[i->first]; + int p = countPending[i->first]; + int completedBar = (c * barWidth) / (c + p); + + std::string bar; + if (conf.get ("color", true)) + { + bar = "\033[42m"; + for (int b = 0; b < completedBar; ++b) + bar += " "; + + bar += "\033[40m"; + for (int b = 0; b < barWidth - completedBar; ++b) + bar += " "; + + bar += "\033[0m"; + } + else + { + for (int b = 0; b < completedBar; ++b) + bar += "="; + + for (int b = 0; b < barWidth - completedBar; ++b) + bar += " "; + } + table.addCell (row, 4, bar); + + char percent[12]; + sprintf (percent, "%d%%", 100 * c / (c + p)); + table.addCell (row, 3, percent); + } + } + + if (table.rowCount ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " project" : " projects") + << std::endl; + else + std::cout << "No projects." << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +// A summary of the most important pending tasks. +// +// For every project, pull important tasks to present as an 'immediate' task +// list. This hides the overwhelming quantity of other tasks. +// +// Present at most three tasks for every project. Select the tasks using +// these criteria: +// - due:< 1wk, pri:* +// - due:*, pri:H +// - pri:H +// - due:*, pri:M +// - pri:M +// - due:*, pri:L +// - pri:L +// - due:*, pri:* <-- everything else +// +// Make the "three" tasks a configurable number +// +void handleReportNext (const TDB& tdb, T& task, Config& conf) +{ + // Load all pending. + std::vector pending; + tdb.allPendingT (pending); + + // Restrict to matching subset. + std::vector matching; + gatherNextTasks (tdb, task, conf, pending, matching); + + // Determine window size, and set table accordingly. + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + /* int gcCount = */ tdb.gc (); + + // Get the pending tasks. + std::vector tasks; + tdb.pendingT (tasks); + + initializeColorRules (conf); + + // 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"); + table.addColumn ("Age"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + table.setColumnUnderline (5); + table.setColumnUnderline (6); + + 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); + table.setColumnWidth (6, Table::flexible); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (3, Table::right); + table.setColumnJustification (5, Table::right); + + table.sortOn (3, Table::ascendingDate); + table.sortOn (2, Table::descendingPriority); + table.sortOn (1, Table::ascendingCharacter); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + // Iterate over each task, and apply selection criteria. + foreach (i, matching) + { + T refTask (pending[*i]); + + // Apply description filter. + unsigned int matches = 0; + for (unsigned int w = 0; w < descWords.size (); ++w) + if (refTask.getDescription ().find (descWords[w]) != std::string::npos) + ++matches; + + if (matches == descWords.size ()) + { + // Apply attribute filter. + matches = 0; + foreach (a, attrList) + if (a->second == refTask.getAttribute (a->first)) + ++matches; + + if (matches == attrList.size ()) + { + // Apply tag filter. + matches = 0; + for (unsigned int t = 0; t < tagList.size (); ++t) + if (refTask.hasTag (tagList[t])) + ++matches; + + if (matches == tagList.size ()) + { + // Now format the matching task. + bool imminent = false; + bool overdue = false; + Date now; + std::string due = refTask.getAttribute ("due"); + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + due = formatted; + + overdue = (dt < now) ? true : false; + now += 7 * 86400; + imminent = dt < now ? true : false; + } + + std::string active; + if (refTask.getAttribute ("start") != "") + active = "*"; + + std::string age; + std::string created = refTask.getAttribute ("entry"); + if (created.length () && created.find ("/") == std::string::npos) + { + 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); + table.addCell (row, 5, age); + table.addCell (row, 6, 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 ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No matches." + << std::endl; + +/* + if (gcCount) + std::cout << std::endl + << "[gc: " + << gcCount + << " transferred]" + << std::endl; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// Year Month Added Completed Deleted +// 2006 November 87 63 14 +// 2006 December 21 6 1 +time_t monthlyEpoch (const std::string& date) +{ + // Convert any date in epoch form to m/d/y, then convert back + // to epoch form for the date m/1/y. + if (date.length ()) + { + Date d1 (::atoi (date.c_str ())); + int m, d, y; + d1.toMDY (m, d, y); + Date d2 (m, 1, y); + time_t epoch; + d2.toEpoch (epoch); + return epoch; + } + + return 0; +} + +void handleReportHistory (const TDB& tdb, T& task, Config& conf) +{ + std::map groups; + std::map addedGroup; + std::map completedGroup; + std::map deletedGroup; + + // Scan the pending tasks. + std::vector pending; + tdb.allPendingT (pending); + for (unsigned int i = 0; i < pending.size (); ++i) + { + T task (pending[i]); + time_t epoch = monthlyEpoch (task.getAttribute ("entry")); + if (epoch) + { + groups[epoch] = 0; + + if (addedGroup.find (epoch) != addedGroup.end ()) + addedGroup[epoch] = addedGroup[epoch] + 1; + else + addedGroup[epoch] = 1; + + if (task.getStatus () == T::deleted) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + + if (deletedGroup.find (epoch) != deletedGroup.end ()) + deletedGroup[epoch] = deletedGroup[epoch] + 1; + else + deletedGroup[epoch] = 1; + } + else if (task.getStatus () == T::completed) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + + if (completedGroup.find (epoch) != completedGroup.end ()) + completedGroup[epoch] = completedGroup[epoch] + 1; + else + completedGroup[epoch] = 1; + } + } + } + + // Scan the completed tasks. + std::vector completed; + tdb.allCompletedT (completed); + for (unsigned int i = 0; i < completed.size (); ++i) + { + T task (completed[i]); + time_t epoch = monthlyEpoch (task.getAttribute ("entry")); + if (epoch) + { + groups[epoch] = 0; + + if (addedGroup.find (epoch) != addedGroup.end ()) + addedGroup[epoch] = addedGroup[epoch] + 1; + else + addedGroup[epoch] = 1; + + epoch = monthlyEpoch (task.getAttribute ("end")); + if (task.getStatus () == T::deleted) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + + if (deletedGroup.find (epoch) != deletedGroup.end ()) + deletedGroup[epoch] = deletedGroup[epoch] + 1; + else + deletedGroup[epoch] = 1; + } + else if (task.getStatus () == T::completed) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + if (completedGroup.find (epoch) != completedGroup.end ()) + completedGroup[epoch] = completedGroup[epoch] + 1; + else + completedGroup[epoch] = 1; + } + } + } + + // Now build the table. + Table table; + table.addColumn ("Year"); + table.addColumn ("Month"); + table.addColumn ("Added"); + table.addColumn ("Completed"); + table.addColumn ("Deleted"); + table.addColumn ("Net"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + table.setColumnUnderline (5); + + table.setColumnJustification (2, Table::right); + table.setColumnJustification (3, Table::right); + table.setColumnJustification (4, Table::right); + table.setColumnJustification (5, Table::right); + + char *months[] = + { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", + }; + + int priorYear = 0; + foreach (i, groups) + { + int row = table.addRow (); + + Date dt (i->first); + int m, d, y; + dt.toMDY (m, d, y); + + if (y != priorYear) + { + table.addCell (row, 0, y); + priorYear = y; + } + table.addCell (row, 1, months[m - 1]); + + int net = 0; + + if (addedGroup.find (i->first) != addedGroup.end ()) + { + table.addCell (row, 2, addedGroup[i->first]); + net +=addedGroup[i->first]; + } + + if (completedGroup.find (i->first) != completedGroup.end ()) + { + table.addCell (row, 3, completedGroup[i->first]); + net -= completedGroup[i->first]; + } + + if (deletedGroup.find (i->first) != deletedGroup.end ()) + { + table.addCell (row, 4, deletedGroup[i->first]); + net -= deletedGroup[i->first]; + } + + table.addCell (row, 5, net); + if (conf.get ("color", true) && net) + table.setCellFg (row, 5, net > 0 ? Text::red: Text::green); + } + + if (table.rowCount ()) + std::cout << std::endl + << table.render () + << std::endl; + else + std::cout << "No tasks." << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +// A summary of the command usage. Not useful to users, but used to display +// usage statistics for feedback. +// +// 2006-12-04 19:59:43 "task list" +// +void handleReportUsage (const TDB& tdb, T& task, Config& conf) +{ + if (conf.get ("command.logging") == "on") + { + std::map usage; + std::vector all; + tdb.logRead (all); + for (unsigned int i = 0; i < all.size (); ++i) + { + // 0123456789012345678901 + // v 21 + // 2006-12-04 19:59:43 "task list" + std::string command = all[i].substr (21, all[i].length () - 22); + + // Parse as a command line. + std::vector args; + split (args, command, " "); + + try + { + T task; + std::string commandName; + parse (args, commandName, task); + + usage[commandName]++; + } + + // Deliberately ignore errors from parsing the command log, as there may + // be commands from a prior version of task in there, which were + // abbreviated, and are now ambiguous. + catch (...) {} + } + + // Now render the table. + Table table; + table.addColumn ("Command"); + table.addColumn ("Frequency"); + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnJustification (1, Table::right); + table.sortOn (1, Table::descendingNumeric); + + foreach (i, usage) + { + int row = table.addRow (); + table.addCell (row, 0, (i->first == "" ? "(modify)" : i->first)); + table.addCell (row, 1, i->second); + } + + if (table.rowCount ()) + std::cout << std::endl + << table.render () + << std::endl; + else + std::cout << "No usage." << std::endl; + } + else + std::cout << "Command logging is not enabled, so no history has been kept." + << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleReportCalendar (const TDB& tdb, T& task, Config& conf) +{ + // Today. + Date date; + int m = date.month (); + int y = date.year (); + int today = date.day (); + + // Read all the tasks, filter by those that have a due date. + std::vector annotations; + std::vector pending; + tdb.pendingT (pending); + for (unsigned int i = 0; i < pending.size (); ++i) + { + T task (pending[i]); + if (task.getAttribute ("due") != "") + { + Date d (::atoi (task.getAttribute ("due").c_str ())); + if (d.year () == y && d.month () == m) + annotations.push_back (d.day ()); + } + } + + pending.clear (); + + Table table; + table.addColumn ("Su"); + table.addColumn ("Mo"); + table.addColumn ("Tu"); + table.addColumn ("We"); + table.addColumn ("Th"); + table.addColumn ("Fr"); + table.addColumn ("Sa"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + table.setColumnUnderline (5); + table.setColumnUnderline (6); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (1, Table::right); + table.setColumnJustification (2, Table::right); + table.setColumnJustification (3, Table::right); + table.setColumnJustification (4, Table::right); + table.setColumnJustification (5, Table::right); + table.setColumnJustification (6, Table::right); + + int days = Date::daysInMonth (m, y); + int row = table.addRow (); + for (int d = 1; d <= days; ++d) + { + Date temp (m, d, y); + int dow = temp.dayOfWeek (); + + table.addCell (row, dow, d); + + if (conf.get ("color", true) && d == today) + table.setCellFg (row, dow, Text::cyan); + + for (unsigned int a = 0; a < annotations.size (); ++a) + { + if (conf.get ("color", true) && annotations[a] == d) + { + table.setCellFg (row, dow, Text::black); + table.setCellBg (row, dow, d < today ? Text::red : Text::yellow); + } + } + + if (dow == 6 && d < days) + row = table.addRow (); + } + + std::cout << std::endl + << Date::monthName (m) + << " " + << y + << std::endl + << std::endl + << table.render () + << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleReportActive (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 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.pendingT (tasks); + + initializeColorRules (conf); + + // Create a table for output. + Table table; + table.setTableWidth (width); + table.addColumn ("ID"); + table.addColumn ("Project"); + table.addColumn ("Pri"); + table.addColumn ("Due"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::minimum); + table.setColumnWidth (2, Table::minimum); + table.setColumnWidth (3, Table::minimum); + table.setColumnWidth (4, Table::flexible); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (3, Table::right); + + table.sortOn (3, Table::ascendingDate); + table.sortOn (2, Table::descendingPriority); + table.sortOn (1, Table::ascendingCharacter); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + // Iterate over each task, and apply selection criteria. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + if (refTask.getAttribute ("start") != "") + { + bool imminent = false; + bool overdue = false; + std::string due = refTask.getAttribute ("due"); + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + due = formatted; + + Date now; + overdue = dt < now ? true : false; + now += 7 * 86400; + imminent = dt < now ? true : false; + } + + // All criteria match, so add refTask to the output table. + int row = table.addRow (); + table.addCell (row, 0, (int) i + 1); + table.addCell (row, 1, refTask.getAttribute ("project")); + table.addCell (row, 2, refTask.getAttribute ("priority")); + table.addCell (row, 3, due); + table.addCell (row, 4, 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 ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No active tasks." << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleReportOverdue (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 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.pendingT (tasks); + + initializeColorRules (conf); + + // Create a table for output. + Table table; + table.setTableWidth (width); + table.addColumn ("ID"); + table.addColumn ("Project"); + table.addColumn ("Pri"); + table.addColumn ("Due"); + table.addColumn ("Description"); + + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnUnderline (2); + table.setColumnUnderline (3); + table.setColumnUnderline (4); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::minimum); + table.setColumnWidth (2, Table::minimum); + table.setColumnWidth (3, Table::minimum); + table.setColumnWidth (4, Table::flexible); + + table.setColumnJustification (0, Table::right); + table.setColumnJustification (3, Table::right); + + table.sortOn (3, Table::ascendingDate); + table.sortOn (2, Table::descendingPriority); + table.sortOn (1, Table::ascendingCharacter); + + // Split any description specified into words. + std::vector descWords; + split (descWords, task.getDescription (), ' '); + + // Get all the tags to match against. + std::vector tagList; + task.getTags (tagList); + + // Get all the attributes to match against. + std::map attrList; + task.getAttributes (attrList); + + Date now; + + // Iterate over each task, and apply selection criteria. + for (unsigned int i = 0; i < tasks.size (); ++i) + { + T refTask (tasks[i]); + std::string due; + if ((due = refTask.getAttribute ("due")) != "") + { + if (due.length () && due.find ("/") == std::string::npos) + { + Date dt (::atoi (due.c_str ())); + int m, d, y; + dt.toMDY (m, d, y); + char formatted[12]; + sprintf (formatted, "%d/%d/%04d", m, d, y); + due = formatted; + + // If overdue. + if (dt < now) + { + // All criteria match, so add refTask to the output table. + int row = table.addRow (); + table.addCell (row, 0, (int) i + 1); + table.addCell (row, 1, refTask.getAttribute ("project")); + table.addCell (row, 2, refTask.getAttribute ("priority")); + table.addCell (row, 3, due); + table.addCell (row, 4, 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) + table.setCellFg (row, 3, Text::red); + } + } + } + } + } + + if (table.rowCount ()) + std::cout << std::endl + << table.render () + << std::endl + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks") + << std::endl; + else + std::cout << "No overdue tasks." << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleReportStats (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 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.allT (tasks); + + Date now; + time_t earliest = time (NULL); + time_t latest = 1; + int totalT = 0; + int deletedT = 0; + int pendingT = 0; + int completedT = 0; + int taggedT = 0; + float daysPending = 0.0; + int descLength = 0; + + std::vector ::iterator it; + for (it = tasks.begin (); it != tasks.end (); ++it) + { + ++totalT; + if (it->getStatus () == T::deleted) ++deletedT; + if (it->getStatus () == T::pending) ++pendingT; + if (it->getStatus () == T::completed) ++completedT; + + time_t entry = ::atoi (it->getAttribute ("entry").c_str ()); + if (entry < earliest) earliest = entry; + if (entry > latest) latest = entry; + + if (it->getStatus () == T::completed) + { + time_t end = ::atoi (it->getAttribute ("end").c_str ()); + daysPending += (end - entry) / 86400.0; + } + + if (it->getStatus () == T::pending) + daysPending += (now - entry) / 86400.0; + + descLength += it->getDescription ().length (); + + std::vector tags; + it->getTags (tags); + if (tags.size ()) ++taggedT; + } + + // TODO Unused feature list + + std::cout << "Pending " << pendingT << std::endl + << "Completed " << completedT << std::endl + << "Deleted " << deletedT << std::endl + << "Total " << totalT << std::endl; + + Date e (earliest); + std::cout << "Oldest task " << e.toString () << std::endl; + Date l (latest); + std::cout << "Newest task " << l.toString () << std::endl; + std::cout << "Task used for " << formatSeconds (latest - earliest) << std::endl; + + if (totalT) + std::cout << "Task added every " << formatSeconds ((latest - earliest) / totalT) << std::endl; + + if (completedT) + std::cout << "Task completed every " << formatSeconds ((latest - earliest) / completedT) << std::endl; + + if (deletedT) + std::cout << "Task deleted every " << formatSeconds ((latest - earliest) / deletedT) << std::endl; + + if (pendingT || completedT) + std::cout << "Average time pending " << formatSeconds ((int) ((daysPending / (pendingT + completedT)) * 86400)) << std::endl; + + if (totalT) + { + std::cout << "Average desc length " << (int) (descLength / totalT) << " characters" << std::endl; + std::cout << "Tasks tagged " << std::setprecision (3) << (100.0 * taggedT / totalT) << "%" << std::endl; + } +} + +//////////////////////////////////////////////////////////////////////////////// +void handleVersion (Config& conf) +{ + // Determine window size, and set table accordingly. + int width = 80; +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + + // Handle case for zero width on mobile device. + if (width == 0) + width = 80; + + // Create a table for output. + Table table; + table.setTableWidth (width); + table.addColumn ("Config variable"); + table.addColumn ("Value"); + table.setColumnUnderline (0); + table.setColumnUnderline (1); + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::minimum); + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + table.sortOn (0, Table::ascendingCharacter); + + std::vector all; + conf.all (all); + foreach (i, all) + { + std::string value = conf.get (*i); + if (value != "") + { + int row = table.addRow (); + table.addCell (row, 0, *i); + table.addCell (row, 1, value); + } + } + + std::cout << "Copyright (C) 2006 - 2008 Paul Beckingham." + << std::endl + << PACKAGE + << " " + << VERSION + << std::endl + << std::endl + << table.render () + << std::endl; + + // Verify installation. This is mentioned in the documentation as the way to + // ensure everything is properly installed. + + if (all.size () == 0) + std::cout << "Configuration error: .taskrc contains no entries" + << std::endl; + else + { + if (conf.get ("data.location") == "") + std::cout << "Configuration error: data.location not specified in .taskrc " + "file." + << std::endl; + + if (access (conf.get ("data.location").c_str (), X_OK)) + std::cout << "Configuration error: data.location contains a directory name" + " that doesn't exist, or is unreadable." + << std::endl; + } +} + +//////////////////////////////////////////////////////////////////////////////// +void handleDelete (const TDB& tdb, T& task, Config& conf) +{ + if (conf.get ("confirmation") != "yes" || confirm ("Permanently delete task?")) + tdb.deleteT (task); + else + std::cout << "Task not deleted." << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +void handleStart (const 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") == "") + { + char startTime[16]; + sprintf (startTime, "%u", (unsigned int) time (NULL)); + original.setAttribute ("start", startTime); + + original.setId (task.getId ()); + tdb.modifyT (original); + + nag (tdb, task, conf); + return; + } + else + std::cout << "Task " << task.getId () << " already started." << std::endl; + } + } + + throw std::string ("Task not found."); +} + +//////////////////////////////////////////////////////////////////////////////// +void handleDone (const TDB& tdb, T& task, Config& conf) +{ + if (!tdb.completeT (task)) + throw std::string ("Could not mark task as completed."); + + nag (tdb, task, conf); +} + +//////////////////////////////////////////////////////////////////////////////// +void handleExport (const TDB& tdb, T& task, Config& conf) +{ + std::string file = trim (task.getDescription ()); + + std::ofstream out (file.c_str ()); + if (out.good ()) + { + out << "'id'," + << "'status'," + << "'tags'," + << "'entry'," + << "'start'," + << "'due'," + << "'end'," + << "'project'," + << "'priority'," + << "'fg'," + << "'bg'," + << "'description'" + << "\n"; + + std::vector all; + tdb.allT (all); + foreach (t, all) + { + out << t->composeCSV ().c_str (); + } + out.close (); + } + else + throw std::string ("Could not write to export file."); +} + +//////////////////////////////////////////////////////////////////////////////// +void handleModify (const 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); + + // A non-zero value forces a file write. + int changes = 0; + + // Apply a new description, if any. + if (task.getDescription () != "") + { + original.setDescription (task.getDescription ()); + ++changes; + } + + // Apply or remove tags, if any. + std::vector tags; + task.getTags (tags); + for (unsigned int i = 0; i < tags.size (); ++i) + { + if (tags[i][0] == '+') + original.addTag (tags[i].substr (1, std::string::npos)); + else + original.addTag (tags[i]); + + ++changes; + } + + task.getRemoveTags (tags); + for (unsigned int i = 0; i < tags.size (); ++i) + { + if (tags[i][0] == '-') + original.removeTag (tags[i].substr (1, std::string::npos)); + else + original.removeTag (tags[i]); + + ++changes; + } + + // Apply or remove attributes, if any. + std::map attributes; + task.getAttributes (attributes); + foreach (i, attributes) + { + if (i->second == "") + original.removeAttribute (i->first); + else + original.setAttribute (i->first, i->second); + + ++changes; + } + + std::string from; + std::string to; + task.getSubstitution (from, to); + if (from != "") + { + std::string description = original.getDescription (); + unsigned int pattern = description.find (from); + if (pattern != std::string::npos) + { + description = description.substr (0, pattern) + + to + + description.substr (pattern + from.length (), std::string::npos); + original.setDescription (description); + ++changes; + } + } + + if (changes) + { + original.setId (task.getId ()); + tdb.modifyT (original); + } + + return; + } + } + + throw std::string ("Task not found."); +} + +//////////////////////////////////////////////////////////////////////////////// +void gatherNextTasks ( + const TDB& tdb, + T& task, + Config& conf, + std::vector & pending, + std::vector & all) +{ + // For counting tasks by project. + std::map countByProject; + std::map matching; + + Date now; + + // How many items per project? Default 3. + int limit = conf.get ("next", 3); + + // due:< 1wk, pri:* + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string due = pending[i].getAttribute ("due"); + if (due != "") + { + Date d (::atoi (due.c_str ())); + if (d < now + (7 * 24 * 60 * 60)) // if due:< 1wk + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + } + + // due:*, pri:H + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string due = pending[i].getAttribute ("due"); + if (due != "") + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "H") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + } + + // pri:H + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "H") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + + // due:*, pri:M + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string due = pending[i].getAttribute ("due"); + if (due != "") + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "M") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + } + + // pri:M + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "M") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + + // due:*, pri:L + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string due = pending[i].getAttribute ("due"); + if (due != "") + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "L") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + } + + // pri:L + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "L") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + + // due:, pri: + for (unsigned int i = 0; i < pending.size (); ++i) + { + if (pending[i].getStatus () == T::pending) + { + std::string due = pending[i].getAttribute ("due"); + if (due == "") + { + std::string priority = pending[i].getAttribute ("priority"); + if (priority == "") + { + std::string project = pending[i].getAttribute ("project"); + if (countByProject[project] < limit && matching.find (i) == matching.end ()) + { + ++countByProject[project]; + matching[i] = true; + } + } + } + } + } + + // Convert map to vector. + foreach (i, matching) + all.push_back (i->first); +} + +//////////////////////////////////////////////////////////////////////////////// +void nag (const TDB& tdb, T& task, Config& conf) +{ + std::string nagMessage = conf.get ("nag", std::string ("")); + if (nagMessage != "") + { + // Load all pending. + std::vector pending; + tdb.allPendingT (pending); + + // Restrict to matching subset. + std::vector matching; + gatherNextTasks (tdb, task, conf, pending, matching); + + foreach (i, matching) + if (pending[*i].getId () == task.getId ()) + return; + + std::cout << nagMessage << std::endl; + } +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/task.h b/src/task.h new file mode 100644 index 000000000..b756c56da --- /dev/null +++ b/src/task.h @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "Config.h" +#include "Table.h" +#include "color.h" +#include "TDB.h" +#include "T.h" +#include "stlmacros.h" +#include "../auto.h" + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +// parse.cpp +void parse (std::vector &, std::string&, T&); +bool validDate (std::string&); + +// task.cpp +void handleAdd (const TDB&, T&, Config&); +void handleProjects (const TDB&, T&, Config&); +void handleTags (const TDB&, T&, Config&); +void handleList (const TDB&, T&, Config&); +void handleInfo (const TDB&, T&, Config&); +void handleLongList (const TDB&, T&, Config&); +void handleSmallList (const TDB&, T&, Config&); +void handleCompleted (const TDB&, T&, Config&); +void handleReportSummary (const TDB&, T&, Config&); +void handleReportNext (const TDB&, T&, Config&); +void handleReportHistory (const TDB&, T&, Config&); +void handleReportUsage (const TDB&, T&, Config&); +void handleReportCalendar (const TDB&, T&, Config&); +void handleReportActive (const TDB&, T&, Config&); +void handleReportOverdue (const TDB&, T&, Config&); +void handleReportStats (const TDB&, T&, Config&); +void handleVersion (Config&); +void handleExport (const TDB&, T&, Config&); +void handleDelete (const TDB&, T&, Config&); +void handleStart (const TDB&, T&, Config&); +void handleDone (const TDB&, T&, Config&); +void handleModify (const TDB&, T&, Config&); +void gatherNextTasks (const TDB&, T&, Config&, std::vector &, std::vector &); +void nag (const TDB&, T&, Config&); + +// util.cpp +bool confirm (const std::string&); +void wrapText (std::vector &, const std::string&, const int); +std::string trimLeft (const std::string& in, const std::string& t = " "); +std::string trimRight (const std::string& in, const std::string& t = " "); +std::string trim (const std::string& in, const std::string& t = " "); +std::wstring trimLeft (const std::wstring& in, const std::wstring& t = L" "); // UNICODE safe +std::wstring trimRight (const std::wstring& in, const std::wstring& t = L" "); // UNICODE safe +std::wstring trim (const std::wstring& in, const std::wstring& t = L" "); // UNICODE safe +void extractParagraphs (const std::string&, std::vector&); +void extractLine (std::string&, std::string&, int); +void split (std::vector&, const std::string&, const char); +void split (std::vector&, const std::string&, const std::string&); +void join (std::string&, const std::string&, const std::vector&); +std::string commify (const std::string&); +std::string lowerCase (const std::string&); +void delay (float); +int autoComplete (const std::string&, const std::vector&, std::vector&); +void formatTimeDeltaDays (std::string&, time_t); +std::string formatSeconds (time_t); +const std::string uuid (); + +// rules.cpp +void initializeColorRules (Config&); +void autoColorize (T&, Text::color&, Text::color&); + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tests/.gitignore b/src/tests/.gitignore new file mode 100644 index 000000000..e0f390805 --- /dev/null +++ b/src/tests/.gitignore @@ -0,0 +1,5 @@ +t.t +tdb.t +pending.data +completed.data + diff --git a/src/tests/Makefile b/src/tests/Makefile new file mode 100644 index 000000000..b58b7042d --- /dev/null +++ b/src/tests/Makefile @@ -0,0 +1,24 @@ +PROJECT = t.t tdb.t +CFLAGS = -I.. -I../../../library/include -Wall -pedantic -ggdb3 -fno-rtti +LFLAGS = -L/usr/local/lib -L../../../library/lib -lcompany -lpcre -lncurses -lcurl + +all: $(PROJECT) + +install: $(PROJECT) + @echo unimplemented + +test: $(PROJECT) + @echo unimplemented + +clean: + -rm *.o $(PROJECT) + +.cpp.o: + g++ -c $(CFLAGS) $< + +t.t: t.t.o ../T.o ../parse.o ../../../library/lib/libcompany.a + g++ t.t.o ../T.o ../parse.o $(LFLAGS) -o t.t + +tdb.t: tdb.t.o ../TDB.o ../T.o ../parse.o ../../../library/lib/libcompany.a + g++ tdb.t.o ../TDB.o ../T.o ../parse.o $(LFLAGS) -o tdb.t + diff --git a/src/tests/t.t.cpp b/src/tests/t.t.cpp new file mode 100644 index 000000000..fd74e5744 --- /dev/null +++ b/src/tests/t.t.cpp @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char** argv) +{ + plan (4); + + T t; + std::string s = t.compose (); + is ((int)s.length (), 46, "T::T (); T::compose ()"); + diag (s); + + t.setStatus (T::completed); + s = t.compose (); + is (s[37], '+', "T::setStatus (completed)"); + diag (s); + + t.setStatus (T::deleted); + s = t.compose (); + is (s[37], 'X', "T::setStatus (deleted)"); + 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"); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/tests/tdb.t.cpp b/src/tests/tdb.t.cpp new file mode 100644 index 000000000..b27f95746 --- /dev/null +++ b/src/tests/tdb.t.cpp @@ -0,0 +1,127 @@ +//////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char** argv) +{ + plan (43); + + try + { + // Remove any residual test file. + unlink ("./pending.data"); + unlink ("./completed.data"); + + // Try reading an empty database. + 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"); + + // Add a new task. + T t1; + t1.setId (1); + t1.setStatus (T::pending); + t1.setAttribute ("project", "p1"); + t1.setDescription ("task 1"); + diag (t1.compose ()); + 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"); + + // 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"); + + 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"); + + // Add a new task. + T t2; + t2.setId (2); + t2.setAttribute ("project", "p2"); + t2.setDescription ("task 2"); + diag (t2.compose ()); + ok (tdb.addT (t2), "TDB::addT t2"); + + fail ("verify"); + + // Delete task. + ok (tdb.deleteT (t2), "TDB::deleteT t2"); + + fail ("verify"); + + // 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"); + } + + catch (std::string& error) + { + diag (error); + return -1; + } + + catch (...) + { + diag ("Unknown error."); + return -2; + } + +/* + unlink ("./pending.data"); + unlink ("./completed.data"); +*/ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/text.cpp b/src/text.cpp new file mode 100644 index 000000000..882339834 --- /dev/null +++ b/src/text.cpp @@ -0,0 +1,277 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2004 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include "task.h" + +/////////////////////////////////////////////////////////////////////////////// +void wrapText ( + std::vector & lines, + const std::string& text, + const int width) +{ + std::string copy = text; + std::string line; + + while (copy.length ()) + { + extractLine (copy, line, width); + lines.push_back (line); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void split (std::vector& results, const std::string& input, const char delimiter) +{ + std::string temp = input; + std::string::size_type i; + while ((i = temp.find (delimiter)) != std::string::npos) + { + std::string token = temp.substr (0, i); + results.push_back (token); + temp.erase (0, i + 1); + } + + if (temp.length ()) results.push_back (temp); +} + +//////////////////////////////////////////////////////////////////////////////// +void split (std::vector& results, const std::string& input, const std::string& delimiter) +{ + std::string temp = input; + std::string::size_type i; + while ((i = temp.find (delimiter)) != std::string::npos) + { + std::string token = temp.substr (0, i); + results.push_back (token); + temp.erase (0, i + delimiter.length ()); + } + + if (temp.length ()) results.push_back (temp); +} + +//////////////////////////////////////////////////////////////////////////////// +void join ( + std::string& result, + const std::string& separator, + const std::vector& items) +{ + result = ""; + unsigned int size = items.size (); + for (unsigned int i = 0; i < size; ++i) + { + result += items[i]; + if (i < size - 1) + result += separator; + } +} + +//////////////////////////////////////////////////////////////////////////////// +void extractParagraphs (const std::string& input, std::vector& output) +{ + std::string copy = input; + while (1) + { + unsigned int so = copy.find ("

"); + unsigned int eo = copy.find ("

"); + + if (so == std::string::npos && eo == std::string::npos) + break; + + std::string extract = trim (copy.substr (so + 3, eo - so - 3)); + copy = copy.substr (eo + 4, std::string::npos); + output.push_back (extract); + } + + // There were no paragraphs. + if (!output.size ()) + output.push_back (input); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string trimLeft (const std::string& in, const std::string& t /*= " "*/) +{ + std::string out = in; + return out.erase (0, in.find_first_not_of (t)); +} + +// UNICODE safe +std::wstring trimLeft (const std::wstring& in, const std::wstring& t /*= L" "*/) +{ + std::wstring out = in; + return out.erase (0, in.find_first_not_of (t)); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string trimRight (const std::string& in, const std::string& t /*= " "*/) +{ + std::string out = in; + return out.erase (out.find_last_not_of (t) + 1); +} + +// UNICODE safe +std::wstring trimRight (const std::wstring& in, const std::wstring& t /*= L" "*/) +{ + std::wstring out = in; + return out.erase (out.find_last_not_of (t) + 1); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string trim (const std::string& in, const std::string& t /*= " "*/) +{ + std::string out = in; + return trimLeft (trimRight (out, t), t); +} + +// UNICODE safe +std::wstring trim (const std::wstring& in, const std::wstring& t /*= L" "*/) +{ + std::wstring out = in; + return trimLeft (trimRight (out, t), t); +} + +//////////////////////////////////////////////////////////////////////////////// +// Remove enclosing balanced quotes. Assumes trimmed text. +void unquoteText (std::string& text) +{ + char quote = text[0]; + if (quote == '\'' || quote == '"') + if (text[text.length () - 1] == quote) + text = text.substr (1, text.length () - 3); +} + +//////////////////////////////////////////////////////////////////////////////// +void extractLine (std::string& text, std::string& line, int length) +{ + unsigned int eol = text.find ("\n"); + + // Special case: found \n in first length characters. + if (eol != std::string::npos && eol < (unsigned) length) + { + line = text.substr (0, eol); // strip \n + text = text.substr (eol + 1, std::string::npos); + return; + } + + // Special case: no \n, and less than length characters total. + // special case: text.find ("\n") == std::string::npos && text.length () < length + if (eol == std::string::npos && text.length () <= (unsigned) length) + { + line = text; + text = ""; + return; + } + + // Safe to ASSERT text.length () > length + + // Look for the last space prior to length + eol = length; + while (eol && text[eol] != ' ' && text[eol] != '\n') + --eol; + + // If a space was found, break there. + if (eol) + { + line = text.substr (0, eol); + text = text.substr (eol + 1, std::string::npos); + } + + // If no space was found, hyphenate. + else + { + line = text.substr (0, length - 1) + "-"; + text = text.substr (length - 1, std::string::npos); + } +} + +//////////////////////////////////////////////////////////////////////////////// +std::string commify (const std::string& data) +{ + // First scan for decimal point and end of digits. + int decimalPoint = -1; + int end = -1; + + int i; + for (int i = 0; i < (int) data.length (); ++i) + { + if (::isdigit (data[i])) + end = i; + + if (data[i] == '.') + decimalPoint = i; + } + + std::string result; + if (decimalPoint != -1) + { + // In reverse order, transfer all digits up to, and including the decimal + // point. + for (i = (int) data.length () - 1; i >= decimalPoint; --i) + result += data[i]; + + int consecutiveDigits = 0; + for (; i >= 0; --i) + { + if (::isdigit (data[i])) + { + result += data[i]; + + if (++consecutiveDigits == 3 && i && ::isdigit (data[i - 1])) + { + result += ','; + consecutiveDigits = 0; + } + } + else + result += data[i]; + } + } + else + { + // In reverse order, transfer all digits up to, but not including the last + // digit. + for (i = (int) data.length () - 1; i > end; --i) + result += data[i]; + + int consecutiveDigits = 0; + for (; i >= 0; --i) + { + if (::isdigit (data[i])) + { + result += data[i]; + + if (++consecutiveDigits == 3 && i && ::isdigit (data[i - 1])) + { + result += ','; + consecutiveDigits = 0; + } + } + else + result += data[i]; + } + } + + // reverse result into data. + std::string done; + for (int i = (int) result.length () - 1; i >= 0; --i) + done += result[i]; + + return done; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string lowerCase (const std::string& input) +{ + std::string output = input; + for (int i = 0; i < (int) input.length (); ++i) + if (::isupper (input[i])) + output[i] = ::tolower (input[i]); + + return output; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 000000000..ff2a4686d --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,212 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2006 - 2008, Paul Beckingham. All rights reserved. +// +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include "Table.h" +#include "task.h" +#include "../auto.h" + +//////////////////////////////////////////////////////////////////////////////// +bool confirm (const std::string& question) +{ + std::cout << question << " (y/n) "; + std::string answer; + std::cin >> answer; + + answer = trim (answer); + if (answer == "y" || answer == "Y") + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void delay (float f) +{ + struct timeval t; + t.tv_sec = (int) f; + t.tv_usec = int ((f - (int)f) * 1000000); + + select (0, NULL, NULL, NULL, &t); +} + +//////////////////////////////////////////////////////////////////////////////// +// Convert a quantity in seconds to a more readable format. +// Long version: +// 0-59 S seconds +// 60-3599 M minutes, S seconds +// 3600-86399 H hours, M minutes, S seconds +// 86400- D days, H hours, M minutes, S seconds +// Short version: +// 0-59 S seconds +// 60-3599 M minutes, S seconds +// 3600-86399 H hours, M minutes, S seconds +// +void formatTimeDeltaDays (std::string& output, time_t delta) +{ + char formatted[24]; + float days = (float) delta / 86400.0; + + if (days > 365) + sprintf (formatted, "%.1f yrs", (days / 365.2422)); + else if (days > 84) + sprintf (formatted, "%1d mths", (int) (days / 30.6)); + else if (days > 13) + sprintf (formatted, "%d wks", (int) (days / 7.0)); + else if (days > 5.0) + sprintf (formatted, "%d days", (int) days); + else if (days > 1.0) + sprintf (formatted, "%.1f days", days); + else if (days * 24 > 1.0) + sprintf (formatted, "%d hrs", (int) (days * 24.0)); + else if (days * 24 * 60 > 1) + sprintf (formatted, "%d mins", (int) (days * 24 * 60)); + else if (days * 24 * 60 * 60 > 1) + sprintf (formatted, "%d secs", (int) (days * 24 * 60 * 60)); + else + strcpy (formatted, "-"); + + output = formatted; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string formatSeconds (time_t delta) +{ + char formatted[24]; + float days = (float) delta / 86400.0; + + if (days > 365) + sprintf (formatted, "%.1f yrs", (days / 365.2422)); + else if (days > 84) + sprintf (formatted, "%1d mths", (int) (days / 30.6)); + else if (days > 13) + sprintf (formatted, "%d wks", (int) (days / 7.0)); + else if (days > 5.0) + sprintf (formatted, "%d days", (int) days); + else if (days > 1.0) + sprintf (formatted, "%.1f days", days); + else if (days * 24 > 1.0) + sprintf (formatted, "%d hrs", (int) (days * 24.0)); + else if (days * 24 * 60 > 1) + sprintf (formatted, "%d mins", (int) (days * 24 * 60)); + else if (days * 24 * 60 * 60 > 1) + sprintf (formatted, "%d secs", (int) (days * 24 * 60 * 60)); + else + strcpy (formatted, "-"); + + return std::string (formatted); +} + +//////////////////////////////////////////////////////////////////////////////// +int autoComplete ( + const std::string& partial, + const std::vector& list, + std::vector& matches) +{ + matches.erase (matches.begin (), matches.end ()); + + // Handle trivial case. + unsigned int length = partial.length (); + if (length) + { + for (unsigned int i = 0; i < list.size (); ++i) + { + // Special case where there is an exact match. + if (partial == list[i]) + { + matches.erase (matches.begin (), matches.end ()); + matches.push_back (list[i]); + return 1; + } + + // Maintain a list of partial matches. + if (length <= list[i].length () && + ! strncmp (partial.c_str (), list[i].c_str (), length)) + matches.push_back (list[i]); + } + } + + return matches.size (); +} + +//////////////////////////////////////////////////////////////////////////////// +#ifdef HAVE_UUID + +#include + +const std::string uuid () +{ + uuid_t id; + uuid_generate (id); + char buffer[100]; + uuid_unparse_lower (id, buffer); + + return std::string (buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +#else +#warning "Using custom UUID generator" + +#include +static char randomHexDigit () +{ + static char digits[] = "0123456789abcdef"; + return digits[random () % 16]; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string uuid () +{ + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + char id [37]; + id[0] = randomHexDigit (); + id[1] = randomHexDigit (); + id[2] = randomHexDigit (); + id[3] = randomHexDigit (); + id[4] = randomHexDigit (); + id[5] = randomHexDigit (); + id[6] = randomHexDigit (); + id[7] = randomHexDigit (); + id[8] = '-'; + id[9] = randomHexDigit (); + id[10] = randomHexDigit (); + id[11] = randomHexDigit (); + id[12] = randomHexDigit (); + id[13] = '-'; + id[14] = randomHexDigit (); + id[15] = randomHexDigit (); + id[16] = randomHexDigit (); + id[17] = randomHexDigit (); + id[18] = '-'; + id[19] = randomHexDigit (); + id[20] = randomHexDigit (); + id[21] = randomHexDigit (); + id[22] = randomHexDigit (); + id[23] = '-'; + id[24] = randomHexDigit (); + id[25] = randomHexDigit (); + id[26] = randomHexDigit (); + id[27] = randomHexDigit (); + id[28] = randomHexDigit (); + id[29] = randomHexDigit (); + id[30] = randomHexDigit (); + id[31] = randomHexDigit (); + id[32] = randomHexDigit (); + id[33] = randomHexDigit (); + id[34] = randomHexDigit (); + id[35] = randomHexDigit (); + + return id; +} +#endif + +////////////////////////////////////////////////////////////////////////////////