]> Joshua Wise's Git repositories - tdl.git/commitdiff
Initial import of tdl-1.6-pre1
authorJoshua Wise <jwise@andrew.cmu.edu>
Tue, 22 Mar 2011 07:31:22 +0000 (03:31 -0400)
committerJoshua Wise <jwise@andrew.cmu.edu>
Tue, 22 Mar 2011 07:31:22 +0000 (03:31 -0400)
29 files changed:
.arch-inventory [new file with mode: 0644]
COPYING [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
add.c [new file with mode: 0644]
configure [new file with mode: 0755]
dates.c [new file with mode: 0644]
done.c [new file with mode: 0644]
impexp.c [new file with mode: 0644]
inter.c [new file with mode: 0644]
io.c [new file with mode: 0644]
list.c [new file with mode: 0644]
main.c [new file with mode: 0644]
memory.h [new file with mode: 0644]
mkversion [new file with mode: 0755]
move.c [new file with mode: 0644]
narrow.c [new file with mode: 0644]
purge.c [new file with mode: 0644]
remove.c [new file with mode: 0644]
report.c [new file with mode: 0644]
tdl.1 [new file with mode: 0644]
tdl.h [new file with mode: 0644]
tdl.spec [new file with mode: 0644]
tdl.texi [new file with mode: 0644]
util.c [new file with mode: 0644]
version.h [new file with mode: 0644]
version.txt [new file with mode: 0644]

diff --git a/.arch-inventory b/.arch-inventory
new file mode 100644 (file)
index 0000000..7f0136a
--- /dev/null
@@ -0,0 +1,4 @@
+backup ^(Makefile|config\.log|tdl)$
+backup ^tdl\.(aux|cp|dvi|fn|html|info|ky|log|pdf|pg|toc|tp|txt|vr)$
+precious ^\.tdldb$
+
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..916d1f0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..84536ca
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,27 @@
+Although tdl does not use autoconf, it does have a hand-rolled configure script.
+
+To build the program do
+
+    ./configure
+    make
+    make install
+
+You can see what options ./configure can take with
+
+    ./configure --help
+
+For example, you might want to use --prefix to install the software in a
+non-standard location.
+
+To get started,
+
+    tdl create
+    tdl add "Some task I have to do"
+    tdl list
+
+It's suggested that you try
+
+    man tdl
+
+as well.
+
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..6385dde
--- /dev/null
@@ -0,0 +1,120 @@
+#  $Header: /cvs/src/tdl/Makefile.in,v 1.7.2.2 2004/01/07 00:09:05 richard Exp $
+#  
+#  tdl - A console program for managing to-do lists
+#  Copyright (C) 2001-2004  Richard P. Curnow
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+#######################################################################
+# Note, the @xxx@ macros are filled in by the configure script.  You
+# should not need to edit this file directly.
+#######################################################################
+# Select C compiler and compile options
+CC=@cc@
+CFLAGS=@cflags@ @defines@
+READLINE_DEFINE=@readline_define@
+INC_READLINE=@inc_readline@
+LIB_READLINE=@lib_readline@
+
+#######################################################################
+# If you're generating a package, you may want to use
+#      make DESTDIR=temporary_dir install
+# to get the software installed to a directory where you can create
+# a tdl.tar.gz from it
+DESTDIR=
+
+#######################################################################
+
+prefix=$(DESTDIR)@prefix@
+bindir=$(DESTDIR)@bindir@
+mandir=$(DESTDIR)@mandir@
+man1dir=$(mandir)/man1
+
+#######################################################################
+# YOU SHOULD NOT NEED TO CHANGE ANYTHING BELOW HERE
+
+OBJ = main.o io.o add.o done.o remove.o move.o list.o \
+      report.o purge.o util.o dates.o impexp.o narrow.o \
+                       inter.o
+
+all : tdl
+
+tdl : $(OBJ)
+       $(CC) $(CFLAGS) -o tdl $(OBJ) $(LIB_READLINE)
+
+%.o : %.c
+       $(CC) $(CFLAGS) -c $<
+
+inter.o : inter.c
+       $(CC) $(CFLAGS) $(READLINE_DEFINE) $(INC_READLINE) -c $<
+
+version.h:
+       ./mkversion
+
+main.o : version.h
+
+%.s : %.c
+       $(CC) $(CFLAGS) -S $<
+
+clean:
+       rm -f tdl *.o core \
+       tdl.vr tdl.tp tdl.pg tdl.ky tdl.fn tdl.cp \
+       tdl.toc tdl.log tdl.dvi tdl.aux \
+       tdl.txt tdl.html tdl.info* tdl.pdf tdl.ps
+       
+distclean: clean
+       rm -f Makefile config.log
+
+install:
+       [ -d $(pprefix) ] || mkdir -p $(pprefix)
+       [ -d $(bindir) ] || mkdir -p $(bindir)
+       [ -d $(mandir) ] || mkdir -p $(mandir)
+       [ -d $(man1dir) ] || mkdir -p $(man1dir)
+       cp tdl $(bindir)/tdl
+       chmod 555 $(bindir)/tdl
+       (cd $(bindir); ln -sf tdl tdla; ln -sf tdl tdll; ln -sf tdl tdls; ln -sf tdl tdld; ln -sf tdl tdlg)
+       gzip -9 < tdl.1 > $(man1dir)/tdl.1.gz
+       chmod 444 $(man1dir)/tdl.1.gz
+       (cd $(man1dir); for x in tdla tdll tdls tdld tdlg ; do ln -sf tdl.1.gz $${x}.1.gz ; done )
+
+       
+docs : tdl.info tdl.txt tdl.html tdl.dvi tdl.pdf
+
+tdl.info : tdl.texi
+       makeinfo tdl.texi
+
+tdl.txt : tdl.texi
+       makeinfo --no-split --number-sections --no-headers tdl.texi > tdl.txt
+
+tdl.html : tdl.texi
+       makeinfo --no-split --number-sections --html tdl.texi > tdl.html
+
+tdl.dvi : tdl.texi
+       tex tdl.texi
+       tex tdl.texi
+
+tdl.ps : tdl.dvi
+       dvips tdl.dvi -o
+
+tdl.pdf : tdl.texi
+       pdftex tdl.texi
+       pdftex tdl.texi
+
+.PHONY : ChangeLog
+
+# Using cvs2cl.pl version 2.48
+ChangeLog:
+       cvs2cl -r -b -T --show-dead
+
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..f17db6b
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,144 @@
+NEW IN VERSION 1.6
+
+- Various compile warnings removed
+- Documentation fixes
+- Fix for spec file
+- Fix readline-less build
+- Fix argument checking for export command
+- Add -u option to unlock a db that's been left locked
+- Hitting ^C 4 times exits a wedged run (with data loss)
+- Allow start of entry text to be used for indexing instead of just entry
+  number.
+- Switch development and release process to use GNU Arch instead of CVS.
+
+NEW IN VERSION 1.5.2
+
+- allow more than 5 digits for pid in creating name of lock file
+
+NEW IN VERSION 1.5.1
+
+- bug fix : string for forming lock file name was undersized by 1 byte
+
+NEW IN VERSION 1.5
+
+- bug fix : trying to move a node above/below/before/after itself corrupted the
+  internal state
+- add per-database dotlocking to prevent multiple processes accessing the same
+  database.  (Can be disabled with --disable-dotlock option to configure).
+
+NEW IN VERSION 1.4.2
+
+- moveto command (alias for into command)
+- bug fix
+
+NEW IN VERSION 1.4.1
+
+- Fix TDL_LIST_MONOCHROME
+- Output without colours if stdout is not a terminal
+- Install manpage compressed and create links from other shortcuts to it
+- Don't include -g amongst default CFLAGS
+
+NEW IN VERSION 1.4
+
+- Add more intelligent configure script
+- Support rl_completion_matches (replaces completion_matches in newer readline
+  libraries)
+- Add "defer" command
+- Remove start-time editing from "edit" command
+- Bug fix : bad path expression with postpone used to segfault
+- Bug fix : completion always matched on 3 characters of command instead
+  of per-command length (broke completion for 'ls')
+- Copy permissions of old database to new database when saving
+- Ask user for confirmation before acting on "quit" command
+
+NEW IN VERSION 1.3.1
+
+- Bug fix : widen without doing narrow first segfaults
+- Spec file fix for mandir
+- Updated manpage
+
+NEW IN VERSION 1.3
+
+- Added "save" command
+- Added "delete", "ls" and "revert" commands.
+- Added "narrow" and "widen" commands
+- Improvements to tdl.spec (e.g. so that SRPM construction works properly).
+
+NEW IN VERSION 1.2
+
+- Fix bug : priority with no args core dumped.
+
+- Bring tdl.1 manpage up to date with texinfo.
+
+- Give option to use ncurses instead of termcap in Makefile.
+
+NEW IN VERSION 1.1
+
+- Add 'interactive' mode to complement command line mode of v1.0.  If built
+  with GNU realine, the new mode has better help, completion facilities etc.
+
+- Add postpone and (re-)open commands (long-term deferral of tasks)
+
+- Add clone and copyto commands (deep copy parts of the tree)
+
+- For 'report', enclose text for parent entries in [[,]] brackets if the parent
+  it not itself 'done'.
+
+- Extensions to 'list' command:
+  * single digit options to set max depth of entries that are shown
+  * substring search for text in entries
+  * -p option to show postponed & deferred entries
+
+- Fix various memory leaks
+
+- Main documentation now supplied in texinfo format.  Manpage is not maintained
+  any longer.
+
+- Revamp of internal help system
+
+- Various bug fixes
+
+- Update contact details
+
+NEW IN VERSION 1.0
+
+- Fix: don't complain if the links tdla, tdll etc already exist when installing.
+
+- Add -q option to suppress "no database" warning.
+
+- Fix: initialise 'parent' properly when creating nodes
+
+- Allow dates to be specified for 'add' and 'done'.  'list' ignores entries
+  added 'in the future', except with -a.  Extend 'edit' command to allow start
+  date/time to be modified.
+
+- Improve installation process (patch from Juergen Daubert)
+
+- Added 'undo' command (for reversing accidental 'done' commands).
+
+- If 'list' command is given index argument(s), use the priority of each such
+  entry as the default (instead of 'normal') when deciding whether to display
+  it and its children, unless a specific priority was given on the command
+  line.
+
+- Added 'which' command, to display the path to the database that is being
+  used.
+
+NEW IN VERSION 0.7
+
+- New before and after commands with identical operation to above and below.
+
+- List command now shows only items of at least normal priority by default, and
+  can take a priority argument.
+
+- New import and export commands
+
+- The remove, done and priority commands can take indices of the form 1... to
+  mean entry 1 and everything underneath it.
+
+- Priorities can be specified by just their initial letter (or any bigger
+  substring)
+
+- Add tdlg to do log as a single command name.
+
+$Header: /cvs/src/tdl/NEWS,v 1.22.2.5 2004/02/03 22:17:22 richard Exp $
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..72c545c
--- /dev/null
+++ b/README
@@ -0,0 +1,75 @@
+INTRODUCTION
+tdl is a lightweight program for managing a 'to-do' list of pending jobs that
+you have.
+    
+It supports the following features :
+- 1 database per directory, or per tree of directories (tdl searches up through
+  parent directories to find the database, so you can have one per project, for
+  example.)
+- add new entries, mark them done, edit the text of entries
+- add a new entry and immediately mark it done (e.g. to log tasks you did which
+  you tackled immediately you got them.)
+- organise the entries in a tree structure (sub-tasks of other tasks etc)
+- move the tasks around and re-organise the hierarchy.
+- list the tasks in the database (default listing excludes 'done' tasks, but
+  these can be shown too if desired).  The listing is in colour by default, with
+  monochrome output as an option.
+- allows entries to be prioritised (priorities shown in different colours on
+  listing).  The listing can selectively show only entries at or above a given
+  priority level.
+- the start time for tasks can be set, to allow for 'deferred' tasks with start
+  times in the future.  Such tasks are excluded from the default listing.
+- track date added and date completed for each task
+- generate report of tasks completed in a given earlier time period (useful if
+  you have to produce a weekly summary of your work done, for example)
+- import and export entries, to allow splitting and merging of databases.
+- written in C
+- runs on a Linux console or in a terminal window.  It currently generates a
+  coloured listing, so a colour xterm or rxvt is preferred.
+
+The functionality and interface are heavily inspired by the program devtodo.
+
+LICENCE
+    The software is licensed under the GPL.
+
+HOMEPAGE
+
+    The software can be found at
+
+    http://www.rc0.org.uk/tdl/index.html
+
+CONTACT
+    The author is Richard P. Curnow.
+    He can be contacted at either of
+
+    rc@rc0.org.uk
+    richard@rrbcurnow.freeuk.com
+
+ACKNOWLEDGEMENTS
+
+Ammon Riley
+    Read-only mode (-R)
+    tdls as a synonym for tdll
+    Bug fixes
+
+David Rhodes
+    Concept for current builtin-help system
+
+Juergen Daubert
+    Manpage
+
+Laurent Papier
+    Fixing the RPM spec file
+
+Pedro Zorzenon Neto
+    Handling of database permissions
+    Confirmation of quit command
+
+taviso
+    Bug fixes
+   
+Yaron Minsky
+    Bug fix
+
+$Header: /cvs/src/tdl/README,v 1.8 2003/05/13 21:06:13 richard Exp $
+
diff --git a/add.c b/add.c
new file mode 100644 (file)
index 0000000..a9b2df7
--- /dev/null
+++ b/add.c
@@ -0,0 +1,326 @@
+/*
+   $Header: /cvs/src/tdl/add.c,v 1.16.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001,2002,2003,2004,2005  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <assert.h>
+#include <ctype.h>
+#include "tdl.h"
+
+static int add_new_node(char *parent_path, int set_done, int set_priority, enum Priority new_priority, time_t insert_time, char *child_text)/*{{{*/
+{
+  struct node *parent = NULL;
+  struct node *nn;
+
+  if (parent_path) {
+    parent = lookup_node(parent_path, 0, NULL);
+    if (!parent) return -1;
+  } else {
+    struct node *narrow_top;
+    narrow_top = get_narrow_top();
+    if (narrow_top) {
+      parent = narrow_top;
+    } else {
+      /* If all else fails, i.e. new node is a top-level one. */
+      parent = NULL;
+    }
+  }
+  
+  nn = new_node();
+  nn->text = new_string(child_text);
+  nn->arrived = (long) insert_time;
+  if (set_done) {
+    nn->done = (long) insert_time;
+  }
+  
+  nn->priority = (parent && !set_priority) ? parent->priority
+                                           : new_priority;
+
+  prepend_child(nn, parent);
+
+  /* Clear done status of parents - they can't be any longer! */
+  if (!set_done) {
+    while (parent) {
+      parent->done = 0;
+      parent = parent->parent;
+    }
+  }
+
+  return 0;
+}
+/*}}}*/
+static char *make_composite(char *prefix, char *suffix)/*{{{*/
+{
+  int plen, slen;
+  int tlen;
+  char *result;
+  int suffix_is_dot;
+  
+  plen = prefix ? strlen(prefix) : 0;
+  slen = suffix ? strlen(suffix) : 0;
+  suffix_is_dot = slen ? (!strcmp(suffix,".")) : 0;
+  tlen = plen + slen;
+  if (plen && slen) ++tlen; /* for internal '.' */
+  if (!plen && !slen) return NULL;
+  result = new_array(char, 1+tlen);
+  result[0] = '\0';
+  if (plen) {
+    strcat(result, prefix);
+    if (slen && !suffix_is_dot) strcat(result, ".");
+  }
+  if (slen && !suffix_is_dot) strcat(result, suffix);
+  return result;
+}
+/*}}}*/
+static int try_add_interactive(char *parent_path, int set_done)/*{{{*/
+{
+  char *text;
+  time_t insert_time;
+  int error = 0;
+  int blank;
+  int status;
+  char prompt[128];
+  char *prompt_base;
+  char *composite_index;
+
+  composite_index = make_composite(get_narrow_prefix(), parent_path);
+
+  prompt_base = set_done ? "log" : "add";
+  if (composite_index) {
+    sprintf(prompt, "%s (%s)> ", prompt_base, composite_index);
+  } else {
+    sprintf(prompt, "%s> ", prompt_base);
+  }
+  if (composite_index) free(composite_index);
+  
+  do {
+    text = interactive_text(prompt, NULL, &blank, &error);
+    if (error < 0) return error;
+    if (!blank) {
+      time(&insert_time);
+      status = add_new_node(parent_path, set_done, 0, PRI_NORMAL, insert_time, text);
+      free(text);
+      if (status < 0) return status;
+    }
+  } while (!blank);
+  return 0;
+}
+/*}}}*/
+static int could_be_index(char *x)/*{{{*/
+{
+  enum {AFTER_DIGIT, AFTER_PERIOD, AFTER_MINUS} state;
+  char *p;
+  if (x[0] == '.') return 1; /* To handle alphabetical matching */
+  state = AFTER_PERIOD;
+  for (p=x; *p; p++) {
+    switch (state) {
+      case AFTER_DIGIT:
+        if (isdigit(*p))    state = AFTER_DIGIT;
+        else if (*p == '-') state = AFTER_MINUS;
+        else if (*p == '.') state = AFTER_PERIOD;
+        else return 0;
+        break;
+      case AFTER_PERIOD:
+        if (isdigit(*p))    state = AFTER_DIGIT;
+        else if (*p == '-') state = AFTER_MINUS;
+        else return 0;
+        break;
+      case AFTER_MINUS:
+        if (isdigit(*p))    state = AFTER_DIGIT;
+        else return 0;
+        break;
+    }
+  }
+  return 1;
+}
+/*}}}*/
+static int process_add_internal(char **x, int set_done)/*{{{*/
+{
+  /* Expect 1 argument, the string to add.  Need other options at some point. */
+  time_t insert_time;
+  int argc = count_args(x);
+  char *text = NULL;
+  char *parent_path = NULL;
+  enum Priority priority = PRI_NORMAL;
+  int set_priority = 0;
+  char *x0;
+  int error;
+
+  insert_time = time(NULL);
+
+  if ((argc > 1) && (x[0][0] == '@')) {
+    int error;
+    /* For 'add', want date to be in the future.
+     * For 'log', want date to be in the past, as for 'done' */
+    int default_positive = set_done ? 0 : 1;
+    insert_time = parse_date(x[0]+1, insert_time, default_positive, &error);
+    if (error < 0) return error;
+    argc--;
+    x++;
+  }
+  
+  switch (argc) {
+    case 0:
+      return try_add_interactive(NULL, set_done);
+      break;
+    case 1:
+      if (could_be_index(x[0])) {
+        return try_add_interactive(x[0], set_done);
+      } else {
+        text = x[0];
+      }
+      break;
+    case 2:
+      text = x[1];
+      x0 = x[0];
+      if (isdigit(x0[0]) || (x0[0] == '.') || (x0[0] == '-') || (x0[0] == '+')) {
+        parent_path = x[0];
+      } else {
+        priority = parse_priority(x0, &error);
+        if (error < 0) return error;
+        set_priority = 1;
+      }
+      break;
+    case 3:
+      text = x[2];
+      priority = parse_priority(x[1], &error);
+      if (error < 0) return error;
+      set_priority = 1;
+      parent_path = x[0];
+      break;
+
+    default:
+      fprintf(stderr, "Usage : add [@<datespec>] [<parent_index>] [<priority>] <entry_text>\n");
+      return -1;
+      break;
+  }
+
+  return add_new_node(parent_path, set_done, set_priority, priority, insert_time, text);
+
+  return 0;
+}
+/*}}}*/
+int process_add(char **x)/*{{{*/
+{
+  return process_add_internal(x, 0);
+}
+/*}}}*/
+int process_log(char **x)/*{{{*/
+{
+  return process_add_internal(x, 1);
+}
+/*}}}*/
+static void modify_tree_arrival_time(struct node *y, time_t new_time)/*{{{*/
+{
+  struct node *c;
+  for (c = y->kids.next; c != (struct node *) &y->kids; c = c->chain.next) {
+    c->arrived = new_time;
+    if (has_kids(c)) {
+      modify_tree_arrival_time(c, new_time);
+    }
+  }
+}
+/*}}}*/
+static int internal_postpone_open(char **x, time_t when)/*{{{*/
+{
+  struct node *n;
+  int do_descendents;
+  
+  while (*x) {
+    do_descendents = include_descendents(*x); /* May modify *x */
+    n = lookup_node(*x, 0, NULL);
+    if (n) {
+      n->arrived = when;
+      if (do_descendents) modify_tree_arrival_time(n, when);
+    }
+    x++;
+  }
+  return 0;
+}
+/*}}}*/
+int process_postpone(char **x)/*{{{*/
+{
+  return internal_postpone_open(x, POSTPONED_TIME);
+}
+/*}}}*/
+int process_open(char **x)/*{{{*/
+{
+  return internal_postpone_open(x, time(NULL));
+}
+/*}}}*/
+int process_defer(char **x)/*{{{*/
+{
+  int argc;
+  time_t new_start_time;
+  int error;
+  char *date_start;
+
+  argc = count_args(x);
+  if (argc < 2) {
+    fprintf(stderr, "Usage: defer <datespec> <index>...\n");
+    return -1;
+  }
+
+  date_start = x[0];
+  new_start_time = time(NULL);
+  /* 'defer' always takes a date so the @ is optional. */
+  if (*date_start == '@') date_start++;
+  new_start_time = parse_date(date_start, new_start_time, 1, &error);
+  return internal_postpone_open(x+1, new_start_time);
+}
+/*}}}*/
+int process_edit(char **x) /*{{{*/
+{
+  int argc;
+  struct node *n;
+
+  argc = count_args(x);
+
+  if ((argc < 1) || (argc > 2)) {
+    fprintf(stderr, "Usage: edit <path> [<new_text>]\n");
+    return -1;
+  }
+
+  n = lookup_node(x[0], 0, NULL);
+  if (!n) return -1;
+  if (argc == 2) {
+    free(n->text);
+    n->text = new_string(x[1]);
+  } else {
+    int error;
+    int is_blank;
+    char *new_text;
+    char prompt[128];
+    char *composite_index;
+
+    composite_index = make_composite(get_narrow_prefix(), x[0]);
+    assert(composite_index);
+    sprintf(prompt, "edit (%s)> ", composite_index);
+    new_text = interactive_text(prompt, n->text, &is_blank, &error);
+    free(composite_index);
+    if (error < 0) return error;
+    if (!is_blank) {
+      free(n->text);
+      n->text = new_text; /* We own the pointer now */
+    }
+  }
+
+  return 0;
+}
+/*}}}*/
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..0fbb8b1
--- /dev/null
+++ b/configure
@@ -0,0 +1,445 @@
+#!/bin/sh
+#########################################################################
+#
+# $Id: configure,v 1.5.2.2 2004/01/07 00:09:05 richard Exp $
+#
+# =======================================================================
+#
+# tdl - A console program for managing to-do lists
+#
+# Copyright (C) Richard P. Curnow  2003-2004
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+#
+# =======================================================================
+
+if [ -f config.log ]; then rm -f config.log ; fi
+exec 5>config.log
+
+if [ "x${CC}" = "x" ]; then
+  MYCC="gcc"
+else
+  MYCC="${CC}"
+fi
+
+if [ "x${CFLAGS}" = "x" ]; then
+  MYCFLAGS="-O2 -Wall"
+else
+  MYCFLAGS="${CFLAGS}"
+fi
+
+# =======================================================================
+# Functions
+
+#{{{ cleanup
+cleanup () {
+  if [ -f docheck.c ]; then rm -f docheck.c ; fi
+  if [ -f docheck.o ]; then rm -f docheck.o ; fi
+  if [ -f docheck   ]; then rm -f docheck   ; fi
+  rm -rf docheck.c docheck.o docheck
+}
+#}}}
+
+#{{{ test_cc : basic compiler sanity check
+test_cc () {
+  printf "Testing whether your compiler \"$MYCC $MYCFLAGS\" works : "
+  cat >docheck.c <<EOF;
+#include <stdio.h>
+int main (int argc, char **argv)
+{
+  return 0;
+}
+EOF
+  ${MYCC} ${MYCFLAGS} -o docheck docheck.c 1>&5 2>&5
+  if [ $? -eq 0 ]
+  then
+    printf "it works\n"
+  else
+    printf "it doesn't work\n"
+    printf "Failed program was\n" 1>&5
+    cat docheck.c 1>&5
+    rm -f docheck.c docheck
+    exit 1
+  fi
+  cleanup
+}
+#}}}
+#{{{ test_readline_include
+test_readline_include () {
+  # Objective : test whether we can compile and link a program using
+  # readline, and if so how.
+
+  # As for includes, is it <readline.h> or <readline/readline.h>
+
+  # As for linking, do we need -ltermcap, -lncurses etc
+
+  printf "Testing what to include for readline : "
+  cat >docheck.c <<EOF;
+#include <stdio.h>
+#include <readline.h>
+int main (int argc, char **argv) {
+  printf("%s\n", rl_library_version);
+  return 0;
+}
+EOF
+  ${MYCC} ${MYCFLAGS} ${readline_inc_arg} -c -o docheck.o docheck.c 1>&5 2>&5
+  if [ $? -eq 0 ]; then
+    printf "<readline.h>\n"
+    READLINE_DEFINE="-DBARE_READLINE_H=1"
+  else
+    rm -f docheck.c
+    cat >docheck.c <<EOF;
+#include <stdio.h>
+#include <readline/readline.h>
+int main (int argc, char **argv) {
+  printf("%s\n", rl_library_version);
+  return 0;
+}
+EOF
+    ${MYCC} ${MYCFLAGS} ${readline_inc_arg} -c -o docheck.o docheck.c 1>&5 2>&5
+    if [ $? -eq 0 ]; then
+      printf "<readline/readline.h>\n"
+      READLINE_DEFINE="-DBARE_READLINE_H=0"
+    else
+      printf "FAILED\n"
+      printf "Can't include readline.h, giving up on readline"
+      use_readline="no"
+    fi
+  fi
+
+  cleanup
+  return
+}
+
+#}}}
+#{{{ test_readline_lib
+test_readline_lib () {
+
+  printf "Testing extra libraries to link with readline : "
+  cat >docheck.c <<EOF;
+#include <stdio.h>
+#if BARE_READLINE_H
+#include <readline.h>
+#else
+#include <readline/readline.h>
+#endif
+
+int main (int argc, char **argv) {
+  printf("%s\n", rl_library_version);
+  return 0;
+}
+EOF
+  ${MYCC} ${MYCFLAGS} ${readline_inc_arg} ${READLINE_DEFINE} -c -o docheck.o docheck.c 1>&5 2>&5
+  if [ $? -ne 0 ]; then
+    printf "unexpected compile failure, giving up on readline\n"
+    use_readline="no"
+    cleanup
+    return
+  fi
+
+  # Now try to link it
+  ${MYCC} ${MYCFLAGS} -o docheck docheck.o ${readline_lib_arg} -lreadline 1>&5 2>&5
+  if [ $? -eq 0 ]; then
+    extra_readline_libs=""
+    printf "(none)\n"
+    cleanup
+    return
+  fi
+
+  ${MYCC} ${MYCFLAGS} -o docheck docheck.o ${readline_lib_arg} -lreadline -ltermcap 1>&5 2>&5
+  if [ $? -eq 0 ]; then
+    extra_readline_libs="-ltermcap"
+    printf -- "-ltermcap\n"
+    cleanup
+    return
+  fi
+
+  ${MYCC} ${MYCFLAGS} -o docheck docheck.o ${readline_lib_arg} -lreadline -lncurses 1>&5 2>&5
+  if [ $? -eq 0 ]; then
+    extra_readline_libs="-lncurses"
+    printf -- "-lncurses\n"
+    cleanup
+    return
+  fi
+
+  printf "can't link a readline program\n"
+  use_readline="no"
+  return
+
+}
+#}}}
+#{{{ test_completion_matches
+test_completion_matches () {
+  # See if the readline library has completion_matches()
+  printf "Testing for completion_matches in readline : "
+  cat >docheck.c <<EOF;
+#include <stdio.h>
+#if BARE_READLINE_H
+#include <readline.h>
+#else
+#include <readline/readline.h>
+#endif
+
+int main (int argc, char **argv) {
+  void *x = &completion_matches;
+  printf("%p\n", x);
+  return 0;
+}
+EOF
+  ${MYCC} ${MYCFLAGS} ${readline_inc_arg} ${READLINE_DEFINE} -c -o docheck.o docheck.c 1>&5 2>&5
+  if [ $? -ne 0 ]; then
+    printf "no\n";
+    has_completion_matches=no
+    cleanup
+    return
+  fi
+
+  ${MYCC} ${MYCFLAGS} -o docheck docheck.o ${readline_lib_arg} -lreadline ${extra_readline_libs} 1>&5 2>&5
+  if [ $? -eq 0 ]; then
+    printf "yes\n"
+    has_completion_matches=yes
+  else
+    printf "no\n"
+    has_completion_matches=no
+  fi
+  cleanup
+}
+#}}}
+#{{{ test_rl_completion_matches
+test_rl_completion_matches () {
+  # See if the readline library has completion_matches()
+  printf "Testing for rl_completion_matches in readline : "
+  cat >docheck.c <<EOF;
+#include <stdio.h>
+#if BARE_READLINE_H
+#include <readline.h>
+#else
+#include <readline/readline.h>
+#endif
+
+int main (int argc, char **argv) {
+  void *x = &rl_completion_matches;
+  printf("%p\n", x);
+  return 0;
+}
+EOF
+  ${MYCC} ${MYCFLAGS} ${readline_inc_arg} ${READLINE_DEFINE} -c -o docheck.o docheck.c 1>&5 2>&5
+  if [ $? -ne 0 ]; then
+    printf "no\n"
+    has_rl_completion_matches=no
+    cleanup
+    return
+  fi
+
+  ${MYCC} ${MYCFLAGS} -o docheck docheck.o ${readline_lib_arg} -lreadline ${extra_readline_libs} 1>&5 2>&5
+  if [ $? -eq 0 ]; then
+    printf "yes\n"
+    has_rl_completion_matches=yes
+  else
+    printf "no\n"
+    has_rl_completion_matches=no
+  fi
+  cleanup
+}
+#}}}
+
+#{{{ usage
+usage () {
+  cat <<EOF;
+\`configure' configures tdl to adapt to many kinds of systems.
+
+Usage: ./configure [OPTION]...
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [/usr/local]
+
+By default, \`make install' will install all the files in
+\`/usr/local/bin', \`/usr/local/lib' etc.  You can specify
+an installation prefix other than \`/usr/local' using \`--prefix',
+for instance \`--prefix=$HOME'.
+
+For better control, use the options below.
+  --without-readline     Don't try to use GNU readline
+  --readline-dir=DIR     Specify parent of readline include and lib directories
+  --readline-inc-dir=DIR Specify where readline include directory is
+  --readline-lib-dir=DIR Specify where readline lib directory is
+
+Fine tuning of the installation directories:
+  --bindir=DIR           user executables [EPREFIX/bin]
+  --infodir=DIR          info documentation [PREFIX/info]
+  --mandir=DIR           man documentation [PREFIX/man]
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+
+Use these variables to override the choices made by \`configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <rc@rc0.org.uk>.
+EOF
+}
+#}}}
+# =======================================================================
+
+# Defaults for variables
+PREFIX=/usr/local
+
+use_readline=yes
+bad_options=no
+use_dotlock=yes
+
+# Parse options to configure
+for option
+do
+       case "$option" in
+
+  --prefix=* | --install-prefix=* )
+    PREFIX=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --bindir=* )
+    BINDIR=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --mandir=* )
+    MANDIR=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --infodir=* )
+    INFODIR=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --without-readline )
+    use_readline=no
+    ;;
+  --readline-dir=* )
+    readline_dir=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --readline-inc-dir=* )
+    readline_inc_dir=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --readline-lib-dir=* )
+    readline_lib_dir=`echo $option | sed -e 's/[^=]*=//;'`
+    ;;
+  --disable-dotlock )
+    use_dotlock=no
+    ;;
+  -h | --help )
+    usage
+    exit 1
+    ;;
+  * )
+    printf "Unrecognized option : $option\n"
+    bad_options=yes
+    ;;
+  esac
+done
+
+if [ ${bad_options} = yes ]; then
+  exit 1
+fi
+
+#{{{ process readline-related arguments
+if [ "$use_readline" = "yes" ]; then
+  if [ "x$readline_dir" != "x" ]; then
+    if [ "x$readline_inc_dir" = "x" ]; then
+      readline_inc_dir="${readline_dir}/include"
+    fi
+    if [ "x$readline_lib_dir" = "x" ]; then
+      readline_lib_dir="${readline_dir}/lib"
+    fi
+  fi
+
+  if [ "x$readline_inc_dir" != "x" ]; then
+    readline_inc_arg="-I${readline_inc_dir}"
+  else
+    readline_inc_arg=""
+  fi
+
+  if [ "x$readline_lib_dir" != "x" ]; then
+    readline_lib_arg="-L${readline_lib_dir} -R${readline_lib_dir}"
+  else
+    readline_lib_arg=""
+  fi
+fi
+#}}}
+
+test_cc
+if [ "${use_readline}" = "yes" ]; then test_readline_include      ; fi
+if [ "${use_readline}" = "yes" ]; then test_readline_lib          ; fi
+if [ "${use_readline}" = "yes" ]; then test_completion_matches    ; fi
+if [ "${use_readline}" = "yes" ]; then test_rl_completion_matches ; fi
+
+case "${use_readline}-${has_rl_completion_matches}-${has_completion_matches}" in
+  yes-yes-* )
+    READLINE_DEFINE="-DUSE_READLINE=1 ${READLINE_DEFINE} -DUSE_RL_COMPLETION_MATCHES=1" 
+    ;;
+  yes-*-yes )
+    READLINE_DEFINE="-DUSE_READLINE=1 ${READLINE_DEFINE} -DUSE_RL_COMPLETION_MATCHES=0" 
+    ;;
+  yes-* )
+    printf "Can't find a completion function in readline\n"
+    exit 1
+    ;;
+  no-* )
+    READLINE_DEFINE="-UUSE_READLINE"
+    printf "Not using readline\n";
+esac
+
+case "${use_dotlock}" in
+  yes )
+    DOTLOCK_DEFINE="-DUSE_DOTLOCK=1"
+    ;;
+  no )
+    DOTLOCK_DEFINE=""
+    ;;
+esac
+
+DEFINES=${DOTLOCK_DEFINE}
+
+if [ "x" = "x${BINDIR}" ]; then BINDIR=${PREFIX}/bin ; fi
+if [ "x" = "x${MANDIR}" ]; then MANDIR=${PREFIX}/man ; fi
+if [ "x" = "x${INFODIR}" ]; then INFODIR=${PREFIX}/info ; fi
+
+if [ "x${use_readline}" = "xno" ]; then
+  lib_readline=""
+else
+  lib_readline="${readline_lib_arg} -lreadline ${extra_readline_libs}"
+fi
+
+echo "Generating Makefile"
+
+rm -f Makefile
+sed -e "s%@cc@%${MYCC}%; \
+        s%@cflags@%${MYCFLAGS}%; \
+        s%@prefix@%${PREFIX}%; \
+        s%@bindir@%${BINDIR}%; \
+        s%@mandir@%${MANDIR}%; \
+        s%@infodir@%${INFODIR}%; \
+        s%@readline_define@%${READLINE_DEFINE}%; \
+        s%@defines@%${DEFINES}%; \
+        s%@inc_readline@%${readline_inc_arg}%; \
+        s%@lib_readline@%${lib_readline}%; \
+       " < Makefile.in > Makefile
+
+# =======================================================================
+# vim:et:sw=2:ht=2:sts=2:fdm=marker:cms=#%s
+
diff --git a/dates.c b/dates.c
new file mode 100644 (file)
index 0000000..8daedac
--- /dev/null
+++ b/dates.c
@@ -0,0 +1,301 @@
+/*
+   $Header: /cvs/src/tdl/dates.c,v 1.5.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+/* This file deals with parsing dates and relative time expressions. */
+
+#include "tdl.h"
+#include <string.h>
+#include <ctype.h>
+
+static time_t tm_to_time_t (struct tm *stm)/*{{{*/
+{
+  /* Convert from (struct tm) to time_t.  This has to be an inverse of
+   * localtime().  It's not obvious that mktime() does this right, especially
+   * with daylight saving time, on all systems (as I found out writing the RTC
+   * support for chrony!).  This function will apply any compensation that is
+   * needed. */ 
+
+  struct tm temp1, temp2;
+  long diff;
+  time_t t1, t2;
+  temp1 = *stm;
+  temp1.tm_isdst = 0;
+  t1 = mktime(&temp1);
+  temp2 = *localtime(&t1);
+  temp2.tm_isdst = 0;
+  t2 = mktime(&temp2);
+  diff = t2 - t1;
+  return t1 - diff;
+}
+/*}}}*/
+static int parse_time(char *d, int *hour, int *min, int *sec)/*{{{*/
+{
+  int n;
+  *hour = *min = *sec = 0;
+  switch (strlen(d)) {
+    case 2:
+      n = sscanf(d, "%2d", hour);
+      if (n != 1) {
+        fprintf(stderr, "Can't parse hour from %s\n", d);
+        return -1;
+      }
+      break;
+    case 4:
+      n = sscanf(d, "%2d%2d", hour, min);
+      if (n != 2) {
+        fprintf(stderr, "Can't parse hour and minute from %s\n", d);
+        return -1;
+      }
+      break;
+    case 6:
+      n = sscanf(d, "%2d%2d%2d", hour, min, sec);
+      if (n != 3) {
+        fprintf(stderr, "Can't parse hour, minute and second from %s\n", d);
+        return -1;
+      }
+      break;
+    default:
+      fprintf(stderr, "Cannot parse time\n");
+      return -1;
+      break;
+  }
+
+  return 0;
+}
+/*}}}*/
+time_t parse_date(char *d, time_t ref, int default_positive, int *error)/*{{{*/
+{
+  int len;
+  char *hyphenpos;
+  char *d0;
+  time_t result;
+  
+  *error = 0; /* default : success */
+  
+  d0 = (*d == '-') ? d+1 : d;
+  hyphenpos = strchr(d0, '-');
+
+  if (hyphenpos) *hyphenpos = 0;
+  
+  len = strlen(d);
+
+  if (!strcmp(d, ".")) {
+    result = ref;
+  
+  } else if (isalpha(d[0]) ||
+      (((d[0] == '+')|| (d[0] == '-')) && isalpha(d[1]))) {
+
+    /* Look for dayname, +dayname, or -dayname */
+    int dow = -1;
+    int posneg, diff;
+    char *dd = d;
+    struct tm stm;
+
+    if      (*dd == '+') posneg = 1, dd++;
+    else if (*dd == '-') posneg = 0, dd++;
+    else                 posneg = default_positive;
+
+    /* This really needs to be internationalized properly.  Somebody who
+     * understands how to make locales work properly needs to fix that. */
+    
+    if      (!strncasecmp(dd, "sun", 3)) dow = 0;
+    else if (!strncasecmp(dd, "mon", 3)) dow = 1;
+    else if (!strncasecmp(dd, "tue", 3)) dow = 2;
+    else if (!strncasecmp(dd, "wed", 3)) dow = 3;
+    else if (!strncasecmp(dd, "thu", 3)) dow = 4;
+    else if (!strncasecmp(dd, "fri", 3)) dow = 5;
+    else if (!strncasecmp(dd, "sat", 3)) dow = 6;
+
+    if (dow < 0) {
+      fprintf(stderr, "Cannot understand day of week\n");
+    }
+
+    stm = *localtime(&ref);
+    diff = dow - stm.tm_wday;
+    
+    if (diff == 0) {
+      /* If the day specified is the same day of the week as today, step a
+       * whole week in the required direction. */
+      if (posneg) result = ref + (7*86400);
+      else        result = ref - (7*86400);
+      
+    } else if (diff > 0) {
+      if (posneg) result = ref + (diff * 86400);
+      else        result = ref - ((7-diff) * 86400);
+    } else { /* diff < 0 */
+      if (posneg) result = ref + ((7+diff) * 86400);
+      else        result = ref + (diff * 86400);
+    }
+
+    stm = *localtime(&result);
+    
+    stm.tm_hour = 12;
+    stm.tm_min = 0;
+    stm.tm_sec = 0;
+    result = tm_to_time_t(&stm);
+    
+  } else if ((len > 1) && isalpha(d[len-1])) {
+    /* Relative time */
+    long interval;
+    int nc;
+    int posneg;
+
+    if      (*d == '+') posneg = 1, d++;
+    else if (*d == '-') posneg = 0, d++;
+    else                posneg = default_positive;
+
+    if (sscanf(d, "%ld%n", &interval, &nc) != 1) {
+      fprintf(stderr, "Cannot recognize interval '%s'\n", d);
+      *error = -1;
+      return (time_t) 0; /* arbitrary */
+    }
+    
+    d += nc;
+    switch (d[0]) {
+      case 'h': interval *= 3600; break;
+      case 'd': interval *= 86400; break;
+      case 'w': interval *= 7 * 86400; break;
+      case 'm': interval *= 30 * 86400; break;
+      case 'y': interval *= 365 * 86400; break;
+      case 's': break; /* use seconds */
+      default:
+        fprintf(stderr, "Can't understand interval multiplier '%s'\n", d);
+        *error = -1;
+        return (time_t) 0; /* arbitrary */
+    }
+    if (!posneg) interval = -interval;
+  
+    result = ref + interval;
+
+  } else {
+    int year, month, day, n;
+    struct tm stm;
+
+    stm = *localtime(&ref);
+
+    /* Try to parse absolute date.
+     * Formats allowed : dd, mmdd, yymmdd, yyyymmdd.
+     * Time is assumed to be noon on the specified day */
+    switch (len) {
+      case 0:
+        day = stm.tm_mday;
+        month = stm.tm_mon + 1;
+        year = stm.tm_year;
+        break;
+      case 2:
+        n = sscanf(d, "%2d", &day);
+        if (n != 1) {
+          fprintf(stderr, "Can't parse day from %s\n", d);
+          *error = -1;
+          return (time_t) 0; /* arbitrary */
+        }
+        month = stm.tm_mon + 1;
+        year = stm.tm_year;
+        break;
+      case 4:
+        n = sscanf(d, "%2d%2d", &month, &day);
+        if (n != 2) {
+          fprintf(stderr, "Can't parse month and day from %s\n", d);
+          *error = -1;
+          return (time_t) 0; /* arbitrary */
+        }
+        year = stm.tm_year;
+        break;
+      case 6:
+        n = sscanf(d, "%2d%2d%2d", &year, &month, &day);
+        if (n != 3) {
+          fprintf(stderr, "Can't parse year, month and day from %s\n", d);
+          *error = -1;
+          return (time_t) 0; /* arbitrary */
+        }
+        if (year < 70) year += 100;
+        break;
+      case 8:
+        n = sscanf(d, "%4d%2d%2d", &year, &month, &day);
+        if (n != 3) {
+          fprintf(stderr, "Can't parse year, month and day from %s\n", d);
+          *error = -1;
+          return (time_t) 0; /* arbitrary */
+        }
+        year -= 1900;
+        break;
+      default:
+       fprintf(stderr, "Can't parse date from %s\n", d);
+        *error = -2;
+        return (time_t) 0; /* arbitrary */
+       break;
+    }
+    stm.tm_year = year;
+    stm.tm_mon = month - 1;
+    stm.tm_mday = day;
+    stm.tm_hour = 12;
+    stm.tm_min = 0;
+    stm.tm_sec = 0;
+    result = tm_to_time_t(&stm);
+  }
+
+  if (hyphenpos) {
+    int hour, min, sec;
+    struct tm stm;
+    
+    parse_time(hyphenpos+1, &hour, &min, &sec);
+    stm = *localtime(&result);
+    stm.tm_hour = hour;
+    stm.tm_min = min;
+    stm.tm_sec = sec;
+    result = tm_to_time_t(&stm);
+  }
+
+  return result;
+  
+}/*}}}*/
+
+/*{{{  Test code*/
+#ifdef TEST
+
+#include <locale.h>
+
+int main (int argc, char **argv)
+{
+  time_t ref, newtime;
+  char buffer[64];
+
+  setlocale(LC_ALL, "");
+  
+  ref = time(NULL);
+
+  if (argc < 2) {
+    fprintf(stderr, "Require an argument\n");
+    return 1;
+  }
+
+  newtime = parse_date(argv[1], ref, (argc > 2));
+
+  strftime(buffer, sizeof(buffer), "%a %A %d %b %B %Y %H:%M:%S", localtime(&newtime));
+  printf("%s\n", buffer);
+
+  return 0;
+
+}
+#endif
+
+/*}}}*/
+
diff --git a/done.c b/done.c
new file mode 100644 (file)
index 0000000..8de05f9
--- /dev/null
+++ b/done.c
@@ -0,0 +1,147 @@
+/*;
+   $Header: /cvs/src/tdl/done.c,v 1.10.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include "tdl.h"
+
+int has_open_child(struct node *y)/*{{{*/
+{
+  struct node *c;
+  int result = 0;
+  for (c = y->kids.next; c != (struct node *) &y->kids; c = c->chain.next) {
+    if (c->done == 0) {
+      result = 1;
+      break;
+    }
+  }
+
+  return result;
+}
+/*}}}*/
+static void mark_done_from_bottom_up(struct links *x, time_t done_time)/*{{{*/
+{
+  struct node *y; 
+
+  for (y = x->next; y != (struct node *) x; y = y->chain.next) {
+
+    if (has_kids(y)) {
+      mark_done_from_bottom_up(&y->kids, done_time);
+    }
+    
+    if (y->flag) {
+      if (has_open_child(y)) {
+        fprintf(stderr, "Cannot mark %s done, it has open sub-tasks\n", y->scratch);
+      } else {
+        y->done = done_time;
+      }
+    }
+  }
+}
+/*}}}*/
+static int internal_done(time_t when, char **x)/*{{{*/
+{
+  struct node *n;
+  int do_descendents;
+
+  clear_flags(&top);
+
+  while (*x) {
+    do_descendents = include_descendents(*x); /* May modify *x */
+    n = lookup_node(*x, 0, NULL);
+    if (!n) return -1;
+    n->flag = 1;
+    if (do_descendents) {
+      mark_all_descendents(n);
+    }
+    n->scratch = *x; /* Safe to alias, *x has long lifetime */
+    x++;
+  }
+   
+  mark_done_from_bottom_up(&top, when);
+
+  return 0;
+}
+/*}}}*/
+int process_done(char **x)/*{{{*/
+{
+  time_t done_time = time(NULL);
+
+  if (*x && (x[0][0] == '@')) {
+    int error;
+    done_time = parse_date(x[0]+1, done_time, 0, &error);
+    if (error < 0) return error;
+    x++;
+  }
+
+  internal_done(done_time, x);
+  return 0;
+}
+/*}}}*/
+int process_ignore(char **x)/*{{{*/
+{
+  /* (struct node).done == 0 means not done yet.  So use 1 to mean ancient
+   * history (never shows up in reports, sure to be purged at next purge) */
+  internal_done(IGNORED_TIME, x);
+  return 0;
+}
+/*}}}*/
+static void undo_descendents(struct node *y)/*{{{*/
+{
+  struct node *c;
+  for (c = y->kids.next; c != (struct node *) &y->kids; c = c->chain.next) {
+    c->done = 0;
+    if (has_kids(c)) {
+      undo_descendents(c);
+    }
+  }
+}
+/*}}}*/
+static void undo_ancestors(struct node *y)/*{{{*/
+{
+  struct node *parent;
+  parent = y->parent;
+  while (parent) {
+    parent->done = 0;
+    parent = parent->parent;
+  }
+}
+/*}}}*/
+int process_undo(char **x)/*{{{*/
+{
+  struct node *n;
+  int do_descendents;
+
+  while (*x) {
+    do_descendents = include_descendents(*x); /* May modify *x */
+    n = lookup_node(*x, 0, NULL);
+    if (!n) return -1;
+    n->done = 0;
+    undo_ancestors(n);
+    if (do_descendents) {
+      undo_descendents(n);
+    }
+    x++;
+  }
+
+  return 0;
+
+}
+/*}}}*/
+
diff --git a/impexp.c b/impexp.c
new file mode 100644 (file)
index 0000000..80c56e4
--- /dev/null
+++ b/impexp.c
@@ -0,0 +1,179 @@
+/*
+   $Header: /cvs/src/tdl/impexp.c,v 1.6.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+/* This code deals with the import and export commands. */
+
+#include "tdl.h"
+
+static struct node *clone_node(struct node *x, struct node *parent)/*{{{*/
+{
+  struct node *r;
+  struct node *k;
+  r = new_node();
+  r->text = new_string(x->text);
+  r->parent = parent;
+  r->priority = x->priority;
+  r->arrived = x->arrived;
+  r->required_by = x->required_by;
+  r->done = x->done;
+
+  for (k = x->kids.next; k != (struct node *) &x->kids; k = k->chain.next) {
+    struct node *nc = clone_node(k, r);
+    prepend_node(nc, &r->kids);
+  }
+
+  return r;
+}
+/*}}}*/
+static void set_arrived_time(struct node *x, time_t now)/*{{{*/
+{
+  struct node *k;
+  x->arrived = now;
+  for (k = x->kids.next; k != (struct node *) &x->kids; k = k->chain.next) {
+    set_arrived_time(k, now);
+  }
+}
+/*}}}*/
+int process_export(char **x)/*{{{*/
+{
+  FILE *out;
+  char *filename;
+  int argc, i;
+  struct links data;
+
+  argc = count_args(x);
+
+  if (argc < 2) {
+    fprintf(stderr, "Need a filename and at least one index to write\n");
+    return -2;
+  }
+  
+  filename = x[0];
+
+  data.prev = data.next = (struct node *) &data;
+
+  /* Build linked list of data to write */
+  for (i=1; i<argc; i++) {
+    struct node *n, *nn;
+    n = lookup_node(x[i], 0, NULL);
+    if (!n) return -1;
+    nn = clone_node(n, NULL);
+    prepend_node(nn, &data);
+  }
+  
+  out = fopen(filename, "wb");
+  if (!out) {
+    fprintf(stderr, "Could not open file %s for writing\n", filename);
+    return -2;
+  }
+
+  write_database(out, &data);
+    
+  fclose(out);
+  free_database(&data);
+  return 0;
+
+}
+/*}}}*/
+int process_import(char **x)/*{{{*/
+{
+  char *filename;
+  FILE *in;
+  struct links data;
+  struct node *n, *nn;
+  int argc;
+  int result;
+
+  argc = count_args(x);
+  if (argc < 1) {
+    fprintf(stderr, "Import requires a filename\n");
+    return -2;
+  }
+
+  filename = x[0];
+  in = fopen(filename, "rb");
+  if (!in) {
+    fprintf(stderr, "Could not open file %s for input\n", filename);
+    return -2;
+  }
+
+  result = read_database(in, &data);
+  fclose(in);
+
+  if (!result) {
+    /* read was OK */
+    for (n = data.next; n != (struct node *) &data; n = nn) {
+      nn = n->chain.next;
+      prepend_node(n, &top);
+      n->parent = NULL;
+    }
+  }
+
+  return result;
+
+}
+/*}}}*/
+static int internal_copy_clone(struct links *l, char **x)/*{{{*/
+{
+  int argc, i;
+  time_t now;
+
+  now = time(NULL);
+
+  argc = count_args(x);
+  if (argc < 1) {
+    fprintf(stderr, "Need at least one index to copy/clone\n");
+    return -2;
+  }
+
+  for (i=0; i<argc; i++) {
+    struct node *n, *nn;
+    n = lookup_node(x[i], 0, NULL);
+    if (!n) return -1;
+    nn = clone_node(n, NULL);
+    set_arrived_time(nn, now);
+    prepend_node(nn, l);
+  }
+
+  return 0;
+}
+/*}}}*/
+int process_clone(char **x)/*{{{*/
+{
+  struct node *narrow_top;
+  narrow_top = get_narrow_top();
+  return internal_copy_clone((narrow_top ? &narrow_top->kids : &top), x);
+}
+/*}}}*/
+int process_copyto(char **x)/*{{{*/
+{
+  struct node *parent;
+  if (count_args(x) < 1) {
+    fprintf(stderr, "Need a parent index to copy into\n");
+    return -2;
+  }
+  parent = lookup_node(x[0], 0, NULL);
+  if (!parent) {
+    return -1;
+  }
+  return internal_copy_clone(&parent->kids, x + 1);
+}
+/*}}}*/
diff --git a/inter.c b/inter.c
new file mode 100644 (file)
index 0000000..ce96805
--- /dev/null
+++ b/inter.c
@@ -0,0 +1,678 @@
+/*
+   $Header: /cvs/src/tdl/inter.c,v 1.13.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+/* This file deals with interactive mode */
+
+#include "tdl.h"
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef USE_READLINE
+#if BARE_READLINE_H 
+#include <readline.h>
+#include <history.h>
+#else
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#if USE_RL_COMPLETION_MATCHES
+#define COMPLETION_MATCHES rl_completion_matches
+#else
+#define COMPLETION_MATCHES completion_matches
+#endif
+
+#endif
+
+
+#ifdef USE_READLINE
+static char *generate_a_command_completion(const char *text, int state)/*{{{*/
+{
+  static int list_index, len;
+  char *name;
+
+  if (!state) {
+    list_index = 0;
+    len = strlen(text);
+  }
+
+  while (list_index < n_cmds) {
+    int inter_ok = cmds[list_index].interactive_ok;
+    name = cmds[list_index].name;
+    list_index++;
+    if (inter_ok && !strncmp(name, text, len)) {
+      return new_string(name);
+    }
+  }
+
+  return NULL; /* For no matches */
+}
+/*}}}*/
+static char *generate_a_priority_completion(const char *text, int state)/*{{{*/
+{
+  static char *priorities[] = {"urgent", "high", "normal", "low", "verylow", NULL};
+  static int list_index, len;
+  char *name;
+
+  if (!state) {
+    list_index = 0;
+    len = strlen(text);
+  }
+
+  while ((name = priorities[list_index])) {
+    list_index++;
+    if (!strncmp(name, text, len)) {
+      return new_string(name);
+    }
+  }
+
+  return NULL; /* For no matches */
+}
+/*}}}*/
+
+/* Structure to build a single linked list of nodes, working towards the
+ * ancestor. */
+struct node_chain {
+  struct node_chain *next;
+  struct node *node;
+  struct node *start; /* To see if we've got back to the start of the ring */
+  int index;
+};
+
+static struct node_chain *start_chain(struct links *x, struct node_chain *parent)/*{{{*/
+{
+  struct node_chain *result;
+  result = new(struct node_chain);
+  result->start = (struct node *) x;
+  result->node = x->next;
+  result->index = 1;
+  result->next = parent;
+  return result;
+}
+/*}}}*/
+static void cleanup_chain(struct node_chain *chain)/*{{{*/
+{
+  /* TODO: Reclaim memory */
+  return;
+}
+/*}}}*/
+static char *format_index(struct node_chain *chain)/*{{{*/
+{
+  int indices[256];
+  int n, first;
+  struct node_chain *x;
+  char buf[1024], buf1[32];
+
+  for (n=0, x=chain; x; n++, x=x->next) {
+    indices[n] = x->index;
+  }
+  buf[0] = '\0';
+  first = 1;
+  while (--n >= 0) {
+    sprintf(buf1, "%d", indices[n]);
+    if (!first) {
+      strcat(buf, ".");
+    } else {
+      first = 0;
+    }
+    strcat (buf, buf1);
+  }
+  return new_string(buf);
+}
+/*}}}*/
+static struct node *advance_chain(struct node_chain **pchain, char **index_string)/*{{{*/
+{
+  struct node_chain *chain = *pchain;
+  struct node *result = NULL;
+
+#if DEBUG_ADVANCE
+  fprintf(stderr, "advance chain, top index=%d\n", chain->index);
+#endif
+  
+  while (1) {
+    if (chain->node == chain->start) {
+      struct node_chain *next = chain->next;
+      free(chain);
+      chain = next;
+      if (chain) {
+        chain->node = chain->node->chain.next;
+        ++chain->index;
+#if DEBUG_ADVANCE
+        fprintf(stderr, "Returning to outer level, index=%d\n", chain->index);
+#endif
+        continue;
+      } else {
+#if DEBUG_ADVANCE
+        fprintf(stderr, "Got to outermost level\n");
+#endif
+        result = NULL;
+        break;
+      }
+    } else if (has_kids(chain->node)) {
+#if DEBUG_ADVANCE
+      fprintf(stderr, "Node has children, scanning children next\n");
+#endif
+      result = chain->node;
+      *index_string = format_index(chain);
+      /* Set-up to visit kids next time */
+      chain = start_chain(&chain->node->kids, chain);
+      break;
+    } else {
+      /* Ordinary case */
+#if DEBUG_ADVANCE
+      fprintf(stderr, "ordinary case\n");
+#endif
+      result = chain->node;
+      *index_string = format_index(chain);
+      chain->node = chain->node->chain.next;
+      ++chain->index;
+      break;
+    }
+  }
+
+  *pchain = chain;
+  return result;
+}
+/*}}}*/
+static char *generate_a_done_completion(const char *text, int state)/*{{{*/
+{
+  static struct node_chain *chain = NULL;
+  struct node *node;
+  struct node *narrow_top;
+  char *buf;
+  int len;
+
+  load_database_if_not_loaded();
+
+  if (!state) {
+    /* Re-initialise the node chain */
+    if (chain) cleanup_chain(chain);
+    narrow_top = get_narrow_top();
+    chain = start_chain((narrow_top ? &narrow_top->kids : &top), NULL);
+  }
+
+  len = strlen(text);
+  while ((node = advance_chain(&chain, &buf))) {
+    int undone = (!node->done);
+    if (undone && !strncmp(text, buf, len)) {
+      return buf;
+    } else {
+      /* Avoid gross memory leak */
+      free(buf);
+    }
+  }
+  return NULL;
+}
+/*}}}*/
+
+/* Can't pass extra args thru to completers :-( */
+static int want_postponed_entries = 0;
+
+static char *generate_a_postpone_completion(const char *text, int state)/*{{{*/
+{
+  static struct node_chain *chain = NULL;
+  struct node *node;
+  struct node *narrow_top;
+  char *buf;
+  int len;
+
+  load_database_if_not_loaded();
+
+  if (!state) {
+    /* Re-initialise the node chain */
+    if (chain) cleanup_chain(chain);
+    narrow_top = get_narrow_top();
+    chain = start_chain((narrow_top ? &narrow_top->kids : &top), NULL);
+  }
+
+  len = strlen(text);
+  while ((node = advance_chain(&chain, &buf))) {
+    int is_done = (node->done > 0);
+    int not_postponed = (node->arrived != POSTPONED_TIME);
+    if (!is_done && (not_postponed ^ want_postponed_entries) && !strncmp(text, buf, len)) {
+      return buf;
+    } else {
+      /* Avoid gross memory leak */
+      free(buf);
+    }
+  }
+  return NULL;
+}
+/*}}}*/
+
+char **complete_help(char *text, int index)/*{{{*/
+{
+  char **matches;
+  matches = COMPLETION_MATCHES(text, generate_a_command_completion);
+  return matches;
+}
+/*}}}*/
+char **default_completer(char *text, int index)/*{{{*/
+{
+  if (cmds[index].synopsis) {
+    fprintf(stderr, "\n%s %s\n", cmds[index].name, cmds[index].synopsis);
+    rl_on_new_line();
+  }
+  return NULL;
+}
+/*}}}*/
+char **complete_list(char *text, int index)/*{{{*/
+{
+  char **matches;
+  if (text[0] && isalpha(text[0])) {
+    /* Try to complete priority */
+    matches = COMPLETION_MATCHES(text, generate_a_priority_completion);
+    return matches;
+  } else {
+    return default_completer(text, index);
+  }
+}
+/*}}}*/
+char **complete_priority(char *text, int index)/*{{{*/
+{
+  return complete_list(text, index);
+}
+/*}}}*/
+char **complete_postpone(char *text, int index)/*{{{*/
+{
+  char **matches;
+  want_postponed_entries = 0;
+  matches = COMPLETION_MATCHES(text, generate_a_postpone_completion);
+  return matches;
+}
+/*}}}*/
+char **complete_open(char *text, int index)/*{{{*/
+{
+  char **matches;
+  want_postponed_entries = 1;
+  matches = COMPLETION_MATCHES(text, generate_a_postpone_completion);
+  return matches;
+}
+/*}}}*/
+char **complete_done(char *text, int index)/*{{{*/
+{
+  char **matches;
+  matches = COMPLETION_MATCHES(text, generate_a_done_completion);
+  return matches;
+}
+/*}}}*/
+
+static char **tdl_completion(char *text, int start, int end)/*{{{*/
+{
+  char **matches = NULL;
+  if (start == 0) {
+    matches = COMPLETION_MATCHES(text, generate_a_command_completion);
+  } else {
+    int i;
+  
+    for (i=0; i<n_cmds; i++) {
+      if (!strncmp(rl_line_buffer, cmds[i].name, cmds[i].matchlen)) {
+        if (cmds[i].completer) {
+          matches = (cmds[i].completer)(text, i);
+        } else {
+          matches = default_completer(text, i);
+        }
+        break;
+      }
+    }
+  }
+
+  return matches;
+}
+/*}}}*/
+static char **null_tdl_completion(char *text, int start, int end)/*{{{*/
+{
+  return NULL;
+}
+/*}}}*/
+#else
+char **complete_help(char *text, int index)/*{{{*/
+{
+  return NULL;
+}/*}}}*/
+char **complete_list(char *text, int index)/*{{{*/
+{
+  return NULL;
+}/*}}}*/
+char **complete_priority(char *text, int index)/*{{{*/
+{
+  return NULL;
+}/*}}}*/
+char **complete_postpone(char *text, int index)/*{{{*/
+{
+  return NULL;
+}
+/*}}}*/
+char **complete_open(char *text, int index)/*{{{*/
+{
+  return NULL;
+}
+/*}}}*/
+char **complete_done(char *text, int index)/*{{{*/
+{
+  return NULL;
+}
+/*}}}*/
+#endif
+
+static void add_null_arg(char ***av, int *max, int *n)/*{{{*/
+{
+  if (*max == *n) {
+    *max += 4;
+    *av = grow_array(char *, *max, *av);
+  }
+  (*av)[*n] = NULL;
+  ++*n;
+  return;
+}
+/*}}}*/
+static void add_arg(char ***av, int *max, int *n, char *p, int len)/*{{{*/
+{
+  char *u, *v;
+  int nn;
+  
+  if (*max == *n) {
+    *max += 4;
+    *av = grow_array(char *, *max, *av);
+  }
+  
+  (*av)[*n] = new_array(char, len + 1);
+  /* Make copy of string, rejecting any escape characters (backslashes) */
+  for (u=p, v=(*av)[*n], nn=len; nn>0; ) {
+    if (*u == '\\') {
+      u++, nn--;
+      switch(*u) {
+        case 'n':
+          *v++ = '\n';
+          u++, nn--;
+          break;
+        case 't':
+          *v++ = '\t';
+          u++, nn--;
+          break;
+        default:
+          *v++ = *u++;
+          nn--;
+          break;
+      }
+    } else {
+      *v++ = *u++;
+      nn--;
+    }
+  }
+  *v = 0;
+  ++*n;
+}
+/*}}}*/
+
+static char **argv = NULL;
+
+static void split_line_and_dispatch(char *line)/*{{{*/
+{
+  static int max = 0;
+  int i, n;
+  char *av0 = "tdl";
+  char *p, *q, t;
+
+  /* Poke in argv[0] */
+  n = 0;
+  add_arg(&argv, &max, &n, av0, strlen(av0));
+
+  /* Now, split the line and add each argument */
+  p = line;
+  for (;;) {
+    while (*p && isspace(*p)) p++;
+    if (!*p) break;
+
+    /* p points to start of argument. */
+    if (*p == '\'' || *p == '"') {
+      t = *p;
+      p++;
+      /* Scan for matching terminator */
+      q = p + 1;
+      while (*q && (*q != t)) {
+        if (*q == '\\') q++; /* Escape following character */
+        if (*q) q++; /* Bogus backslash at end of line */
+      }
+    } else {
+      /* Single word arg */
+      q = p + 1;
+      while (*q && !isspace(*q)) q++;
+    }
+    add_arg(&argv, &max, &n, p, q - p);
+    if (!*q) break;
+    p = q + 1;
+  }
+  
+  add_null_arg(&argv, &max, &n);
+#ifdef TEST
+  /* For now, print arg list for debugging */
+  {
+    int i;
+    for (i=0; argv[i]; i++) {
+      printf("arg %d : <%s>\n", i, argv[i]);
+    }
+    fflush(stdout);
+  }
+#else
+  /* Now dispatch */
+  dispatch(argv);
+#endif
+
+  /* Now free arguments */
+  for (i=0; argv[i]; i++) {
+    free(argv[i]);
+  }
+  
+  return;
+}
+/*}}}*/
+static int is_line_blank(char *line)/*{{{*/
+{
+  char *p;
+  for (p=line; *p; p++) {
+    if (!isspace(*p)) return 0;
+  }
+  return 1;
+}
+/*}}}*/
+static char *make_prompt(void)/*{{{*/
+{
+  char *narrow_prefix = get_narrow_prefix();
+  char *result;
+  if (narrow_prefix) {
+    int length;
+    length = strlen(narrow_prefix) + 8;
+    result = new_array(char, length);
+    strcpy(result, "tdl[");
+    strcat(result, narrow_prefix);
+    strcat(result, "]> ");
+  } else {
+    result = new_string("tdl> ");
+  }
+  return result;
+}
+/*}}}*/
+#ifdef USE_READLINE
+static void interactive_readline(void)/*{{{*/
+{
+  char *cmd;
+  int had_char;
+  
+  do {
+    char *prompt = make_prompt();
+    cmd = readline(prompt);
+    free(prompt);
+
+    /* At end of file (e.g. input is a script, or user hits ^D, or stdin (file 
+       #0) is closed by the signal handler) */
+    if (!cmd) return;
+    
+    /* Check if line is blank */
+    had_char = !is_line_blank(cmd);
+
+    if (had_char) {
+      add_history(cmd);
+      split_line_and_dispatch(cmd);
+      free(cmd);
+    }
+  } while (1);
+
+  return;
+      
+}
+/*}}}*/
+
+static char *readline_initval = NULL;
+static int setup_initval_hook(void)/*{{{*/
+{
+  if (readline_initval) {
+    rl_insert_text(readline_initval);
+    rl_redisplay();
+  }
+  return 0;
+}
+/*}}}*/
+static char *interactive_text_readline(char *prompt, char *initval, int *is_blank, int *error)/*{{{*/
+{
+  char *line;
+  Function *old_rl_pre_input_hook = NULL;
+  
+  *error = 0;
+  old_rl_pre_input_hook = rl_pre_input_hook;
+  if (initval) {
+    readline_initval = initval;
+    rl_pre_input_hook = setup_initval_hook;
+  }
+  line = readline(prompt);
+  rl_pre_input_hook = old_rl_pre_input_hook;
+  readline_initval = NULL;
+  *is_blank = line ? is_line_blank(line) : 1;
+  if (line && !*is_blank) add_history(line);
+  return line;
+}
+/*}}}*/
+#endif
+static char *get_line_stdio(char *prompt, int *at_eof)/*{{{*/
+{
+  int n, max = 0;
+  char *line = NULL;
+  int c;
+  
+  *at_eof = 0;
+  printf("%s",prompt);
+  fflush(stdout);
+  n = 0;
+  do {
+    c = getchar();
+    if (c == EOF) {
+      *at_eof = 1;
+      return NULL;
+    } else {
+      if (n == max) {
+        max += 1024;
+        line = grow_array(char, max, line);
+      }
+      if (c != '\n') {
+        line[n++] = c;
+      } else {
+        line[n++] = 0;
+        return line;
+      }
+    }
+  } while (c != '\n');
+  assert(0);
+}
+/*}}}*/
+static void interactive_stdio(void)/*{{{*/
+{
+  char *line = NULL;
+  int at_eof;
+  
+  do {
+    line = get_line_stdio("tdl> ", &at_eof);
+    if (!at_eof) {
+      split_line_and_dispatch(line);
+      free(line);
+    }
+  } while (!at_eof);
+}
+/*}}}*/
+static char *interactive_text_stdio(char *prompt, char *initval, int *is_blank, int *error)/*{{{*/
+{
+  char *line;
+  int at_eof;
+  *error = 0;
+  line = get_line_stdio(prompt, &at_eof);
+  *is_blank = line ? is_line_blank(line) : 0;
+  return line;
+}
+/*}}}*/
+char *interactive_text (char *prompt, char *initval, int *is_blank, int *error)/*{{{*/
+{
+#ifdef USE_READLINE
+  if (isatty(0)) {
+    char *result;
+    rl_attempted_completion_function = (CPPFunction *) null_tdl_completion;
+    result = interactive_text_readline(prompt, initval, is_blank, error);
+    rl_attempted_completion_function = (CPPFunction *) tdl_completion;
+    return result;
+  } else {
+    /* In case someone wants to drive tdl from a script, by redirecting stdin to it. */
+    return interactive_text_stdio(prompt, initval, is_blank, error);
+  }
+#else
+  return interactive_text_stdio(prompt, initval, is_blank, error);
+#endif
+}
+/*}}}*/
+
+void interactive(void)/*{{{*/
+{
+  /* Main interactive function */
+#ifdef USE_READLINE
+  if (isatty(0)) {
+    rl_completion_entry_function = NULL;
+    rl_attempted_completion_function = (CPPFunction *) tdl_completion;
+    interactive_readline();
+  } else {
+    /* In case someone wants to drive tdl from a script, by redirecting stdin to it. */
+    interactive_stdio();
+  }
+#else
+  interactive_stdio();
+#endif
+  if (argv) free(argv);
+  return;
+}
+
+/*}}}*/
+
+/*{{{  Main routine [for testing]*/
+#ifdef TEST
+int main (int argc, char **argv) {
+  interactive();
+  return 0;
+}
+#endif
+/*}}}*/
diff --git a/io.c b/io.c
new file mode 100644 (file)
index 0000000..ee1b2cd
--- /dev/null
+++ b/io.c
@@ -0,0 +1,167 @@
+/*
+   $Header: /cvs/src/tdl/io.c,v 1.5.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <string.h>
+#include "tdl.h"
+
+#define MAGIC1 0x99bb0001L
+
+static unsigned int count_length(struct links *x)/*{{{*/
+{
+  int n = 0;
+  struct node *y;
+  for (y = x->next; &y->chain != x; y = y->chain.next) n++;
+  return n;
+}
+/*}}}*/
+static unsigned int count_kids(struct node *x)/*{{{*/
+{
+  return count_length(&x->kids);
+}
+/*}}}*/
+static void write_int(unsigned int n, FILE *out)/*{{{*/
+{
+  unsigned int a, b, c, d;
+  a = (n >> 24) & 255;
+  b = (n >> 16) & 255;
+  c = (n >>  8) & 255;
+  d = (n      ) & 255;
+  fputc(a, out);
+  fputc(b, out);
+  fputc(c, out);
+  fputc(d, out);
+}
+/*}}}*/
+/*{{{  static unsigned int read_int(FILE *in)*/
+static unsigned int read_int(FILE *in)
+{
+  
+  unsigned int a, b, c, d;
+  a = fgetc(in);
+  b = fgetc(in);
+  c = fgetc(in);
+  d = fgetc(in);
+  return ((a & 0xff) << 24) + ((b & 0xff) << 16) +
+         ((c & 0xff) <<  8) +  (d & 0xff);
+}
+
+/*}}}*/
+static void write_node(struct node *x, FILE *out)/*{{{*/
+{
+  int len, n_kids;
+  struct node *kid;
+
+  len = strlen(x->text);
+  n_kids = count_kids(x);
+  
+  write_int(x->arrived, out);
+  write_int(x->required_by, out);
+  write_int(x->done, out);
+  fputc(x->priority, out);
+  write_int(len, out);
+  fwrite(x->text, 1, len, out);
+
+  write_int(n_kids, out);
+  for (kid = x->kids.next; kid != (struct node *) &x->kids; kid = kid->chain.next)
+  {
+    write_node(kid, out);
+  }
+}
+/*}}}*/
+void write_database(FILE *out, struct links *from)/*{{{*/
+{
+  int n_kids;
+  struct node *kid;
+
+  n_kids = count_length(from);
+  
+  write_int(MAGIC1, out);
+  write_int(n_kids, out);
+  for (kid = from->next; kid != (struct node *) from; kid = kid->chain.next)
+  {
+    write_node(kid, out);
+  }
+}
+/*}}}*/
+/*{{{  static struct node *read_node(FILE *in)*/
+static struct node *read_node(FILE *in)
+{
+  struct node *r = new_node();
+  int len, n_kids, i;
+  
+  r->arrived     = read_int(in);
+  r->required_by = read_int(in);
+  r->done        = read_int(in);
+  r->priority    = fgetc(in);
+  if ((r->priority < PRI_UNKNOWN) || (r->priority > PRI_URGENT)) {
+    r->priority = PRI_NORMAL;
+  }
+  len            = read_int(in);
+  r->text        = new_array(char, len+1);
+  fread(r->text, 1, len, in);
+  r->text[len] = 0;
+  
+  n_kids         = read_int(in);
+
+  for (i=0; i<n_kids; i++) {
+    struct node *nn;
+    nn = read_node(in);
+    prepend_child(nn, r);
+  }
+
+  return r;
+}
+
+/*}}}*/
+/*{{{  void read_database(FILE *in)*/
+int read_database(FILE *in, struct links *to)
+{
+  int n_kids, i;
+  unsigned int magic;
+
+  /* FIXME : Ought to clear any existing database.  For initial version this
+   * won't happen (only one load per invocation), and even later, we might just
+   * take the memory leak. But for safety, ... */
+
+  to->prev = (struct node *) to;
+  to->next = (struct node *) to;
+  
+  if (feof(in)) {
+    fprintf(stderr, "Can't read anything from database\n");
+    return -1;
+  }
+
+  magic = read_int(in);
+  if (magic != MAGIC1) {
+    fprintf(stderr, "Cannot parse database, wrong magic number\n");
+    return -1;
+  }
+  n_kids = read_int(in);
+  for (i=0; i<n_kids; i++) {
+    struct node *nn;
+    nn = read_node(in);
+    prepend_node(nn, to);
+  }
+
+  return 0;
+}
+
+/*}}}*/
diff --git a/list.c b/list.c
new file mode 100644 (file)
index 0000000..a26a3c7
--- /dev/null
+++ b/list.c
@@ -0,0 +1,594 @@
+/*
+   $Header: /cvs/src/tdl/list.c,v 1.20.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001,2002,2003,2004,2005  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <time.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <unistd.h>
+#include "tdl.h"
+
+struct list_options {
+  unsigned monochrome:1;
+  unsigned show_all:1;
+  unsigned show_postponed:1;
+  unsigned verbose:1;
+  unsigned set_depth:1;
+  int depth;
+};
+
+#define INDENT_TAB 3
+
+/*{{{ Colour definitions */
+#define RED     "\e[31m\e[1m"
+#define GREEN   "\e[32m"
+#define YELLOW  "\e[33m\e[1m"
+#define BLUE    "\e[34m"
+#define MAGENTA "\e[35m"
+#define CYAN    "\e[36m"
+#define NORMAL  "\e[0m"
+#define DIM     "\e[37m\e[2m"
+#define DIMCYAN "\e[36m\e[2m"
+
+/* Table to map priority levels to colours */
+static char *colour_table[] = {
+  NORMAL, BLUE, CYAN, NORMAL, YELLOW, RED
+};
+
+static char *priority_text[] = {
+  "UNKNOWN!", "verylow", "low", "normal", "high", "urgent"
+};
+
+/*}}}*/
+void do_indent(int indent)/*{{{*/
+{
+  int i;
+  for (i=0; i<indent; i++) putchar(' ');
+}
+/*}}}*/
+void do_bullet_indent(int indent)/*{{{*/
+{
+  int i;
+  int n;
+  n = indent - 2;
+  for (i=0; i<indent; i++) putchar((i == n) ? '-' : ' ');
+}
+/*}}}*/
+static void print_timestamp(int timestamp, char *leader, int indent, int monochrome)/*{{{*/
+{
+  char buffer[32];
+  time_t now, timestamp2;
+  long diff, days_ago, days_ahead;
+  
+  now = time(NULL);
+  diff = now - timestamp;
+  days_ago = (diff + ((diff > 0) ? 43200 : -43200)) / 86400;
+  timestamp2 = (time_t) timestamp;
+  strftime(buffer, sizeof(buffer), "%a %d %b %Y %H:%M", 
+           localtime(&timestamp2));
+  do_indent(indent+2);
+  if (days_ago < 0) {
+    days_ahead = - days_ago;
+    if (monochrome) {
+      printf("%s: %s (%ld day%s ahead)\n", leader, buffer, days_ago, (days_ahead == 1) ? "" : "s");
+    } else {
+      printf("%s%s:%s %s %s(%ld day%s ahead)%s\n", GREEN, leader, NORMAL, buffer, MAGENTA, days_ahead, (days_ahead == 1) ? "" : "s", NORMAL);
+    }
+  } else {
+    if (monochrome) {
+      printf("%s: %s (%ld day%s ago)\n", leader, buffer, days_ago, (days_ago == 1) ? "" : "s");
+    } else {
+      printf("%s%s:%s %s (%ld day%s ago)\n", GREEN, leader, NORMAL, buffer, days_ago, (days_ago == 1) ? "" : "s");
+    }
+  }
+}
+/*}}}*/
+static void count_kids(struct links *x, int *n_kids, int *n_done_kids, int *n_open_kids)/*{{{*/
+{
+  int nk, ndk;
+  struct node *y;
+
+  nk = ndk = 0;
+  for (y = x->next;
+       y != (struct node *) x;
+       y = y->chain.next) {
+    if (y->done) ndk++;
+    nk++;
+  }
+
+  if (n_kids) *n_kids = nk;
+  if (n_done_kids) *n_done_kids = ndk;
+  if (n_open_kids) *n_open_kids = (nk - ndk);
+  return;
+}
+/*}}}*/
+static void print_details(struct node *y, int indent, int summarise_kids, const struct list_options *options, char *index_buffer, time_t now)/*{{{*/
+{
+  int is_done;
+  int is_ignored;
+  int is_postponed;
+  int is_deferred;
+  char *p;
+  int n_kids, n_open_kids;
+  int show_state;
+  char *narrow_prefix;
+  int index_buffer_len;
+
+  is_done = (y->done > 0);
+  is_ignored = (y->done == IGNORED_TIME);
+  is_postponed = (y->arrived == POSTPONED_TIME);
+  is_deferred = (y->arrived > now);
+  if (!options->show_all && is_done) return;
+
+  do_indent(indent);
+  count_kids(&y->kids, &n_kids, NULL, &n_open_kids);
+  show_state = options->show_all || options->show_postponed;
+  index_buffer_len = strlen(index_buffer);
+  narrow_prefix = get_narrow_prefix();
+
+  if (narrow_prefix) {
+    if (options->monochrome) printf("%s%s", narrow_prefix, index_buffer_len ? "." : "");
+    else                     printf("%s%s%s%s", BLUE, narrow_prefix, index_buffer_len ? "." : "", NORMAL);
+  }
+  
+  if (options->monochrome) printf("%s", index_buffer);
+  else                     printf("%s%s%s", GREEN, index_buffer, NORMAL);
+
+  if (summarise_kids && (n_kids > 0)) {
+    if (options->monochrome) printf(" [%d/%d]", n_open_kids, n_kids);
+    else                     printf(" %s[%d/%d]%s", CYAN, n_open_kids, n_kids, NORMAL);
+  }
+
+  if (show_state && !options->verbose) {
+    if (is_ignored) {
+      if (options->monochrome) printf(" (IGNORED)");
+      else                     printf(" %s(IGNORED)%s", BLUE, NORMAL);
+    } else if (is_done) {
+      if (options->monochrome) printf(" (DONE)");
+      else                     printf(" %s(DONE)%s", CYAN, NORMAL);
+    } else if (is_postponed) {
+      if (options->monochrome) printf(" (POSTPONED)");
+      else                     printf(" %s(POSTPONED)%s", MAGENTA, NORMAL);
+    } else if (is_deferred) {
+      if (options->monochrome) printf(" (DEFERRED)");
+      else                     printf(" %s(DEFERRED)%s", MAGENTA, NORMAL);
+    }
+    printf(" : ");
+  } else {
+    printf(" ");
+  }
+
+  if (!options->monochrome) printf("%s", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
+
+#if 0
+  
+  if (summarise_kids && (n_kids > 0)) {
+    if (options->monochrome) {
+      printf("%s [%d/%d] %s", index_buffer, n_open_kids, n_kids,
+             (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " : 
+             (show_state && !options->verbose && is_done) ? "(DONE) : " : 
+             (show_state && !options->verbose && is_done) ? "(DONE) : " : 
+             (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": ");
+    } else {
+      printf("%s%s %s[%d/%d]%s %s%s", GREEN, index_buffer, CYAN, n_open_kids, n_kids, NORMAL,
+             (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL :
+             (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL :
+             (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " :
+             (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "",
+             is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
+    }
+  } else {
+    if (options->monochrome) {
+      printf("%s %s", index_buffer,
+             (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " : 
+             (show_state && !options->verbose && is_done) ? "(DONE) : " : 
+             (show_state && !options->verbose && is_postponed) ? "(POSTPONED) : " :
+             (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": ");
+    } else {
+      printf("%s%s%s %s%s", GREEN, index_buffer, NORMAL,
+             (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL :
+             (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL :
+             (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " :
+             (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "",
+             is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
+    }
+  }
+#endif
+  for (p = y->text; *p; p++) {
+    putchar(*p);
+    if (*p == '\n') {
+      do_indent(indent + 5);
+    }
+  }
+  if (!options->monochrome) printf("%s", NORMAL);
+  printf("\n");
+
+  if (options->verbose) {
+    print_timestamp(y->arrived, "Arrived", indent, options->monochrome);
+    do_indent(indent + 2);
+    if (options->monochrome) {
+      printf("Priority: %s\n", priority_text[y->priority]);
+    } else {
+      printf("%sPriority: %s%s%s\n",
+          GREEN, colour_table[y->priority], priority_text[y->priority], NORMAL);
+    }
+    if (y->required_by > 0) print_timestamp(y->required_by, "Required by", indent, options->monochrome);
+    if (y->done > 0) print_timestamp(y->done, "Completed", indent, options->monochrome);
+    printf("\n");
+  }
+
+}
+/*}}}*/
+static void list_chain(struct links *x, int indent, int depth, const struct list_options *options, char *index_buffer, enum Priority prio, time_t now, unsigned char *hits)/*{{{*/
+{
+  struct node *y;
+  int idx, is_done, is_deferred, is_postponed;
+  int show_node;
+  char component_buffer[8];
+  char new_index_buffer[64];
+  
+  for (y = x->next, idx = 1;
+       y != (struct node *) x;
+       y = y->chain.next, ++idx) {
+    
+    is_done = (y->done > 0);
+    is_postponed = (y->arrived == POSTPONED_TIME);
+    is_deferred = (y->arrived > now);
+    show_node = options->show_all 
+             || (options->show_postponed && !is_done)
+             || (!is_deferred && !is_postponed);
+    if (!show_node) continue;
+
+    sprintf(component_buffer, "%d", idx);
+    strcpy(new_index_buffer, index_buffer);
+    if (strlen(new_index_buffer) > 0) {
+      strcat(new_index_buffer, ".");
+    }
+    strcat(new_index_buffer, component_buffer);
+
+    if (y->priority >= prio) {
+      int summarise_kids = (options->set_depth && (options->depth == depth));
+      if (hits[y->iscratch]) {
+        print_details(y, indent, summarise_kids, options, new_index_buffer, now);
+      }
+    }
+
+    /* Maybe list children regardless of priority assigned to parent. */
+    if (!options->set_depth || (depth < options->depth)) {
+      list_chain(&y->kids, indent + INDENT_TAB, depth + 1, options, new_index_buffer, prio, now, hits);
+    }
+
+  }
+  return;
+}
+/*}}}*/
+static void allocate_indices(struct links *x, int *idx)/*{{{*/
+{
+  struct node *y;
+  for (y = x->next;
+       y != (struct node *) x;
+       y = y->chain.next) {
+
+    y->iscratch = *idx;
+    ++*idx;
+    allocate_indices(&y->kids, idx);
+  }
+}
+/*}}}*/
+static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
+{
+  struct node *y;
+  char *p;
+  char *token;
+  int got_hit;
+  unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
+
+  for (y = x->next;
+       y != (struct node *) x;
+       y = y->chain.next) {
+
+    token = y->text;
+
+    switch (max_errors) {
+      /* optimise common cases for few errors to allow optimizer to keep bitmaps
+       * in registers */
+      case 0:/*{{{*/
+        r0 = ~0;
+        got_hit = 0;
+        for(p=token; *p; p++) {
+          int idx = (unsigned int) *(unsigned char *)p;
+          r0 = (r0<<1) | vecs[idx];
+          if (~(r0 | hitvec)) {
+            got_hit = 1;
+            break;
+          }
+        }
+        break;
+        /*}}}*/
+      case 1:/*{{{*/
+        r0 = ~0;
+        r1 = r0<<1;
+        got_hit = 0;
+        for(p=token; *p; p++) {
+          int idx = (unsigned int) *(unsigned char *)p;
+          nr0 = (r0<<1) | vecs[idx];
+          r1  = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
+          r0  = nr0;
+          if (~((r0 & r1) | hitvec)) {
+            got_hit = 1;
+            break;
+          }
+        }
+        break;
+  /*}}}*/
+      case 2:/*{{{*/
+        r0 = ~0;
+        r1 = r0<<1;
+        r2 = r1<<1;
+        got_hit = 0;
+        for(p=token; *p; p++) {
+          int idx = (unsigned int) *(unsigned char *)p;
+          nr0 =  (r0<<1) | vecs[idx];
+          nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
+          r2  = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
+          r0  = nr0;
+          r1  = nr1;
+          if (~((r0 & r1& r2) | hitvec)) {
+            got_hit = 1;
+            break;
+          }
+        }
+        break;
+  /*}}}*/
+      case 3:/*{{{*/
+        r0 = ~0;
+        r1 = r0<<1;
+        r2 = r1<<1;
+        r3 = r2<<1;
+        got_hit = 0;
+        for(p=token; *p; p++) {
+          int idx = (unsigned int) *(unsigned char *)p;
+          nr0 =  (r0<<1) | vecs[idx];
+          nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
+          nr2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
+          r3  = ((r3<<1) | vecs[idx]) & ((r2 & nr2) << 1) & r2;
+          r0  = nr0;
+          r1  = nr1;
+          r2  = nr2;
+          if (~((r0 & r1 & r2 & r3) | hitvec)) {
+            got_hit = 1;
+            break;
+          }
+        }
+        break;
+        /*}}}*/
+      default:
+        assert(0); /* not allowed */
+        break;
+    }
+    if (got_hit) {
+      hits[y->iscratch] = 1;
+    }
+    search_node(&y->kids, max_errors, vecs, hitvec, hits);
+  }
+}
+/*}}}*/
+static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
+{
+  /* See "Fast text searching with errors, Sun Wu and Udi Manber, TR 91-11,
+     University of Arizona.  I have been informed that this algorithm is NOT
+     patented.  This implementation of it is entirely the work of Richard P.
+     Curnow - I haven't looked at any related source (webglimpse, agrep etc) in
+     writing this.
+  */
+
+  int max_errors;
+  char *slash;
+  char *substring;
+  unsigned long a[256];
+  unsigned long hit;
+  int len, i;
+  char *p;
+  unsigned char *hit0;
+
+  slash = strchr(cond, '/');
+  if (!slash) {
+    max_errors = 0;
+    substring = cond;
+  } else {
+    substring = new_string(cond);
+    substring[slash-cond] = '\0';
+    max_errors = atoi(slash+1);
+    if (max_errors > 3) {
+      fprintf(stderr, "Can only match with up to 3 errors, ignoring patterh <%s>\n", cond);
+      goto get_out;
+    }
+  }
+
+  len = strlen(substring);
+  if (len < 1 || len > 31) {
+    fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
+    goto get_out;
+  }
+  
+  /* Set array 'a' to all -1 values */
+  memset(a, 0xff, 256 * sizeof(unsigned long));
+  for (p=substring, i=0; *p; p++, i++) {
+    unsigned char pc;
+    pc = *(unsigned char *) p;
+    a[(unsigned int) pc] &= ~(1UL << i);
+    /* Make search case insensitive */
+    if (isupper(pc)) {
+      a[tolower((unsigned int) pc)] &= ~(1UL << i);
+    }
+    if (islower(pc)) {
+      a[toupper((unsigned int) pc)] &= ~(1UL << i);
+    }
+  }
+  hit = ~(1UL << (len-1));
+
+  hit0 = new_array(unsigned char, n_nodes);
+  memset(hit0, 0, n_nodes);
+  
+  /* Now scan each node against this match criterion */
+  search_node(&top, max_errors, a, hit, hit0);
+  for (i=0; i<n_nodes; i++) {
+    hits[i] &= hit0[i];
+  }
+  free(hit0);
+
+get_out:
+  if (substring != cond) {
+    free(substring);
+  }
+  return;
+}
+/*}}}*/
+int process_list(char **x)/*{{{*/
+{
+  struct list_options options;
+  int options_done = 0;
+  int any_paths = 0;
+  char index_buffer[256];
+  char *y;
+  enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
+  int prio_set = 0;
+  time_t now = time(NULL);
+  
+  unsigned char *hits;
+  int node_index, n_nodes;
+
+  options.monochrome = 0;
+  options.show_all = 0;
+  options.show_postponed = 0;
+  options.verbose = 0;
+  options.set_depth = 0;
+
+  if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
+       (isatty(STDOUT_FILENO) == 0) ) {
+    options.monochrome = 1;
+  }
+  
+  /* Initialisation to support searching */
+  node_index = 0;
+  allocate_indices(&top, &node_index);
+  n_nodes = node_index;
+
+  hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
+
+  /* all nodes match until proven otherwise */
+  memset(hits, 1, n_nodes);
+  
+  while ((y = *x) != 0) {
+    /* An argument starting '1' or '+1' or '+-1' (or '-1' after '--') is
+     * treated as the path of the top node to show */
+    if (isdigit(y[0]) ||
+        (y[0] == '.') ||
+        (options_done && (y[0] == '-') && isdigit(y[1])) ||
+        ((y[0] == '+') &&
+         (isdigit(y[1]) ||
+          ((y[1] == '-' && isdigit(y[2])))))) {
+      
+      struct node *n = lookup_node(y, 0, NULL);
+      int summarise_kids;
+
+      if (!n) return -1;
+      
+      any_paths = 1;
+      index_buffer[0] = '\0';
+      strcat(index_buffer, y);
+      summarise_kids = (options.set_depth && (options.depth==0));
+      if (hits[n->iscratch]) {
+        print_details(n, 0, summarise_kids, &options, index_buffer, now);
+      }
+      if (!options.set_depth || (options.depth > 0)) {
+        node_prio = n->priority;
+
+        /* If the priority has been set on the cmd line, always use that.
+         * Otherwise, use the priority from the specified node, _except_ when
+         * that is higher than normal, in which case use normal. */
+        prio_to_use = (prio_set) ? prio : ((node_prio > prio) ? prio : node_prio);
+        list_chain(&n->kids, INDENT_TAB, 0, &options, index_buffer, prio_to_use, now, hits);
+      }
+    } else if ((y[0] == '-') && (y[1] == '-')) {
+      options_done = 1;
+    } else if (y[0] == '-') {
+      while (*++y) {
+        switch (*y) {
+          case 'v':
+            options.verbose = 1;
+            break;
+          case 'a':
+            options.show_all = 1;
+            break;
+          case 'm':
+            options.monochrome = 1;
+            break;
+          case 'p':
+            options.show_postponed = 1;
+            break;
+          case '1': case '2': case '3':
+          case '4': case '5': case '6': 
+          case '7': case '8': case '9':
+            options.set_depth = 1;
+            options.depth = (*y) - '1';
+            break;
+          default:
+            fprintf(stderr, "Unrecognized option : -%c\n", *y);
+            break;
+        }
+      }
+    } else if (y[0] == '/') {
+      /* search expression */
+      merge_search_condition(hits, n_nodes, y+1);
+       
+    } else {
+      int error;
+      prio = parse_priority(y, &error);
+      if (error < 0) return error;
+      prio_set = 1;
+    }
+
+    x++;
+  }
+  
+  if (!any_paths) {
+    struct node *narrow_top = get_narrow_top();
+    if (narrow_top) {
+      index_buffer[0] = 0;
+      if (hits[narrow_top->iscratch]) {
+        int summarise_kids = (options.set_depth && (options.depth==0));
+        print_details(narrow_top, 0, summarise_kids, &options, index_buffer, now);
+      }
+      if (!options.set_depth || (options.depth > 0)) {
+        list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
+      }
+    } else {
+      index_buffer[0] = 0;
+      list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
+    }
+  }
+
+  if (hits) free(hits);
+  
+  return 0;
+}
+/*}}}*/
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..0590466
--- /dev/null
+++ b/main.c
@@ -0,0 +1,912 @@
+/*
+   $Header: /cvs/src/tdl/main.c,v 1.39.2.6 2004/02/03 22:17:22 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001,2002,2003,2004,2005  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include "tdl.h"
+#include "version.h"
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef USE_DOTLOCK
+#include <sys/utsname.h>
+#include <pwd.h>
+#endif
+
+/* The name of the database file (in whichever directory it may be) */
+#define DBNAME ".tdldb"
+
+/* Set if db doesn't exist in this directory */
+static char *current_database_path = NULL;
+
+/* The currently loaded database */
+struct links top;
+
+/* Flag for whether data is actually loaded yet */
+static int is_loaded = 0;
+
+#ifdef USE_DOTLOCK
+static char *lock_file_name = NULL;
+#endif
+
+/* Flag if currently loaded database has been changed and needs writing back to
+ * the filesystem */
+static int currently_dirty = 0;
+
+/* Flag indicating whether to load databases read only */
+static int read_only = 0;
+
+/* Whether to complain about problems with file operations */
+static int is_noisy = 1;
+
+/* Whether to forcibly unlock the database before the next lock attempt (e.g.
+ * if the program crashed for some reason before.) */
+static int forced_unlock = 0;
+
+static int is_interactive = 0;
+
+static void set_descendent_priority(struct node *x, enum Priority priority)/*{{{*/
+{
+  struct node *y;
+  for (y = x->kids.next; y != (struct node *) &x->kids; y = y->chain.next) {
+    y->priority = priority;
+    set_descendent_priority(y, priority);
+  }
+}
+/*}}}*/
+
+/* This will be variable eventually */
+static char default_database_path[] = "./" DBNAME;
+
+#ifdef USE_DOTLOCK
+static void unlock_database(void)/*{{{*/
+{
+  if (lock_file_name) unlink(lock_file_name);
+  return;
+}
+/*}}}*/
+static volatile void unlock_and_exit(int code)/*{{{*/
+{
+  unlock_database();
+  exit(code);
+}
+/*}}}*/
+static void lock_database(char *path)/*{{{*/
+{
+  struct utsname uu;
+  struct passwd *pw;
+  int pid;
+  int len;
+  char *tname;
+  struct stat sb;
+  FILE *out;
+  
+  if (uname(&uu) < 0) {
+    perror("uname");
+    exit(1);
+  }
+  pw = getpwuid(getuid());
+  if (!pw) {
+    perror("getpwuid");
+    exit(1);
+  }
+  pid = getpid();
+  len = 1 + strlen(path) + 5;
+  lock_file_name = new_array(char, len);
+  sprintf(lock_file_name, "%s.lock", path);
+
+  if (forced_unlock) {
+    unlock_database();
+    forced_unlock = 0;
+  }
+  
+  len += strlen(uu.nodename);
+  /* add on max width of pid field (allow up to 32 bit pid_t) + 2 '.' chars */
+  len += (10 + 2);
+  tname = new_array(char, len);
+  sprintf(tname, "%s.%d.%s", lock_file_name, pid, uu.nodename);
+  out = fopen(tname, "w");
+  if (!out) {
+    fprintf(stderr, "Cannot open lock file %s for writing\n", tname);
+    exit(1);
+  }
+  fprintf(out, "%d,%s,%s\n", pid, uu.nodename, pw->pw_name);
+  fclose(out);
+
+  if (link(tname, lock_file_name) < 0) {
+    /* check if link count==2 */
+    if (stat(tname, &sb) < 0) {
+      fprintf(stderr, "Could not stat the lock file\n");
+      unlink(tname);
+      exit(1);
+    } else {
+      if (sb.st_nlink != 2) {
+        FILE *in;
+        in = fopen(lock_file_name, "r");
+        if (in) {
+          char line[2048];
+          fgets(line, sizeof(line), in);
+          line[strlen(line)-1] = 0; /* strip trailing newline */
+          fprintf(stderr, "Database %s appears to be locked by (pid,node,user)=(%s)\n", path, line);
+          unlink(tname);
+          exit(1);
+        }
+      } else {
+        /* lock succeeded apparently */
+      }
+    }
+  } else {
+    /* lock succeeded apparently */
+  }
+  unlink(tname);
+  free(tname);
+  return;
+}
+/*}}}*/
+#else
+static volatile void unlock_and_exit(int code)/*{{{*/
+{
+  exit(code);
+}
+/*}}}*/
+#endif /* USE_DOTLOCK */
+
+static char *get_database_path(int traverse_up)/*{{{*/
+{
+  char *env_var;
+  env_var = getenv("TDL_DATABASE");
+  if (env_var) {
+    return env_var;
+  } else {
+    int at_root, orig_size, size, dbname_len, found, stat_result;
+    char *orig_cwd, *cwd, *result, *filename;
+    struct stat statbuf;
+    
+    dbname_len = strlen(DBNAME);
+    size = 16;
+    orig_size = 16;
+    found = 0;
+    at_root = 0;
+    cwd = new_array(char, size);
+    orig_cwd = new_array(char, orig_size);
+    do {
+      result = getcwd(orig_cwd, orig_size);
+      if (!result) {
+        if (errno == ERANGE) {
+          orig_size <<= 1;
+          orig_cwd = grow_array(char, orig_size, orig_cwd);
+        } else {
+          fprintf(stderr, "Unexpected error reading current directory\n");
+          unlock_and_exit(1);
+        }
+      }
+    } while (!result);
+    filename = new_array(char, size + dbname_len + 2);
+    filename[0] = 0;
+    do {
+      result = getcwd(cwd, size);
+      if (!result && (errno == ERANGE)) {
+        size <<= 1;
+        cwd = grow_array(char, size, cwd);
+        filename = grow_array(char, size + dbname_len + 2, filename);
+      } else {
+        if (!strcmp(cwd, "/")) {
+          at_root = 1;
+        }
+        strcpy(filename, cwd);
+        strcat(filename, "/");
+        strcat(filename, DBNAME);
+        stat_result = stat(filename, &statbuf);
+        if ((stat_result >= 0) && (statbuf.st_mode & 0600)) {
+          found = 1;
+          break;
+        }
+
+        if (!traverse_up) break;
+        
+        /* Otherwise, go up a level */
+        chdir ("..");
+      }
+    } while (!at_root);
+
+    free(cwd);
+    
+    /* Reason for this : if using create in a subdirectory of a directory
+     * already containing a .tdldb, the cwd after the call here from main would
+     * get left pointing at the directory containing the .tdldb that already
+     * exists, making the call here from process_create() fail.  So go back to
+     * the directory where we started.  */
+    chdir(orig_cwd);
+    free(orig_cwd);
+
+    if (found) {
+      return filename;
+    } else {
+      return default_database_path;
+    }
+  }
+
+}
+/*}}}*/
+static void rename_database(char *path)/*{{{*/
+{
+  int len;
+  char *pathbak;
+
+  len = strlen(path);
+  pathbak = new_array(char, len + 5);
+  strcpy(pathbak, path);
+  strcat(pathbak, ".bak");
+  if (rename(path, pathbak) < 0) {
+    if (is_noisy) {
+      perror("warning, couldn't save backup database:");
+    }
+  }
+  free(pathbak);
+  return;
+} 
+/*}}}*/
+static char *executable_name(char *argv0)/*{{{*/
+{
+  char *p;
+  for (p=argv0; *p; p++) ;
+  for (; p>=argv0; p--) {
+    if (*p == '/') return (p+1);
+  }
+  return argv0;
+}
+/*}}}*/
+static void load_database(char *path) /*{{{*/
+  /* Return 1 if successful, 0 if no database was found */
+{
+  FILE *in;
+  currently_dirty = 0;
+#ifdef USE_DOTLOCK
+  if (!read_only) {
+    lock_database(path);
+  }
+#endif
+  in = fopen(path, "rb");
+  if (in) {
+    /* Database may not exist, e.g. if the program has never been run before.
+       */
+    read_database(in, &top);
+    fclose(in);
+    is_loaded = 1;
+  } else {
+    if (is_noisy) {
+      fprintf(stderr, "warning: no database found above this directory\n");
+    }
+  }
+}
+/*}}}*/
+void load_database_if_not_loaded(void)/*{{{*/
+{
+  if (!is_loaded) {
+    load_database(current_database_path);
+    is_loaded = 1;
+  }
+}
+/*}}}*/
+static mode_t get_mode(const char *path)/*{{{*/
+{
+  mode_t result;
+  const mode_t default_result = 0600; /* access to user only. */
+  struct stat sb;
+  if (stat(path, &sb) < 0) {
+    result = default_result;
+  } else {
+    if (!S_ISREG(sb.st_mode)) {
+      fprintf(stderr, "Warning : existing database is not a regular file!\n");
+      result = default_result;
+    } else {
+      result = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
+    }
+  }
+  return result;
+}
+/*}}}*/
+static void save_database(char *path)/*{{{*/
+{
+  FILE *out = NULL;
+  int out_fd;
+  mode_t database_mode;
+  if (read_only) {
+    fprintf(stderr, "Warning : database opened read-only. Not saving.\n");
+    return;
+  }
+  if (is_loaded && currently_dirty) {
+    database_mode = get_mode(path);
+    
+    /* The next line only used to happen if the command wasn't 'create'.
+     * However, it should quietly fail for create, where the existing database
+     * doesn't exist */
+    rename_database(path);
+
+    /* Open database this way so that the permissions from the existing
+       database can be duplicated onto the new one in way free of race
+       conditions. */
+    out_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, database_mode);
+    if (out_fd < 0) {
+      fprintf(stderr, "Could not open new database %s for writing : %s\n",
+              path, strerror(errno));
+      unlock_and_exit(1);
+    } else {
+      /* Normal case */
+      out = fdopen(out_fd, "wb");
+    }
+    if (!out) {
+      fprintf(stderr, "Cannot open database %s for writing\n", path);
+      unlock_and_exit(1);
+    }
+    write_database(out, &top);
+    fclose(out);
+  }
+  currently_dirty = 0;
+  return;
+}
+/*}}}*/
+void free_database(struct links *x)/*{{{*/
+{
+  struct node *y;
+  struct node *next;
+  for (y=x->next; y != (struct node *) x; y = next) {
+    free_database(&y->kids);
+    free(y->text);
+    next = y->chain.next;
+    free(y);
+  }
+  x->next = x->prev = (struct node *) x;
+}
+/*}}}*/
+
+/* {{{ One line descriptions of the subcommands */
+static char desc_above[] = "Move entries above (before) another entry";
+static char desc_add[] = "Add a new entry to the database";
+static char desc_after[] = "Move entries after (below) another entry";
+static char desc_before[] = "Move entries before (above) another entry";
+static char desc_below[] = "Move entries below (after) another entry";
+static char desc_clone[] = "Make deep copy of one or more entries";
+static char desc_copyto[] = "Insert deep copy of one or more entries under another entry";
+static char desc_create[] = "Create a new database in the current directory";
+static char desc_defer[] = "Put off starting some tasks until a given time";
+static char desc_delete[] = "Remove 1 or more entries from the database";
+static char desc_done[] = "Mark 1 or more entries as done";
+static char desc_edit[] = "Change the text of an entry";
+static char desc_exit[] = "Exit program, saving database";
+static char desc_export[] = "Export entries to another database";
+static char desc_help[] = "Display help information";
+static char desc_import[] = "Import entries from another database";
+static char desc_ignore[] = "Postpone or partially remove 1 or more entries";
+static char desc_into[] = "Move entries to end of new parent";
+static char desc_list[] = "List entries in database (default from top node)";
+static char desc_log[] = "Add a new entry to the database, mark it done as well";
+static char desc_moveto[] = "Move entries to end of new parent";
+static char desc_narrow[] = "Restrict actions to part of the database";
+static char desc_open[] = "Move one or more entries out of postponed/deferred state";
+static char desc_postpone[] = "Make one or more entries postponed indefinitely";
+static char desc_priority[] = "Change the priority of 1 or more entries";
+static char desc_purge[] = "Remove old done entries in subtrees";
+static char desc_quit[] = "Exit program, NOT saving database";
+static char desc_remove[] = "Remove 1 or more entries from the database";
+static char desc_report[] = "Report completed tasks in interval";
+static char desc_revert[] = "Discard changes and reload previous database from disc";
+static char desc_save[] = "Save the database back to disc and keep working";
+static char desc_undo[] = "Mark 1 or more entries as not done (cancel effect of 'done')";
+static char desc_usage[] = "Display help information";
+static char desc_version[] = "Display program version";
+static char desc_which[] = "Display filename of database being used";
+static char desc_widen[] = "Widen the part of the database to which actions apply";
+
+/* }}} */
+/* {{{ Synopsis of each subcommand */
+static char synop_above[] = "<index_to_insert_above> <index_to_move> ...";
+static char synop_add[] = "[@<datespec>] [<parent_index>] [<priority>] <entry_text>";
+static char synop_after[] = "<index_to_insert_below> <index_to_move> ...";
+static char synop_before[] = "<index_to_insert_above> <index_to_move> ...";
+static char synop_below[] = "<index_to_insert_below> <index_to_move> ...";
+static char synop_clone[] = "<index_to_clone> ...";
+static char synop_copyto[] = "<parent_index> <index_to_clone> ...";
+static char synop_create[] = "";
+static char synop_defer[] = "[@]<datespec> <entry_index>{...] ...";
+static char synop_delete[] = "<entry_index>[...] ...";
+static char synop_done[] = "[@<datespec>] <entry_index>[...] ...";
+static char synop_edit[] = "<entry_index> [<new_text>]";
+static char synop_exit[] = "";
+static char synop_export[] = "<filename> <entry_index> ...";
+static char synop_help[] = "[<command-name>]";
+static char synop_ignore[] = "<entry_index>[...] ...";
+static char synop_import[] = "<filename>";
+static char synop_into[] = "<new_parent_index> <index_to_move> ...";
+static char synop_list[] = "[-v] [-a] [-p] [-m] [-1..9] [<min-priority>] [<parent_index>|/<search_condition>...]\n"
+                           "-v                 : verbose (show dates, priorities etc)\n"
+                           "-a                 : show all entries, including 'done' ones\n"
+                           "-p                 : show deferred and postponed entries\n"
+                           "-m                 : don't use colours (monochrome)\n"
+                           "-1,-2,..,-9        : summarise (and don't show) entries below this depth\n"
+                           "<search_condition> : word to match on";
+static char synop_log[] = "[@<datespec>] [<parent_index>] [<priority>] <entry_text>";
+static char synop_moveto[] = "<new_parent_index> <index_to_move> ...";
+static char synop_narrow[] = "<entry_index>";
+static char synop_open[] = "<entry_index>[...] ...";
+static char synop_postpone[] = "<entry_index>[...] ...";
+static char synop_priority[] = "<new_priority> <entry_index>[...] ...";
+static char synop_purge[] = "<since_datespec> [<ancestor_index> ...]";
+static char synop_quit[] = "";
+static char synop_remove[] = "<entry_index>[...] ...";
+static char synop_report[] = "<start_datespec> [<end_datespec>]\n"
+                             "(end defaults to now)";
+static char synop_revert[] = "";
+static char synop_save[] = "";
+static char synop_undo[] = "<entry_index>[...] ...";
+static char synop_usage[] = "[<command-name>]";
+static char synop_version[] = "";
+static char synop_which[] = "";
+static char synop_widen[] = "[<levels>]";
+/* }}} */
+
+static int process_create(char **x)/*{{{*/
+{
+  char *dbpath = get_database_path(0);
+  struct stat sb;
+  int result;
+
+  result = stat(dbpath, &sb);
+  if (result >= 0) {
+    fprintf(stderr, "Can't create database <%s>, it already exists!\n", dbpath);
+    return -1;
+  } else {
+    if (read_only) {
+      fprintf(stderr, "Can't create database <%s> in read-only mode!\n", dbpath);
+       return -1;
+    }
+    /* Should have an empty database, and the dirty flag will be set */
+    current_database_path = dbpath;
+    /* don't emit complaint about not being able to move database to its backup */
+    is_noisy = 0;
+    /* Force empty database to be written out */
+    is_loaded = currently_dirty = 1;
+    return 0;
+  }
+}
+/*}}}*/
+static int process_priority(char **x)/*{{{*/
+{
+  int error;
+  enum Priority priority;
+  struct node *n;
+  int do_descendents;
+  priority = parse_priority(*x, &error);
+  if (error < 0) {
+    fprintf(stderr, "usage: priority %s\n", synop_priority);
+    return error;
+  }
+
+  while (*++x) {
+    do_descendents = include_descendents(*x); /* May modify *x */
+    n = lookup_node(*x, 0, NULL);
+    if (!n) return -1;
+    n->priority = priority;
+    if (do_descendents) {
+      set_descendent_priority(n, priority);
+    }
+  }
+
+  return 0;
+}/*}}}*/
+static int process_which(char **argv)/*{{{*/
+{
+  printf("%s\n", current_database_path);
+  return 0;
+}
+/*}}}*/
+static int process_version(char **x)/*{{{*/
+{
+  fprintf(stderr, "tdl %s\n", PROGRAM_VERSION);
+  return 0;
+}
+/*}}}*/
+static int process_exit(char **x)/*{{{*/
+{
+  save_database(current_database_path);
+  free_database(&top);
+  unlock_and_exit(0);
+  return 0; /* moot */
+}
+/*}}}*/
+static int process_quit(char **x)/*{{{*/
+{
+  /* Just get out quick, don't write the database back */
+  char ans[4];
+  if (currently_dirty) {
+    printf(" WARNING: if you quit, all changes to database will be lost!\n"
+        " Use command 'exit' instead of 'quit' if you wish to save data.\n"
+        " Really quit [y/N]? ");
+    fgets (ans, 4, stdin);
+    if (strcasecmp(ans,"y\n") != 0) {
+      printf(" Quit canceled.\n");
+      return 0;
+    }
+  }
+  free_database(&top);
+  unlock_and_exit(0);
+  return 0; /* moot */
+}
+/*}}}*/
+static int process_save(char **x)/*{{{*/
+{
+  /* FIXME: I'm not sure whether the behaviour here should include renaming the
+   * existing disc database to become the backup file.  I think the precedent
+   * would be how vi or emacs handle backup files when multiple saves are done
+   * within a session. */
+  save_database(current_database_path);
+  return 0;
+}
+/*}}}*/
+static int process_revert(char **x)/*{{{*/
+{
+  if (is_loaded) {
+    free_database(&top);
+  }
+  is_loaded = currently_dirty = 0;
+  return 0;
+}
+/*}}}*/
+/* Forward prototype */
+static int usage(char **x);
+
+struct command cmds[] = {/*{{{*/
+  {"--help",   NULL,   usage,            desc_help,    NULL,          NULL,              0, 0, 3, 0, 1},
+  {"-h",       NULL,   usage,            desc_help,    NULL,          NULL,              0, 0, 2, 0, 1},
+  {"-V",       NULL,   process_version,  desc_version, NULL,          NULL,              0, 0, 2, 0, 1},
+  {"above",    NULL,   process_above,    desc_above,   synop_above,   NULL,              1, 1, 2, 1, 1},
+  {"add",      "tdla", process_add,      desc_add,     synop_add,     NULL,              1, 1, 2, 1, 1},
+  {"after",    NULL,   process_below,    desc_after,   synop_after,   NULL,              1, 1, 2, 1, 1},
+  {"before",   NULL,   process_above,    desc_before,  synop_before,  NULL,              1, 1, 3, 1, 1},
+  {"below",    NULL,   process_below,    desc_below,   synop_below,   NULL,              1, 1, 3, 1, 1},
+  {"clone",    NULL,   process_clone,    desc_clone,   synop_clone,   NULL,              1, 1, 2, 1, 1}, 
+  {"copyto",   NULL,   process_copyto,   desc_copyto,  synop_copyto,  NULL,              1, 1, 2, 1, 1}, 
+  {"create",   NULL,   process_create,   desc_create,  synop_create,  NULL,              1, 0, 2, 0, 1},
+  {"defer",    NULL,   process_defer,    desc_defer,   synop_defer,   NULL,              1, 1, 3, 1, 1},
+  {"delete",   NULL,   process_remove,   desc_delete,  synop_delete,  NULL,              1, 1, 3, 1, 1},
+  {"done",     "tdld", process_done,     desc_done,    synop_done,    complete_done,     1, 1, 2, 1, 1},
+  {"edit",     NULL,   process_edit,     desc_edit,    synop_edit,    NULL,              1, 1, 2, 1, 1},
+  {"exit",     NULL,   process_exit,     desc_exit,    synop_exit,    NULL,              0, 0, 3, 1, 0},
+  {"export",   NULL,   process_export,   desc_export,  synop_export,  NULL,              0, 1, 3, 1, 1},
+  {"help",     NULL,   usage,            desc_help,    synop_help,    complete_help,     0, 0, 1, 1, 1},
+  {"ignore",   NULL,   process_ignore,   desc_ignore,  synop_ignore,  complete_done,     1, 1, 2, 1, 1},
+  {"import",   NULL,   process_import,   desc_import,  synop_import,  NULL,              1, 1, 2, 1, 1},
+  {"into",     NULL,   process_into,     desc_into,    synop_into,    NULL,              1, 1, 2, 1, 1},
+  {"list",     "tdll", process_list,     desc_list,    synop_list,    complete_list,     0, 1, 2, 1, 1},
+  {"ls",       "tdls", process_list,     desc_list,    synop_list,    complete_list,     0, 1, 2, 1, 1},
+  {"log",      "tdlg", process_log,      desc_log,     synop_log,     NULL,              1, 1, 2, 1, 1},
+  {"moveto",   NULL,   process_into,     desc_moveto,  synop_moveto,  NULL,              1, 1, 1, 1, 1},
+  {"narrow",   NULL,   process_narrow,   desc_narrow,  synop_narrow,  NULL,              0, 1, 1, 1, 0},
+  {"open",     NULL,   process_open,     desc_open,    synop_open,    complete_open,     1, 1, 1, 1, 1},
+  {"postpone", NULL,   process_postpone, desc_postpone,synop_postpone,complete_postpone, 1, 1, 2, 1, 1},
+  {"priority", NULL,   process_priority, desc_priority,synop_priority,complete_priority, 1, 1, 2, 1, 1},
+  {"purge",    NULL,   process_purge,    desc_purge,   synop_purge,   NULL,              1, 1, 2, 1, 1},
+  {"quit",     NULL,   process_quit,     desc_quit,    synop_quit,    NULL,              0, 0, 1, 1, 0},
+  {"remove",   NULL,   process_remove,   desc_remove,  synop_remove,  NULL,              1, 1, 3, 1, 1},
+  {"report",   NULL,   process_report,   desc_report,  synop_report,  NULL,              0, 1, 3, 1, 1},
+  {"revert",   NULL,   process_revert,   desc_revert,  synop_revert,  NULL,              0, 0, 3, 1, 0},
+  {"save",     NULL,   process_save,     desc_save,    synop_save,    NULL,              0, 1, 1, 1, 0},
+  {"undo",     NULL,   process_undo,     desc_undo,    synop_undo,    NULL,              1, 1, 2, 1, 1},
+  {"usage",    NULL,   usage,            desc_usage,   synop_usage,   complete_help,     0, 0, 2, 1, 1},
+  {"version",  NULL,   process_version,  desc_version, synop_version, NULL,              0, 0, 1, 1, 1},
+  {"which",    NULL,   process_which,    desc_which,   synop_which,   NULL,              0, 0, 2, 1, 1},
+  {"widen",    NULL,   process_widen,    desc_widen,   synop_widen,   NULL,              0, 1, 2, 1, 0}
+};/*}}}*/
+int n_cmds = 0;
+
+#define N(x) (sizeof(x) / sizeof(x[0]))
+
+static int is_processing = 0;
+static int signal_count = 0;
+
+static void handle_signal(int a)/*{{{*/
+{
+  signal_count++;
+  /* And close stdin, which should cause readline() in inter.c to return
+   * immediately if it was active when the signal arrived. */
+  close(0);
+
+  if (signal_count == 3) {
+    /* User is desperately hitting ^C to stop the program.  Bail out without tidying up, and give a warning. */
+    static char msg[] = "About to force exit due to repeated termination signals.\n"
+       "Database changes since the last save will be LOST.\n";
+    write(2, msg, strlen(msg));
+  }
+  if (signal_count == 4) {
+    static char msg[] = "The database may be left locked.\n"
+      "You will need to run 'tdl -u' next time to unlock it.\n";
+    write(2, msg, strlen(msg));
+    exit(1);
+  }
+}
+/*}}}*/
+static void guarded_sigaction(int signum, struct sigaction *sa)/*{{{*/
+{
+  if (sigaction(signum, sa, NULL) < 0) {
+    perror("sigaction");
+    unlock_and_exit(1);
+  }
+}
+/*}}}*/
+static void setup_signals(void)/*{{{*/
+{
+  struct sigaction sa;
+  if (sigemptyset(&sa.sa_mask) < 0) {
+    perror("sigemptyset");
+    unlock_and_exit(1);
+  }
+  sa.sa_handler = handle_signal;
+  sa.sa_flags = 0;
+#if 0
+  guarded_sigaction(SIGHUP, &sa);
+  guarded_sigaction(SIGINT, &sa);
+  guarded_sigaction(SIGQUIT, &sa);
+  guarded_sigaction(SIGTERM, &sa);
+#endif
+
+  return;
+}
+/*}}}*/
+static void print_copyright(void)/*{{{*/
+{
+  fprintf(stderr,
+          "tdl %s, Copyright (C) 2001,2002,2003,2004,2005 Richard P. Curnow\n"
+          "tdl comes with ABSOLUTELY NO WARRANTY.\n"
+          "This is free software, and you are welcome to redistribute it\n"
+          "under certain conditions; see the GNU General Public License for details.\n\n",
+          PROGRAM_VERSION);
+}
+/*}}}*/
+void dispatch(char **argv) /* and other args *//*{{{*/
+{
+  int i, p_len, matchlen, index=-1;
+  char *executable;
+  int is_tdl;
+  char **p, **pp;
+
+  if (signal_count > 0) {
+    save_database(current_database_path);
+    unlock_and_exit(0);
+  }
+
+  executable = executable_name(argv[0]);
+  is_tdl = (!strcmp(executable, "tdl"));
+  
+  p = argv + 1;
+  while (*p && *p[0] == '-') p++;
+  
+  /* Parse command line */
+  if (is_tdl && !*p) {
+    /* If no arguments, go into interactive mode, but only if we didn't come from there (!) */
+    if (!is_interactive) {
+      setup_signals();
+      print_copyright();
+      is_interactive = 1;
+      interactive();
+    }
+    return;
+  }
+
+  if (is_tdl) {
+    p_len = strlen(*p);
+    for (i=0; i<n_cmds; i++) {
+      matchlen = p_len < cmds[i].matchlen ? cmds[i].matchlen : p_len;
+      if ((is_interactive ? cmds[i].interactive_ok : cmds[i].non_interactive_ok) &&
+          !strncmp(cmds[i].name, *p, matchlen)) {
+        index = i;
+        break;
+      }
+    }
+  } else {
+    for (i=0; i<n_cmds; i++) {
+      if (cmds[i].shortcut && !strcmp(cmds[i].shortcut, executable)) {
+        index = i;
+        break;
+      }
+    }
+  }
+
+  /* Skip commands that dirty the database, if it was opened read-only. */
+  if (index >= 0 && cmds[index].dirty && read_only) {
+    fprintf(stderr, "Can't use command <%s> in read-only mode\n",
+                cmds[index].name);
+    if (!is_interactive) {
+      unlock_and_exit(-1);
+    }
+  } else if (index >= 0) {
+    int result;
+
+    is_processing = 1;
+
+    if (!is_loaded && cmds[index].load_db) {
+      load_database(current_database_path);
+    }
+
+    pp = is_tdl ? (p + 1) : p;
+    result = (cmds[index].func)(pp);
+    
+    /* Check for failure */
+    if (result < 0) {
+      if (!is_interactive) {
+        unlock_and_exit(-result);
+      }
+
+      /* If interactive, the handling function has emitted its error message.
+       * Just 'abort' this command and go back to the prompt */
+
+    } else {
+    
+      if (cmds[index].dirty) {
+        currently_dirty = 1;
+      }
+    }
+
+    is_processing = 0;
+    if (signal_count > 0) {
+      save_database(current_database_path);
+      unlock_and_exit(0);
+    }
+    
+  } else {
+    if (is_tdl && *p) {
+      fprintf(stderr, "tdl: Unknown command <%s>\n", *p);
+    } else {
+      fprintf(stderr, "tdl: Unknown command\n");
+    }
+    if (!is_interactive) {
+      unlock_and_exit(1);
+    }
+  }
+  
+}
+/*}}}*/
+static int usage(char **x)/*{{{*/
+{
+  int i, index;
+  char *cmd = *x;
+
+  if (cmd) {
+    /* Detailed help for the one command */
+    index = -1;
+    for (i=0; i<n_cmds; i++) {
+      if (!strncmp(cmds[i].name, cmd, 3) &&
+          (is_interactive ? cmds[i].interactive_ok : cmds[i].non_interactive_ok)) {
+        index = i;
+        break;
+      }
+    }
+    if (index >= 0) {
+      fprintf(stdout, "Description\n  %s\n\n", cmds[i].descrip);
+      fprintf(stdout, "Synopsis\n");
+      
+      if (is_interactive) {
+        fprintf(stdout, "  %s %s\n", cmds[i].name, cmds[i].synopsis ? cmds[i].synopsis : "");
+      } else {
+        fprintf(stdout, "  tdl  [-qR] %s %s\n", cmds[i].name, cmds[i].synopsis ? cmds[i].synopsis : "");
+        if (cmds[i].shortcut) {
+          fprintf(stdout, "  %s [-qR] %s\n", cmds[i].shortcut, cmds[i].synopsis ? cmds[i].synopsis : "");
+        }
+      }
+
+      fprintf(stdout,
+              "\n"
+              "General notes (where they apply to a command):\n"
+              "\n"
+              "<*_index>  : 1, 1.1 etc (see output of 'tdl list')\n"
+              "<priority> : urgent|high|normal|low|verylow\n"
+              "<datespec> : [-|+][0-9]+[shdwmy][-hh[mm[ss]]]  OR\n"
+              "             [-|+](sun|mon|tue|wed|thu|fri|sat)[-hh[mm[ss]]] OR\n"
+              "             [[[cc]yy]mm]dd[-hh[mm[ss]]]\n"
+              "<text>     : Any text (you'll need to quote it if >1 word)\n"
+              );
+
+    } else {
+      fprintf(stderr, "Unrecognized command <%s>, no help available\n", cmd);
+    }
+        
+  } else {
+    print_copyright();
+
+    if (!is_interactive) {
+      fprintf(stdout, "tdl  [-qR]          : Enter interactive mode\n");
+    }
+    for (i=0; i<n_cmds; i++) {
+      if (is_interactive) {
+        if (cmds[i].interactive_ok) {
+          fprintf(stdout, "%-8s : %s\n", cmds[i].name, cmds[i].descrip);
+        }
+      } else {
+        if (cmds[i].non_interactive_ok) {
+          fprintf(stdout, "tdl  [-qR] %-8s : %s\n", cmds[i].name, cmds[i].descrip);
+          if (cmds[i].shortcut) {
+            fprintf(stdout, "%s [-qR]          : %s\n", cmds[i].shortcut, cmds[i].descrip);
+          }
+        }
+      }
+    }
+    if (is_interactive) {
+      fprintf(stdout, "\nEnter 'help <command-name>' for more help on a particular command\n");
+    } else {
+      fprintf(stdout, "\nEnter 'tdl help <command-name>' for more help on a particular command\n");
+    }
+  }
+
+  fprintf(stdout, "\n");
+
+  return 0;
+
+}
+/*}}}*/
+
+/*{{{  int main (int argc, char **argv)*/
+int main (int argc, char **argv)
+{
+  int i = 0;
+  n_cmds = N(cmds);
+  is_interactive = 0;
+  
+  /* Initialise database */
+  top.prev = (struct node *) &top;
+  top.next = (struct node *) &top;
+
+  if (argc > 1) {
+    for (i=1; i<argc && argv[i][0] == '-'; i++) {
+      if (strspn(argv[i]+1, "qRu")+1 != strlen(argv[i])) {
+        fprintf(stderr, "Unknown flag <%s>\n", argv[i]);
+        return 1;
+      }
+
+      if (strchr(argv[i], 'q')) {
+        is_noisy = 0;
+      }
+      if (strchr(argv[i], 'R')) {
+        read_only = 1;
+      }
+      if (strchr(argv[i], 'u')) {
+        forced_unlock = 1;
+      }
+    }
+  }
+
+  current_database_path = get_database_path(1);
+
+  dispatch(argv);
+
+  if (! read_only) {
+    save_database(current_database_path);
+  }
+  free_database(&top);
+  unlock_and_exit(0);
+  return 0; /* moot */
+}
+/*}}}*/
+
diff --git a/memory.h b/memory.h
new file mode 100644 (file)
index 0000000..bd64cb4
--- /dev/null
+++ b/memory.h
@@ -0,0 +1,32 @@
+/*
+   $Header: /cvs/src/tdl/memory.h,v 1.1.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#ifndef MEMORY_H
+#define MEMORY_H
+#include <stdlib.h>
+
+#define new_string(s) strcpy((char *) malloc(1+strlen(s)), (s))
+#define extend_string(x,s) (strcat(realloc(x, (strlen(x)+strlen(s)+1)), s))
+#define new(T) (T *) malloc(sizeof(T))
+#define new_array(T, n) (T *) malloc(sizeof(T) * (n))
+#define grow_array(T, n, oldX) (T *) ((oldX) ? realloc(oldX, (sizeof(T) * (n))) : malloc(sizeof(T) * (n)))
+#endif
+
diff --git a/mkversion b/mkversion
new file mode 100755 (executable)
index 0000000..5d864bb
--- /dev/null
+++ b/mkversion
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+rm -f version.h
+echo "#ifndef VERSION_H" > version.h
+echo "#define VERSION_H 1" >> version.h
+
+if [ -f version.txt ]; then
+       ver=`cat version.txt`
+       echo "#define PROGRAM_VERSION \"$ver\"" >> version.h
+else
+       echo "#define PROGRAM_VERSION \"DEVELOPMENT\"" >> version.h
+fi
+
+echo "#endif /* VERSION_H */" >> version.h
+
diff --git a/move.c b/move.c
new file mode 100644 (file)
index 0000000..b0338f1
--- /dev/null
+++ b/move.c
@@ -0,0 +1,142 @@
+/*
+   $Header: /cvs/src/tdl/move.c,v 1.8.2.2 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include "tdl.h"
+
+
+static int is_ancestor(struct node *anc, struct node *dec)/*{{{*/
+{
+  /* Check includes whether the two nodes are the same */
+  struct node *parent;
+  for (parent = dec; parent != NULL; parent = parent->parent) {
+    if (parent == anc) {
+      return 1;
+    }
+  }
+  return 0;
+}
+/*}}}*/
+
+static int process_move_internal(char **x, int below_not_above, int into_parent)/*{{{*/
+{
+  /* x is the argument list
+   * below_not_above is true to insert below x[0], false to insert above
+   * into_parent means an implicit .0 is appended to x[0] to get the path
+   */
+  
+  int argc, i, n;
+  struct links *insert_point;
+  struct node **table;
+  struct node *insert_parent, *insert_peer=NULL, *parent;
+  
+  char *option;
+
+  option = below_not_above ? "below" : "above";
+  
+  argc = count_args(x);
+  if (argc < 2) {
+    fprintf(stderr, "Usage: %s <index_to_insert_%s> <index_to_move> ...\n",
+            option, option);
+    return -1;
+  }
+
+  n = argc - 1;
+  if (into_parent) {
+    insert_parent = lookup_node(x[0], 0, NULL);
+    if (!insert_parent) return -1;
+    insert_point = &insert_parent->kids;
+  } else {
+    insert_peer = lookup_node(x[0], 1, &insert_parent); /* Allow final component to be zero */
+    insert_point = (struct links *) &insert_peer->chain;
+    if (!insert_point) return -1;
+  }
+  table = new_array(struct node *, n);
+  x++;
+
+  /* Have to do the move in 2 passes, otherwise the indices of the entries
+     could change in mid-lookup. */
+  for (i=0; i<n; i++) {
+    table[i] = lookup_node(x[i], 0, NULL); /* Don't allow zero for this */
+    if (!table[i]) return -1; /* memory leak */
+
+    /* Check for an attempt to move a node onto one of its own descendents */
+    if (is_ancestor(table[i], insert_parent)) {
+      fprintf(stderr, "Can't re-parent entry %s onto a descendent of itself\n", x[i]);
+      return -1;
+    }
+  }
+
+  for (i=0; i<n; i++) { 
+    /* Unlink from its current location */
+    struct node *prev, *next;
+
+    if (table[i] == insert_peer) {
+      fprintf(stderr, "Can't move %s relative to itself\n", x[0]);
+      continue;
+    }
+    
+    next = table[i]->chain.next;
+    prev = table[i]->chain.prev;
+    prev->chain.next = next;
+    next->chain.prev = prev;
+
+    (below_not_above ? append_node : prepend_node) (table[i], insert_point);
+    if (into_parent) {
+      table[i]->parent = insert_parent;
+    } else {
+      /* in this case 'insert_peer' is just the one we're putting things above
+       * or below, i.e. the entries being moved will be siblings of it and will
+       * share its parent. */
+      table[i]->parent = insert_peer->parent;
+    }
+      
+    /* To insert the nodes in the command line order */
+    if (below_not_above) insert_point = &table[i]->chain;
+    /* if inserting above something, the insertion point stays fixed */
+    
+    /* Clear done status of insertion point and its ancestors */
+    if (table[i]->done == 0) {
+      parent = insert_parent;
+      while (parent) {
+        parent->done = 0;
+        parent = parent->parent;
+      }
+    }
+  }
+
+  return 0;
+}
+/*}}}*/
+int process_above(char **x)/*{{{*/
+{
+  return process_move_internal(x, 0, 0);
+}
+/*}}}*/
+int process_below(char **x)/*{{{*/
+{
+  return process_move_internal(x, 1, 0);
+}
+/*}}}*/
+int process_into(char **x)/*{{{*/
+{
+  return process_move_internal(x, 0, 1);
+}
+/*}}}*/
diff --git a/narrow.c b/narrow.c
new file mode 100644 (file)
index 0000000..a158995
--- /dev/null
+++ b/narrow.c
@@ -0,0 +1,151 @@
+/*
+   $Header: /cvs/src/tdl/narrow.c,v 1.2.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include "tdl.h"
+
+static struct node *narrow_top = NULL;
+static char *narrow_prefix = NULL;
+
+struct node *get_narrow_top(void)/*{{{*/
+{
+  return narrow_top;
+}
+/*}}}*/
+char *get_narrow_prefix(void)/*{{{*/
+{
+  return narrow_prefix;
+}
+/*}}}*/
+static char *generate_prefix(struct node *x, char *suffix)
+{
+  int count;
+  struct node *y, *parent;
+  char buffer[16];
+  int suffix_len, count_len;
+  int total_len;
+  char *result;
+  char *new_suffix;
+
+  parent = x->parent;
+  count = 1;
+  if (parent) {
+    for (y = parent->kids.next; y != x; y = y->chain.next) count++;
+  } else {
+    for (y = top.next; y != x; y = y->chain.next) count++;
+  }
+  sprintf(buffer, "%d", count);
+  count_len = strlen(buffer);
+  suffix_len = strlen(suffix);
+  total_len = count_len + suffix_len;
+  if (suffix_len) ++total_len; /* allow for '.' */
+  
+  new_suffix = new_array(char, 1+total_len);
+  strcpy(new_suffix, buffer);
+  if (suffix_len) strcat(new_suffix, ".");
+  strcat(new_suffix, suffix);
+
+  if (parent) {
+    result = generate_prefix(parent, new_suffix);
+    free(new_suffix);
+    return result;
+  } else {
+    return new_suffix;
+  }
+}
+
+static void setup_narrow_prefix(void)/*{{{*/
+{
+  if (narrow_prefix) {
+    free(narrow_prefix);
+    narrow_prefix = NULL;
+  }
+
+  narrow_prefix = generate_prefix(narrow_top, "");
+}
+/*}}}*/
+int process_narrow(char **x)/*{{{*/
+{
+  int argc;
+  struct node *n;
+
+  argc = count_args(x);
+
+  if (argc < 1) {
+    fprintf(stderr, "Usage : narrow <entry_index>\n");
+    return -1;
+  }
+
+  n = lookup_node(x[0], 0, NULL);
+  if (!n) return -1;
+
+  narrow_top = n;
+
+  /* Compute prefix string. */
+  setup_narrow_prefix();
+
+  return 0;
+}
+/*}}}*/
+int process_widen(char **x)/*{{{*/
+{
+  int argc;
+  int n_levels;
+  int i;
+  struct node *new_narrow_top;
+  
+  argc = count_args(x);
+  if (argc > 1) {
+    fprintf(stderr, "Usage : widen [<n_levels>]\n");
+    return -1;
+  }
+  if (argc > 0) {
+    if (sscanf(x[0], "%d", &n_levels) != 1) {
+      fprintf(stderr, "Usage : widen [<n_levels>]\n");
+      return -1;
+    }
+  } else {
+    n_levels = 1;
+  }
+
+  new_narrow_top = narrow_top;
+  if (!new_narrow_top) goto widen_to_top;
+
+  for (i=0; i<n_levels; i++) {
+    new_narrow_top = new_narrow_top->parent;
+    if (!new_narrow_top) goto widen_to_top;
+  }
+
+  narrow_top = new_narrow_top;
+  setup_narrow_prefix();
+  return 0;
+
+    /* Widen back to top level */
+widen_to_top:
+  narrow_top = NULL;
+  if (narrow_prefix) {
+    free(narrow_prefix);
+    narrow_prefix = NULL;
+  }
+
+  return 0;
+}
+/*}}}*/
+
diff --git a/purge.c b/purge.c
new file mode 100644 (file)
index 0000000..7022fe7
--- /dev/null
+++ b/purge.c
@@ -0,0 +1,109 @@
+/*
+   $Header: /cvs/src/tdl/purge.c,v 1.6.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <time.h>
+#include "tdl.h"
+
+static void purge_from_bottom_up(struct links *x)/*{{{*/
+{
+  struct node *y, *ny; 
+  for (y = x->next; y != (struct node *) x; y = ny) {
+
+    /* Calculate this now in case y gets deleted later */
+    ny = y->chain.next; 
+    
+    if (has_kids(y)) {
+      purge_from_bottom_up(&y->kids);
+    }
+
+    if (y->flag) {
+      /* If the node still has children, just keep quite and don't delete it. 
+       * The idea of the 'purge' command is just a fast bulk delete of all
+       * old stuff under certain tasks, so this is OK */
+      if (!has_kids(y)) {
+        /* Just unlink from the chain for now, don't bother about full clean up. */
+        struct node *next, *prev;
+        next = y->chain.next;
+        prev = y->chain.prev;
+        prev->chain.next = next;
+        next->chain.prev = prev;
+      }
+    }
+  }
+}
+/*}}}*/
+static void scan_from_top_down(struct links *x, time_t then)/*{{{*/
+{
+  struct node *y; 
+  for (y = x->next; y != (struct node *) x; y = y->chain.next) {
+    if ((y->done > 0) && (y->done < then)) y->flag = 1;
+    scan_from_top_down(&y->kids, then);
+  }
+}
+/*}}}*/
+
+int process_purge(char **x)/*{{{*/
+{
+  int argc;
+  time_t now, then;
+  char *d;
+  int error;
+  struct node *narrow_top;
+  struct links *to_purge_from;
+  
+  narrow_top = get_narrow_top();
+  to_purge_from = narrow_top ? &narrow_top->kids : &top;
+
+  argc = count_args(x);
+  if (argc < 1) {
+    fprintf(stderr, "Usage: purge <interval> <entry_index> ...\n");
+    return -1;
+  }
+
+  d = x[0];
+  if (*d == '@') d++;
+  now = time(NULL);
+  then = parse_date(d, now, 0, &error);
+  if (error < 0) return error;
+  x++;
+
+  clear_flags(to_purge_from);
+  now = time(NULL);
+  if (!*x) {
+    /* If no indices given, do whole database */
+    scan_from_top_down(to_purge_from, then);
+  } else {
+    while (*x) {
+      struct node *n;
+      n = lookup_node(*x, 0, NULL);
+      if (!n) return -1;
+      if ((n->done > 0) && (n->done < then)) n->flag = 1;
+      scan_from_top_down(&n->kids, then);
+      x++;
+    }
+  }
+
+  purge_from_bottom_up(to_purge_from);
+
+  return 0;
+}
+/*}}}*/
diff --git a/remove.c b/remove.c
new file mode 100644 (file)
index 0000000..855f9da
--- /dev/null
+++ b/remove.c
@@ -0,0 +1,77 @@
+/*
+   $Header: /cvs/src/tdl/remove.c,v 1.5.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <string.h>
+#include "tdl.h"
+
+static void delete_from_bottom_up(struct links *x)/*{{{*/
+{
+  struct node *y, *ny; 
+  for (y = x->next; y != (struct node *) x; y = ny) {
+
+    /* Calculate this now in case y gets deleted later */
+    ny = y->chain.next; 
+    
+    if (has_kids(y)) {
+      delete_from_bottom_up(&y->kids);
+    }
+
+    if (y->flag) {
+      if (has_kids(y)) {
+        fprintf(stderr, "Cannot delete %s, it has sub-tasks\n", y->scratch);
+      } else {
+        /* Just unlink from the chain for now, don't bother about full clean up. */
+        struct node *next, *prev;
+        next = y->chain.next;
+        prev = y->chain.prev;
+        prev->chain.next = next;
+        next->chain.prev = prev;
+        free(y->text);
+        free(y);
+      }
+    }
+  }
+}
+/*}}}*/
+int process_remove(char **x)/*{{{*/
+{
+  struct node *n;
+  int do_descendents;
+
+  clear_flags(&top);
+
+  while (*x) {
+    do_descendents = include_descendents(*x); /* May modify *x */
+    n = lookup_node(*x, 0, NULL);
+    if (!n) return -1;
+    n->flag = 1;
+    if (do_descendents) {
+      mark_all_descendents(n);
+    }
+    n->scratch = *x; /* *x has long lifetime => OK to alias */
+    ++x;
+  }
+
+  delete_from_bottom_up(&top);
+  
+  return 0;
+}
+/*}}}*/
diff --git a/report.c b/report.c
new file mode 100644 (file)
index 0000000..7c561e0
--- /dev/null
+++ b/report.c
@@ -0,0 +1,108 @@
+/*
+   $Header: /cvs/src/tdl/report.c,v 1.5.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <time.h>
+#include "tdl.h"
+
+static int mark_nodes_done_since(struct links *x, time_t start, time_t end)/*{{{*/
+{
+  int flag_set = 0;
+  struct node *y;
+
+  for (y = x->next; y != (struct node *) x; y = y->chain.next) {
+    if ((y->done >= start) && (y->done <= end)) {
+      y->flag = 1;
+      flag_set = 1;
+    }
+
+    if (has_kids(y)) {
+      if (mark_nodes_done_since(&y->kids, start, end)) {
+        y->flag = 1;
+        flag_set = 1;
+      }
+    }
+  }
+  return flag_set;
+}
+/*}}}*/
+static void print_report(struct links *x, int indent)/*{{{*/
+{
+  struct node *y;
+  char *p;
+
+  for (y = x->next; y != (struct node *) x; y = y->chain.next) {
+    if (y->flag) {
+      do_bullet_indent(indent+2);
+      if (!y->done) printf ("[[");
+      for (p = y->text; *p; p++) {
+        putchar(*p);
+        if (*p == '\n') {
+          do_indent(indent + 2);
+        }
+      }
+      if (!y->done) printf ("]]");
+      putchar('\n');
+
+      if (has_kids(y)) {
+        print_report(&y->kids, indent + 3);
+      }
+    }
+  }
+}
+/*}}}*/
+int process_report(char **x)/*{{{*/
+{
+  time_t now, start, end;
+  int argc;
+  char *d0, *d1;
+  int error;
+
+  argc = count_args(x);
+  start = end = now = time(NULL);
+
+  switch (argc) {
+    case 1:
+      d0 = x[0];
+      if (*d0 == '@') d0++;
+      start = parse_date(d0, now, 0, &error);
+      if (error < 0) return error;
+      break;
+    case 2:
+      d0 = x[0];
+      d1 = x[1];
+      if (*d0 == '@') d0++;
+      if (*d1 == '@') d1++;
+      start = parse_date(d0, now, 0, &error);
+      if (error < 0) return error;
+      end = parse_date(d1, now, 0, &error);
+      if (error < 0) return error;
+      break;
+    default:
+      fprintf(stderr, "Usage: report <start_epoch> [<end_epoch>]\n");
+      break;
+  }
+
+  clear_flags(&top);
+  mark_nodes_done_since(&top, start, end);
+  print_report(&top, 0);
+  return 0;
+}
+/*}}}*/
diff --git a/tdl.1 b/tdl.1
new file mode 100644 (file)
index 0000000..b29600a
--- /dev/null
+++ b/tdl.1
@@ -0,0 +1,969 @@
+.TH "tdl" 1 "May 2003" "1.4"
+.SH NAME
+tdl \- To do list manager
+.SH SYNOPSIS
+tdl  [\-q]
+.br
+tdl  [\-q] add|edit|defer|log
+.br
+tdl  [\-q] list|done|undo|report
+.br
+tdl  [\-q] remove|above|below|into|clone|copyto
+.br
+tdl  [\-q] postpone|ignore|open
+.br
+tdl  [\-q] which|version|help
+.br
+tdla [\-q]
+.br
+tdll [\-q]
+.br
+tdls [\-q]
+.br
+tdld [\-q]
+.br
+tdlg [\-q]
+
+.SH DESCRIPTION
+A program for managing a to-do list.
+.PP
+tdl has a set of functions that can be accessed in two different ways:
+
+    * Directly from the command line
+    * Interactively
+
+In the 'direct' method, the function and its arguments are provided on the 
+command line. This mode is useful if you only want to perform a single 
+operation. An example
+
+    % tdl add "A task"
+    %
+
+The 'interactive' method is entered when the tdl command is run with no 
+arguments. In this mode, many tdl operations may be performed within a 
+single run of the program. This avoids loading and saving the database 
+for each operation, which may have a small performance benefit. 
+However, if the program is compiled with the readline library, the 
+<tab> key will provide various completion functions. An example
+
+    % tdl
+    tdl> add "A task"
+    tdl> exit
+    %
+
+When in interactive mode, these methods can be used to exit and return 
+to the shell:
+
+* The 
+.B exit 
+command (see exit command)
+.br
+* Hitting <Ctrl-D> (i.e. end of file on stdin)
+.br
+* Hitting <Ctrl-C>, <Ctrl-\> etc. The associated signals are caught by 
+tdl and it will attempt to save the database. However, this method is 
+more risky than the first two.
+.br
+* The 
+.B quit 
+command (see quit command). Caution: this does not save the 
+modified database back to the disk. Only use it if you want to discard 
+all changes made in this tdl run. 
+
+.pp
+All forms may take
+.I \-q
+as the first command line argument.  Currently, this suppresses the warning
+message if no existing database can be found.  The intended use is for using
+.B tdll
+when changing into a directory, to list outstanding work in that directory.
+.PP
+Any command that modifies the database will rename the old database to a file
+called
+.B .tdldb.bak
+before writing out the new
+.B .tdldb
+(The backup file will be in the same directory as the main one.)  This allows
+for one level of database recovery, if the database is corrupted or a command
+is issued in error which causes large losses of data (e.g. misuse of the
+.B remove
+command.)
+
+
+.SH SUBCOMMANDS
+.B tdl above
+.I index_to_insert_above
+.I index_to_move ...
+.br
+.B tdl before
+.I index_to_insert_above
+.I index_to_move ...
+.PP
+This command moves one or more entries to a new location in the tree.  The
+first index specifies the entry which will end up immediately below the moved
+entries after the operation.  As a special case, you can specify the final
+component of the first argument as zero.  In this case, the moved entries
+appear as the last children of the parent node afterwards.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl add
+.I [@datespec]
+.I [parent_index]
+.I [priority]
+.I text_for_node
+.br
+.B tdla
+.I [@datespec]
+.I [parent_index]
+.I [priority]
+.I text_for_node
+.PP
+The
+.B add
+command is used to add a new entry to the database.
+.PP
+.B datespec
+is the time at which the entry will be visible when the database is printed
+with the
+.B list
+command.  It defaults to now.  The format for datespec is described in the
+.B "DATE SPECIFICATION"
+section later in this page.
+.PP
+.B parent_index
+is the index of the parent node (e.g. 1 or 2.4).  This defaults to the root
+node if missing, i.e. a new top-level entry is created.
+.PP
+.B priority is one of
+.IR urgent ,
+.IR high ,
+.IR normal ,
+.IR low " or"
+.IR verylow .
+Normal is the default if this argument is not specified.  Priorities may be
+abbreviated (even to just the first letter.)
+.PP
+.B text_for_node
+is the text describing the task for this entry.  If this is more than a single
+word, you will need to quote it to make the shell keep it as a single argument
+to tdl.  The text may span multiple lines (i.e. if you hit return whilst the
+quotation marks are still open in the shell.)
+.PP
+If no database exists, the
+.B add
+command will create it automatically (in the current directory, unless the
+.B TDL_DATABASE
+environment is set, in which case this specifies the path to use).
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl below
+.I index_to_insert_below
+.I index_to_move ...
+.br
+.B tdl after
+.I index_to_insert_below
+.I index_to_move ...
+.PP
+This command moves one or more entries to a new location in the tree.  The
+first index specifies the entry which will end up immediately above the moved
+entries after the operation.  As a special case, you can specify the final
+component of the first argument as zero.  In this case, the moved entries
+appear as the first children of the parent node afterwards.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl clone
+.I index_to_clone ...
+.PP
+The clone command can be used to make a deep copy of one or more entries and 
+add them as new top-level entries in the database. You might use this if you 
+have a task with a set of subtasks, and find that the same subtasks apply to 
+some new task. You could copy the first task, and edit the new top-level task 
+to change its text. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl copyto
+.I new_parent_index
+.I index_to_clone ...
+.PP
+The copyto command is very similar to the 
+.B clone 
+command. The difference is that copyto inserts the newly created entries as 
+children of an existing entry, rather than making them new top level entries. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl create
+.PP
+This command is used to create a .tdldb file in the current
+directory (or at the location specified by the environment variable
+TDL_DATABASE).  If the database file is already found to exist, a warning will
+be printed and the command has no effect.
+.PP
+The situation where you are likely to use this command is where there is
+already a .tdldb file in another directory further up the path from this one.
+Most of the tdl commands will find and use this other database file, assuming
+that you want to share it across all the directories in the tree.  You might
+want to use a single database across an entire large project, for example.  The
+.B create
+command will ignore any .tdldb file found in an ancestor directory.  It always
+operates in either the current directory or on the file pointed to by
+TDL_DATABASE.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl defer
+.I [@datespec]
+.I index_to_change...
+.PP
+The defer command is used to modify the start-time of one or more existing entries, 
+where the @ on the datespec is optional because the argument is required, although 
+the @ can be included for consistency with other commands where a datespec is optional. 
+.PP
+An example of use is 
+.P
+       tdl> defer @+fri 1 2.1... 5
+.P
+which defers entries 1, 2.1 and all its children, and 5 until the following Friday. 
+To list deferred entries, use 
+.I list \-p
+, to defer entries indefinitely, see 
+.I postpone 
+command. 
+To re-activate deferred or postponed entries, see 
+.I open 
+command. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl done
+.I @datespec
+.I index_of_done_entry ...
+.br
+.B tdld
+.I @datespec
+.I index_of_done_entry ...
+.PP
+The
+.B done
+command is used to mark one or more tasks as completed.  The effects are as follows:
+.IP o
+The entries no longer appear on the default listing (tdl list / tdll)
+.IP o
+The entries are eligible to appear on the report list (tdl report)
+.IP o
+The entries are eligible for removal by the purge command (tdl purge.)
+.PP
+If the string "..." is appended to an index, it means that entry and all its
+descendents.  This provides a quick way to mark a whole sub-tree of tasks as
+being completed.
+.PP
+.B datespec
+is the time at which the entry/entries should be marked as having been
+completed.  The default is to mark them completed at the current time.  The
+competion time of an entry affects whether it is shown by the
+.B report
+command for a particular range of reported times.
+.PP
+The format for datespec is described in the
+.B "DATE SPECIFICATION"
+section later in this page.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl edit
+.I index_to_change
+.I [new_text]
+.PP
+This command is used to change the text of an entry.  If no [new-text] is
+provided, you will be prompted with the old text to edit interactively. (This
+is only useful if the GNU readline library has been linked in.)
+.PP
+Note, in earlier versions, edit could be used to change the start-time of one 
+or more entries. This is now handled by the 
+.B defer 
+command.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B exit
+.PP
+The exit command is used to exit from tdl when it is used in interactive mode. 
+The exit command is not available in the command line mode, where it would not 
+make sense. 
+.br
+The exit command writes any pending updates to the database before exiting. 
+Compare the 
+.B quit 
+command, which loses all updates made during the current tdl run. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl export
+.I filename
+.I index_to_export ...
+.PP
+This command is used to create a new TDL database (whose name is given by the
+.I filename
+argument).  The initial contents of the new database are the entries specified
+by the list of indices following the filename, in that order.  Each index
+becomes a top-level entry of the new database.  The operation is read-only on
+the original database.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl help
+.PP
+This command displays a summary of use of each of the commands.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl ignore
+.I index_to_ignore ...
+.PP
+The ignore command puts one or more entries into an ignored state. It is 
+actually implemented in the same way as marking them as done, but as though 
+they were done a very long time ago. Thus, ignored entries will be deleted 
+by any subsequent purge operation.
+.br
+I added this feature because, when applying remove to several entries, I kept 
+getting tripped up by the indices changing below the entry that was removed 
+(I kept removing the wrong entries later by not using the revised indices). 
+Instead, I can ignore them and rely on a periodic purge to clean up the database.
+.br
+Another use for the ignore command would be to move moribund entries into a 
+wastebasket to stop them cluttering up the normal listing, but without removing 
+them entirely in case you need to reprieve them later. 
+.br
+If you need to un-ignore an entry, just 
+.B undo 
+it
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl import
+.I filename
+.PP
+This command is used to merge entries from the TDL database
+.I filename
+into the default TDL database (i.e. the one that most of the other commands
+would be accessing).
+.PP
+You might use this command if you had a number of separate TDL databases, and
+wanted to merge their entries to form one combo database.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl into
+.I new_parent_index
+.I indices_to_move ...
+.PP
+This command moves one or more entries under a new parent entry.  It is
+equivalent to the
+.B above
+command when the
+.B new_parent_index
+argument has ".0" appended to it.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl list
+.I [\-v]
+.I [\-a]
+.I [\-p]
+.I [\-m]
+.I [\-1...9]
+.I [<min-priority>]
+.I [<parent_index>|<search_conditions>...]
+.br
+.B tdll
+.I [\-v]
+.I [\-a]
+.I [\-p]
+.I [\-m]
+.I [\-1...9]
+.I [<min-priority>]
+.I [<parent_index>|<search_conditions...]
+.br
+.B tdls
+.I [\-v]
+.I [\-a]
+.I [\-p]
+.I [\-m]
+.I [\-1...9]
+.I [<min-priority>]
+.I [<parent_index>|<search_conditions...]
+.PP
+The
+.B list
+or it's synonymous
+.B ls
+command is used to display the entries in the database.  By default, only
+entries that have not been marked
+.B done
+and which don't have start times deferred into the future are shown.  If you
+want to display all entries, include the
+.B \-a
+option (which means 'all').  If you want to display the dates and times when
+the entries were added and/or done, include the 
+.B \-v
+option (which means 'verbose').
+The
+.B \-p
+option stands for postponed. It means that tasks which are 'deferred' or 'postponed' 
+are shown as well as open tasks. 
+.PP
+By default, only entries having normal, high or urgent priority are shown.  To
+change the minimum priority shown, specify the
+.B min-priority
+argument.  For example, 'tdll h' will only show entries with priority high or
+urgent.
+.PP
+By default, the whole database is scanned.  If you only want to show part(s) of
+the database, additional arguments can be given.  These are the indices of the
+top node of each part of the database you want to show.  So if your database
+contains entries with indices 1, 2, 2.1, 2.2, 2.2.1, 3 and 4, the command
+.PP
+tdl list \-a 2
+.PP
+will show all entries 2, 2.1, 2.2 and 2.2.1, whether or not they are completed.
+.PP
+Also by default, all entries in the database, at any depth, will be shown.  If
+you only wish to show 'top-level' entries, for example, you can use
+.PP
+tdl list \-1
+.PP
+This lists level-1 entries.  Any level-1 entry with hidden child entries
+underneath it will show a summary of how many such children there are.  For
+example, the output
+.PP
+3 [2/7] A top level entry
+.PP
+means that the entry with index 3 has a total of 7 entries underneath it, of
+which 2 are still open and 5 are completed (i.e. they've had 'tdl done' applied
+to them.)
+.PP
+Because the single digit arguments are used this way for the 'list' subcommand,
+the normal 'negative index' method can't be used to specify an entry a certain
+distance from the end of the list.  If you want to do this, use a syntax like
+.PP
+tdl list \-\- \-1
+.PP
+to show the last index in the array, or
+.PP
+tdl list \-2 \-\- \-3 \-2 \-1
+.PP
+to show level-1 and level-2 entries within the last 3 level-1 entries in the
+list.
+.PP
+Each 
+.B search condition 
+specifies a case-insensitive substring match that is applied to all parent 
+indices further on in the arguments. (If no parent indices are given, all the 
+search conditions are and'ed together and applied to filter all the nodes that 
+would be shown according to the depth, priority etc arguments).
+.PP
+Each search condition takes one of the following forms
+.PP
+    /substring
+    /substring/1
+.PP
+In each case, an entry will match if substring is actually a substring of the 
+text of that entry. In the second form (where the number may be 0, 1, 2 or 3), 
+a match occurs if there are up to that many errors in the substring. An error 
+is a single character inserted, removed or changed.
+.PP
+This option is most useful if you have a large database and can remember you 
+have an entry somewhere containing particular word(s), but can't remember where 
+it is.
+.PP
+If you need regular expression matching, the best approach would be to run 
+tdll from the shell and pipe the output to grep. The internal matching does 
+approximate matches with keys up to 31 characters. 
+.PP
+By default, the listing is produced with colour highlighting.  The
+.B \-m
+option can be used to produce a monochrome listing instead.  Alternatively, the
+.B TDL_LIST_MONOCHROME
+enviroment variable can be set (to any value) to achieve the same effect.
+.PP
+The colours are assigned as follows:
+.PP
+.TS
+tab(&);
+l | l.
+_
+Colour & Meaning
+_
+Red & Urgent task
+Yellow & High priority task
+White & Normal priority task
+Cyan & Low priority task, done task
+Blue & Very low priority task
+Green & Captions
+_
+.TE
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl log
+.br
+.B tdlg
+.PP
+This command is used to add a new entry and mark it done immediately.  It is
+most useful in conjunction with the
+.B report
+command, to record unexpected extra tasks you had to do.
+.PP
+The arguments for the
+.B log
+command are the same as those for the
+.B add
+command.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B narrow
+.I new_root_index
+.br
+.PP
+The 
+.B narrow 
+command can be used to limit the effects of later commands to operate within 
+a particular sub-tree of your database. Because the indices you specify for 
+the later operations have the common prefix omitted, this can save typing if 
+you have many changes to make within the same subtree.
+.P
+If your listings are in colour, the common prefix is coloured in blue whilst 
+the paths below the root of the sub-tree are shown in the usual green. 
+(In monochrome mode, there is no distinction.)
+.P
+Whilst your view is narrowed, the index of the sub-tree root is shown in square 
+brackets between tdl and > (i.e. [2]).
+.P
+If you want to operate on the sub-tree root entry itself whilst you are 
+narrowed, you can use . to specify its index (think: current directory in Unix.)
+.P
+To reverse the effects of the narrow command, use the 
+.B widen 
+command (see widen command).
+.P
+This command is only available when tdl is being run interactively, i.e. when 
+you have a tdl prompt. It is not available directly from the shell (where it 
+wouldn't make much sense). 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl open 
+.I index_to_reopen[...] ...
+.PP
+The open command is used to reverse the effect of the 
+.B postpone 
+command. Its effect is actually to set the arrival time of the entries to the 
+current time. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl postpone
+.I index_to_postpone[...] ...
+.PP
+The postpone command is used to make 1 more more entries postponed. Its effect 
+is actually to set the arrival time of the entries a long way in the future 
+(i.e. it's an extreme form of the 'deferred' feature available through the add and 
+defer commands.) Postponed entries can be re-activated with the 
+.B open 
+command. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl pri
+.I new_priority
+.I index_to_change ...
+.PP
+This command changes the priority of one or more entries.  The indices are in
+the same format as those in the output of the
+.B list
+command.  The
+.B new_priority
+argument takes the same possible values as for the
+.B add
+command.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl purge
+.I since_epoch
+.I [entry_index...]
+.PP
+This command is used to remove old done entries from the database.  It is much more convenient than repeated
+.B remove
+commands.
+.PP
+The
+.B since_epoch
+argument specifies a time.  The format for this argument is described in the
+.B "DATE SPECIFICATION"
+section later. Entries that were marked done (using the
+.B done
+command) before that epoch will be purged.
+.PP
+Zero or more
+.B entry_indices
+may be given.  These restrict the purging to just those entries and their
+descendents.  The default is to purge the entire database.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B quit
+.PP
+The quit command is used to exit from tdl when it is used in interactive mode. 
+The quit command is not available in the command line mode, where it would not 
+make sense. 
+.P
+The quit command DOES NOT write any pending updates to the database before 
+exiting. Compare the 
+.B exit 
+command, which does write all updates made during the current tdl run.
+.P
+The main use for the quit command would be to avoid damaging the database if a serious error had been made. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl remove
+.I index_to_remove ...
+.br
+.B tdl delete
+.I index_to_remove ...
+.PP
+Completely remove one or more entries from the database.  The indices are the
+same format as those shown in the output of the
+.B done
+command.
+.PP
+If the string "..." is appended to an index, it means that entry and all its
+descendents.  This provides a quick way to remove a whole sub-tree of tasks.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl report
+.I start_time
+.I [end_time]
+.PP
+The
+.B report
+command produces a report (in bulleted list format) of tasks completed in a
+certain time period.  This is useful if (for example) you have to write a
+weekly summary of the work you've done.
+.PP
+The default for the end of the time period is the current time, if the
+.B end_time
+argument is not present.  The start of the period to report on must always be
+specified.  The format for the time arguments is described in the
+.B "DATE SPECIFICATION"
+section later.
+Examples :
+.PP
+tdl report 1w
+.PP
+will list all tasks completed in the previous week, whereas
+.PP
+tdl report 2w 1w
+.PP
+will list all tasks completed between 2 and 1 weeks ago.
+.PP
+Where a child entry has been completed in the reporting period, but its parent
+has not been completed, the parent text in the report will be surrounded by
+'[[' and ']]'.  To give one example, this will happen if the parent has other
+child entries that haven't been completed yet.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B revert
+.PP
+The revert command discards any changes made in the session and reloads the 
+in-memory database from disc. If you have used the 
+.B save 
+command in the session, the database will revert to its state at the most 
+recent save. Otherwise it will revert to its state when tdl was initially run.
+.P
+The revert command does not take any arguments. 
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B save
+.PP
+The 
+.B save
+command can be used to write the current in-memory database out to the disc 
+database file. The behaviour is currently equivalent to the command exit 
+followed by re-running tdl from the shell.
+
+This command is useful if you tend to do long interactive tdl sessions. 
+It guards against the risks of
+.P
+1. accidentally typing quit when you meant exit
+.br
+2. machine crashes
+.br
+3. running tdl in another window and seeing a stale copy of the database file. 
+.P
+The save command does not take any arguments.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl undo
+.I index_of_entry_to_undo ...
+.PP
+This command cancels the effect of the
+.B done
+command for one or more entries, e.g. after they have been mistakenly marked as
+done.
+.PP
+If the string "..." is appended to an index, it means that entry and all its
+descendents.  This provides a quick way to re-open a whole sub-tree of tasks.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl usage
+.PP
+Same as
+.B tdl help
+(q.v.)
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl version
+.PP
+Show the version number of the software.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B tdl which
+.PP
+Show the filename of the database that tdl accesses in the current context.
+.P
+.ce 1
+--ooOOoo--
+.PP
+.B widen
+.I n_level
+.PP
+The optional n_levels parameter tells tdl how many levels to widen the view. 
+If the parameter is not specified, it defaults to 1. If you try to widen more 
+levels than the depth of the current sub-tree root node, the widening will be 
+silently limited to its depth.
+.P
+This command is only available when tdl is being run interactively, i.e. when 
+you have a tdl prompt. It is not available directly from the shell 
+(where it wouldn't make much sense). 
+
+.SH Completion facilities
+.PP
+
+When tdl has been compiled to use the readline library, the interactive mode 
+supports a number of completion functions, activated with the <tab> key.
+.P
+In particular, the following are supported:
+
+.B Command completion. 
+If <tab> is pressed when the command line is empty, a list of possible commands 
+will be shown. If <tab> is pressed when a partial command has been typed, the 
+command will be completed immediately if possible, otherwise a list of commands 
+matching the already-typed prefix will be shown.
+.P
+.B Help completion. 
+If help or usage is already in the buffer, a list of commands will be shown 
+(as above). The <tab> completion works in the same way to complete the name of 
+the command you want a help summary for.
+.P
+.B Priority completion. 
+If list or priority is at the start of the input buffer and the current word 
+starts with a letter, tdl will try to complete the name of a priority level if 
+<tab> is pressed.
+.P
+.B Open task completion. 
+If done is at the start of the input buffer, hitting <tab> will show a list of 
+task indices that are still open. If part of an index has already been typed, 
+the open task indices for which the typed characters are a prefix will be shown.
+.P
+.B Postpone completion. 
+If postpone is at the start of the input buffer, hitting <tab> will show a list 
+of tasks that may be postponed. Tasks marked done are excluded. If open is at 
+the start of the input buffer, hitting <tab> will show a list of tasks that may 
+be opened.
+.P
+.B Parameter hints. 
+If some other command is at the start of the input buffer and <tab> is pressed, 
+tdl will show a one-line summary of that command's parameters. 
+
+.SH DATE SPECIFICATIONS
+.PP
+The commands
+.BR add ,
+.BR done ,
+.BR purge ,
+.BR report ,
+take arguments defining dates (with add and done it is optional).  Dates may be
+specified in several formats, shown by the following examples:
+.PP
+.TS
+tab(&);
+l l.
+\-1h & exactly 1 hour ago
+\-2d & exactly 2 days ago
++1w & exactly 1 week in the future
++1m & exactly 1 month (30 days) in the future
++2y & exactly 2 years in the future
+\-1d\-0815 & 08:15am yesterday
++1d\-08 & 8am tomorrow
++1w\-08 & 8am on the same day as today next week
++6h\-08 & 8am on the day containing the time 6 hours ahead of now
+\.\-08 & 8am today
+\.\-20 & 8pm today
+20011020 & absolute : 12 noon on 20th October 2001
+011020 & absolute : 12 noon on 20th October 2001 (current century)
+1020 & absolute : 12 noon on 20th October 2001 (current century and year)
+20 & absolute : 12 noon on 20th October 2001 (current century, year and month)
+20011020\-081500 & absolute : 08:15am on 20th October 2001
+20011020\-0815 & absolute : 08:15am on 20th October 2001 (seconds=0)
+20011020\-08 & absolute : 08:00am on 20th October 2001 (minutes=seconds=0)
+011020\-08 & absolute : 08:00am on 20th October 2001 (minutes=seconds=0, current century)
+etc & (see below)
+\-sun & 12 noon on the previous Sunday
++sat & 12 noon on the following Saturday
++sat\-08 & 8am on the following Saturday
+\-tue\-0815 & 08:15am on the previous Tuesday
+etc & (see below)
+.TE
+.PP
+In the 'all-numeric' format, the rule is that dates can have fields omitted
+from the start (assumed to be the current value), and times can have fields
+omitted from the end (assumed to be zero, except if the hours figure is missing
+it is assumed to be 12, since most work is done in the day.)
+.PP
+In the 'weekday and time' format, the time rule is the same: missing minutes
+and seconds are taken as zero and missing hours as 12.  If the weekday is the
+same as today, the offset is always 7 days in the required direction.  If the
+weekday is not the same as today, the offset will always be less than 7 days in
+the required direction.
+.PP
+In the 'relative' format, when a time is included as well, the procedure is as
+follows.  First the time is determined which is the given number of hours, days
+etc away from the current time.  Then the specified time on that day is used.
+The main use for this is to specify times like '8am yesterday'.  Obviously some
+of the more uses of this mode are rather far-fetched.
+.PP
+For the weekday and relative formats, the sign is actually optional.  The
+default sign (implying past (-) or future (+)) will then be assumed depending on
+the command as shown below:
+
+.PP
+.TS
+tab(&);
+l l l.
+Command & Default & Reason
+_
+add & + & Add entries with deferred start times
+done & - & Entries have been completed at some time in the past
+report & - & Reporting on earlier completed tasks not future ones
+purge & - & Tasks won't be completed in the future, so no need to purge future ones
+.TE
+
+.SH HOMEPAGE
+.PP
+The homepage for
+.B tdl
+on the internet is http://www.rc0.org.uk/tdl/
+.SH AUTHOR
+.PP
+The author is Richard P. Curnow <rc@rc0.org.uk>.
+.SH ACKNOWLEDGEMENTS
+.PP
+I got the idea from a program called devtodo.  I liked what that program did
+and the command line approach to using it, but I ran into lots of compilation
+problems with it on older C++ installations.  The path of least resistance
+turned out to be to hack up a C program to do a similar job.
+
+.SH ENVIRONMENT
+.TP
+TDL_DATABASE
+If this variable is set, it defines the name of the file to use for holding the
+database of tasks.  If the variable is not set, the search approach described
+in the FILES section is used.
+.TP
+TDL_LIST_MONOCHROME
+If this variable is set, the output from the
+.B list
+command is produced in monochrome instead of colour (the default).
+.SH FILES
+.TP
+ ./.tdldb, ../.tdldb, ../../.tdldb, ...
+If the TDL_DATABASE environment variable is not present, the file .tdldb in the
+current directory is used, if that is present.  If not, the same file in the
+parent directory is used, and so on, until the root directory of the filesystem
+is reached.  If the database is still not found, a new one will be created in
+the current directory (except for options that don't modify the database, such
+as list, help and version.)
+.PP
+If you want to have a .tdldb file in 
+.I every
+directory, the suggested approach is to set the TDL_DATABASE environment variable to "./.tdldb".  So in a Bourne-like shell (sh, bash, zsh, ksh etc), you'd write
+.IP
+TDL_DATABASE=./.tdldb
+.br
+export TDL_DATABASE
+.PP
+and in a C-like shell (csh, tcsh etc) you'd write
+.IP
+setenv TDL_DATABASE ./.tdldb
+.PP
+If you want to share .tdldb files between directory hierarchies in some non-standard way, the suggested approach is to use symbolic links to do this, for example:
+.IP
+cd project1
+.br
+ln \-s ../project2/.tdldb .
+
+.SH BUGS
+Please report them to the author.
+
+.SH SEE ALSO
+The full documentation for tdl is maintained as a Texinfo manual. If the info and tdl
+programs are properly installed at your site, the command
+.IP
+info tdl
+.PP
+should give you access to the complete manual.
+
diff --git a/tdl.h b/tdl.h
new file mode 100644 (file)
index 0000000..6faa193
--- /dev/null
+++ b/tdl.h
@@ -0,0 +1,187 @@
+/*
+   $Header: /cvs/src/tdl/tdl.h,v 1.22.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001-2004  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#ifndef TDL_H
+#define TDL_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+
+enum Priority {
+  PRI_UNKNOWN = 0,
+  PRI_VERYLOW = 1,
+  PRI_LOW     = 2,
+  PRI_NORMAL  = 3,
+  PRI_HIGH    = 4,
+  PRI_URGENT  = 5
+};
+
+/* Magic time_t value used in the 'done' field to signify an ignored item */
+#define IGNORED_TIME 1
+
+#define POSTPONED_TIME LONG_MAX
+
+struct node;
+
+struct links {
+  struct node *next;
+  struct node *prev;
+};
+
+struct node {
+  struct links chain;
+  struct links kids;
+  char *text;
+  struct node *parent;
+  enum Priority priority;
+  long arrived;
+  long required_by;
+  long done;
+  char *scratch; /* For functions to attach stuff to nodes */
+  int iscratch;  /* More scratch space */
+  char flag;
+};
+
+extern struct links top;
+
+/* Memory macros */
+
+#define new_string(s) strcpy((char *) malloc(1+strlen(s)), (s))
+#define extend_string(x,s) (strcat(realloc(x, (strlen(x)+strlen(s)+1)), s))
+#define new(T) (T *) malloc(sizeof(T))
+#define new_array(T, n) (T *) malloc(sizeof(T) * (n))
+#define grow_array(T, n, oldX) (T *) ((oldX) ? realloc(oldX, (sizeof(T) * (n))) : malloc(sizeof(T) * (n)))
+
+typedef char** (*Completer)(char *, int);
+
+/* Command table (shared between dispatcher and readline completer) */
+struct command {/*{{{*/
+  char *name; /* add, remove etc */
+  char *shortcut; /* tdla etc */
+  int (*func)(char **); /* ptr to function that actually does the work for this cmd */
+  char *descrip; /* One line description */
+  char *synopsis; /* Description of parameters */
+  char ** (*completer)(char *, int); /* Function to generate completions */
+  unsigned char  dirty; /* 1 if operation can dirty the database, 0 if it leaves it clean */
+  unsigned char  load_db; /* 1 if cmd requires current database to be loaded first */
+  unsigned char  matchlen; /* number of characters to make command unambiguous */
+  unsigned char  interactive_ok; /* 1 if OK to use interactively. */
+  unsigned char  non_interactive_ok; /* 1 if OK to use from command line */
+};
+/*}}}*/
+
+extern struct command cmds[];
+extern int n_cmds;
+
+/* Function prototypes. */
+
+/* In io.c */
+int read_database(FILE *in, struct links *to);
+void write_database(FILE *out, struct links *from);
+struct node *new_node(void);
+void append_node(struct node *n, struct links *l);
+void append_child(struct node *child, struct node *parent);
+void clear_flags(struct links *x);
+int has_kids(struct node *x);
+
+/* In list.c */
+void do_indent(int indent);
+void do_bullet_indent(int indent);
+int process_list(char **x);
+
+/* In report.c */
+int process_report(char **x);
+long read_interval(char *xx);
+
+/* In util.c */
+int count_args(char **x);
+int include_descendents(char *x);
+struct node *lookup_node(char *path, int allow_zero_index, struct node **parent);
+enum Priority parse_priority(char *priority, int *error);
+void clear_flags(struct links *x);
+void mark_all_descendents(struct node *n);
+int has_kids(struct node *x);
+struct node *new_node(void);
+void free_node(struct node *x);
+void append_node(struct node *n, struct links *l);
+void prepend_node(struct node *n, struct links *l);
+void prepend_child(struct node *child, struct node *parent);
+
+/* In done.c */
+int has_open_child(struct node *y);
+int process_done(char **x);
+int process_ignore(char **x);
+int process_undo(char **x);
+
+/* In add.c */
+int process_add(char **x);
+int process_log(char **x);
+int process_edit(char **x);
+int process_postpone(char **x);
+int process_open(char **x);
+int process_defer(char **x);
+
+/* In remove.c */
+int process_remove(char **x);
+
+/* In purge.c */
+int process_purge(char **x);
+
+/* In move.c */
+int process_above(char **x);
+int process_below(char **x);
+int process_into(char **x);
+
+/* In impexp.c */
+int process_export(char **x);
+int process_import(char **x);
+int process_clone(char **x);
+int process_copyto(char **x);
+
+/* In dates.c */
+time_t parse_date(char *d, time_t ref, int default_positive, int *error);
+
+/* In main.c */
+void dispatch(char **argv);
+void free_database(struct links *x);
+void load_database_if_not_loaded(void);
+
+/* In inter.c */
+void interactive(void);
+char *interactive_text(char *prompt, char *initval, int *is_blank, int *error);
+char **complete_help(char *, int);
+char **complete_list(char *, int);
+char **complete_priority(char *, int);
+char **complete_postpone(char *, int);
+char **complete_open(char *, int);
+char **complete_done(char *, int);
+
+/* In narrow.c */
+struct node *get_narrow_top(void);
+char *get_narrow_prefix(void);
+int process_narrow(char **x);
+int process_widen(char **x);
+
+#endif /* TDL_H */
+          
diff --git a/tdl.spec b/tdl.spec
new file mode 100644 (file)
index 0000000..776c973
--- /dev/null
+++ b/tdl.spec
@@ -0,0 +1,41 @@
+Summary: A to-do list manager
+Name: tdl
+Version: 1.6-pre1
+Release: 1
+Copyright: GPL
+Source: tdl-%{version}.tar.gz
+Group: Applications/Utilities
+Packager: Richard P. Curnow <rc@rc0.org.uk>
+BuildRoot: %{_tmppath}/%{name}-%{version}-root-%(id -u -n)
+Requires: readline
+
+%description
+tdl is a console to-do list manager.  You can run its subcommands direct from
+the command line, or enter an interactive mode which uses the readline library.
+
+%prep
+%setup -q
+
+%build
+configure
+make %{?_smp_mflags} CC=gcc CFLAGS=-O2 prefix=%{_prefix}
+make %{?_smp_mflags} tdl.txt prefix=%{_prefix}
+make %{?_smp_mflags} tdl.info prefix=%{_prefix}
+make %{?_smp_mflags} tdl.html prefix=%{_prefix}
+
+%install
+rm -rf %{buildroot}
+make install DESTDIR=%{buildroot} prefix=%{_prefix} bindir=%{buildroot}%{_bindir} mandir=%{buildroot}%{_mandir}
+mkdir -p %{buildroot}%{_infodir}
+cp tdl.info* %{buildroot}/%{_infodir}
+
+%clean
+rm -rf %{buildroot}
+
+%files
+%defattr(-,root,root,-)
+%doc README tdl.txt tdl.html
+%{_bindir}/tdl*
+%{_mandir}/man[^3]/*
+%{_infodir}/*
+
diff --git a/tdl.texi b/tdl.texi
new file mode 100644 (file)
index 0000000..06413dd
--- /dev/null
+++ b/tdl.texi
@@ -0,0 +1,1831 @@
+\input texinfo
+@c {{{ Main header stuff
+@afourwide
+@paragraphindent 0
+@setfilename tdl.info
+@settitle User guide for the tdl program
+@c @setchapternewpage off
+
+@ifinfo
+@dircategory Utilities
+@direntry
+* tdl: (tdl).                  To-do-list management utility
+@end direntry
+@end ifinfo
+
+@titlepage
+@sp 10
+@title The tdl program
+@subtitle This manual describes how to use
+@subtitle the tdl program for managing to-do-lists.
+@author Richard P. Curnow
+@page
+@vskip 0pt plus 1filll
+Copyright @copyright{} 2001,2002,2003,2004,2005 Richard P. Curnow
+@end titlepage
+
+@contents
+@c }}}
+
+@ifnottex
+@node Top
+@top
+@menu
+* Introduction::    What the tdl program does
+* Installation::    Compiling and installing the software
+* Usage::           Quickstart guide and examples of use
+* Reference::       Reference section
+@end menu
+@end ifnottex
+
+
+@c {{{ Introduction
+@node Introduction
+@chapter Introduction
+tdl is a lightweight program for managing a 'to-do' list of pending jobs that
+you have.
+    
+It supports the following features :
+@itemize @bullet
+@item 1 database per directory, or per tree of directories (tdl searches up
+through parent directories to find the database, so you can have one per
+project, for example.)
+
+@item add new entries, mark them done, edit the text of entries
+
+@item add a new entry and immediately mark it done (e.g. to log tasks you did
+which you tackled immediately you got them.)
+
+@item organise the entries in a tree structure (sub-tasks of other tasks etc)
+
+@item move the tasks around and re-organise the hierarchy.
+
+@item list the tasks in the database (default listing excludes 'done' tasks,
+but these can be shown too if desired).  The listing is in colour by default,
+with monochrome output as an option.
+
+@item allows entries to be prioritised (priorities shown in different colours
+on listing).  The listing can selectively show only entries at or above a given
+priority level.
+
+@item the start time for tasks can be set, to allow for 'deferred' tasks with
+start times in the future.  Such tasks are excluded from the default listing.
+
+@item tasks can be marked 'postponed' to avoid them cluttering up the normal
+listing.
+
+@item track date added and date completed for each task
+
+@item generate report of tasks completed in a given earlier time period (useful
+if you have to produce a weekly summary of your work done, for example)
+
+@item import and export entries, to allow splitting and merging of databases.
+
+@item written in C
+
+@item runs on a Linux console or in a terminal window.  It currently generates
+a coloured listing (monochrome is an option), so a colour xterm or rxvt is
+preferred.
+
+@item tdl can run a single sub-command direct from the command line, or it can
+run in an interactive mode where several sub-commands can be used within a
+single run.  If the GNU readline library is available at compile time, the
+interactive mode features command line editing and various completion and usage
+hint facilities.
+@end itemize
+@c }}}
+@c {{{ Installation
+@node Installation
+@chapter Installation
+This section discusses installation
+
+@menu
+* Build from source:: Installing tdl from source code
+* Packaging::         Notes for package builders
+@end menu
+
+@c {{{ Build from source
+@node Build from source
+@section Installing tdl from source code
+The procedure for installing tdl from source code is as follows:
+
+@enumerate
+@item @strong{Unpack the sources}
+@example
+gunzip < tdl-1.0.tar.gz | tar xvf -
+cd tdl-1.0
+@end example
+
+@item @strong{Configure the makefile}
+
+tdl does not use a @code{./configure} mechanism (yet!) to configure options.
+You have to manually edit @file{Makefile}.  The variables you may want to edit are
+
+@table @samp
+@item CC
+The choice of C compiler
+@item CFLAGS
+The choice of flags to pass to the C compiler
+@item prefix
+The parent directory where the binaries and documentation will be installed.
+You'd normally set this to @file{/usr/local} or @file{/usr}, unless you use a
+stow or stow-like approach, or are building a distribution package.
+@item INC_READLINE
+If you want readline support in the interactive mode (highly recommended),
+uncomment the appropriate line in the Makefile, and if necessary edit the path
+for the include directory where @file{readline.h} and @file{history.h} can be
+found.  Note that a @file{readline} subdirectory is assumed to be suffixed onto
+whatever path you define.
+@item LIB_READLINE
+Likewise, edit the path for the directory where @file{libreadline} and
+@file{libhistory} can be found.
+@end table
+
+@item @strong{Compile the sources}
+@example
+make
+@end example
+
+@item @strong{Install the software}
+@example
+make install
+@end example
+
+@item (Optional) @strong{Build the documentation}
+
+This assumes you have the @file{makeinfo} and @file{tex} tools on your path.
+@example
+make docs
+@end example
+
+@item (Optional) @strong{Install the documentation}
+
+Currently, there are no Makefile targets for this.  Pick the documentation
+formats you want to keep and install them manually to the appropriate places.
+@end enumerate
+@c }}}
+@c {{{ Packaging
+@node Packaging
+@section Notes for package builders
+For building a Slackware package, you could follow the steps above except for
+the installation
+
+@example
+vi Makefile
+make
+make docs
+mkdir pkg
+make install DESTDIR=./pkg
+(copy appropriate docs into subdirectories of pkg)
+cd pkg
+makepkg tdl.tgz
+@end example
+
+Packagers for other distributions may be able to adapt this.  (The point the
+example is making is that @file{Makefile} contains support for using a variable
+@var{DESTDIR} in this way.)
+
+An example spec file for RedHat packaging is in the file
+@file{tdl.spec.sample}.
+
+@c }}}
+
+@c }}}
+
+@c {{{ Usage
+@node Usage
+@chapter Usage
+This section contains examples of using tdl.
+
+@menu
+* Getting started::   Getting started with tdl
+@end menu
+
+@c {{{ Getting started
+@node Getting started
+@section Getting started
+This section shows how you can get started with tdl.
+
+Let's assume you have a working directory for a project, and you want to
+maintain a to-do list for things you need to do on that project.  Let's assume
+the working directory for the project is @file{/home/foobar/myproject}.  Then
+you'd start by entering the following commands into your shell:
+
+@example
+% cd /home/foobar/myproject
+% tdl create
+@end example
+
+Now, lets say you have some tasks to keep track of:
+
+@example
+% tdl
+tdl> add "Write user guide"
+tdl> add "Write release notes"
+tdl> add "Fix bug where empty data file causes core dump"
+tdl> exit
+%
+@end example
+
+The above sequence will add 3 tasks to your newly created database.  A few days
+later, you might come back to the project and think "Hmmm.  What did I need to
+do next?"  You can enter
+
+@example
+% tdl list
+1 Write user guide
+2 Write release notes
+3 Fix bug where empty data file causes core dump
+%
+@end example
+
+This shows another feature of tdl.  If you pass a sub-command (and its
+arguments, if any) on the tdl command line, tdl will execute just that command,
+and return you to your shell prompt.  However, if you run tdl with no
+arguments, it will go into its interactive mode.  For a single command like
+@command{list} in this situation, you'd probably find the direct method
+quicker.
+
+Suppose you fix the bug.  Then you could enter
+
+@example
+% tdl done 3
+@end example
+
+after which the list command would only show the first two tasks as still being
+open, like this:
+
+@example
+% tdl list
+1 Write user guide
+2 Write release notes
+%
+@end example
+
+The @command{add}, @command{list} and @command{done} commands may be all that
+you need in some cases.  However, another useful command is @command{report},
+which will summarise all the tasks you completed in a given period.  For
+example, you could list everything you completed in the last 7 days like this
+
+@example
+% tdl report 7d
+- Fix bug where empty data file causes core dump
+%
+@end example
+
+The other commands in tdl are mostly to do with changing the order of tasks in
+the database, assigning them priorities, and so on.
+@c }}}
+
+@c }}}
+
+@node Reference
+@chapter Reference
+@menu
+* Start and Exit::      Starting and exiting tdl
+* Command line flags::  Flags that can be used from the shell command line
+* Node specification::  How to reference existing nodes
+* Command list::        Alphabetical list of all commands
+* Shortcuts::           Quick access to tdl commands from the shell
+* Interrupting::        How to make tdl stop whilst it is running
+* Location::            How tdl finds the database file to use
+* Completion::          Completion facilities
+* Datespec::            How dates are specified
+* Backup file::         How tdl saves a backup copy of the database
+* Index specification:: How indices can be specified
+@end menu
+
+@c {{{ Start and Exit
+@node Start and Exit
+@section Starting and exiting tdl
+tdl has a set of functions that can be accessed in two different ways:
+
+@itemize @bullet
+@item Directly from the command line
+@item Interactively
+@end itemize
+
+In the 'direct' method, the function and its arguments are provided on the
+command line.  This mode is useful if you only want to perform a single
+operation.  An example
+
+@example
+% tdl add "A task"
+%
+@end example
+
+The 'interactive' method is entered when the tdl command is run with no
+arguments.  In this mode, many tdl operations may be performed within a single
+run of the program.  This avoids loading and saving the database for each
+operation, which may have a small performance benefit.  However, if the program
+is compiled with the readline library, the @key{tab} key will provide various
+completion functions.  An example
+
+@example
+% tdl
+tdl> add "A task"
+tdl> exit
+%
+@end example
+
+When in interactive mode, these methods can be used to exit and return to the shell:
+
+@itemize @bullet
+@item The @command{exit} command (@pxref{exit command})
+@item Hitting @key{Ctrl-D} (i.e. end of file on stdin)
+@item Hitting @key{Ctrl-C}, @key{Ctrl-\} etc.  The associated signals are
+caught by tdl and it will attempt to save the database.  However, this method
+is more risky than the first two.
+@item The @command{quit} command (@pxref{quit command}).  @strong{Caution:} this
+does not save the modified database back to the disk.  Only use it if you want
+to discard all changes made in this tdl run.
+@end itemize
+
+@c }}}
+
+@c {{{ Command line flags
+@node Command line flags
+@section Command line flags
+@menu
+* -q::              Be quiet
+* -R::              Open the database read-only
+* -u::              Forced unlock
+@end menu
+
+@c {{{ Quiet mode
+@node -q
+@subsection Quiet mode
+If the @command{-q} flag is specified, various warning and informational
+messages will be suppressed.
+
+@example
+% tdl -q list
+@end example
+@c }}}
+@c {{{ Read-only mode
+@node -R
+@subsection Read-only mode
+If the @command{-R} flag is specified, the database will be opened in read-only
+mode.  In this case, the database will not be locked, and all commands that
+modify the database will be barred.
+
+This flag can be used together with the @command{-q} flag if required.
+
+@example
+% tdl -R list
+% tdl -qR list
+@end example
+@c }}}
+@c {{{ Forced unlock
+@node -u
+@subsection Forced unlock
+If the @command{-u} flag is specified, the database's lockfile will be removed
+if it is found.  This is mainly intended for use when an earlier run has left
+the database locked after a crash.
+
+Care shoule be taken that the database is not currently being accessed by a
+live process, otherwise updates could be lost by one or other of the processes.
+@example
+% tdl -u list
+@end example
+@c }}}
+@c }}}
+@c {{{ Node specification
+@node Node specification
+@section Node specification
+Many commands in tdl require you to refer to an existing entry, for example
+@itemize @bullet
+@item to add a child entry to an existing entry
+@item to mark an entry as being done
+@item and so on
+@end itemize
+
+There are two ways you can refer to existing nodes
+@itemize @bullet
+@item by the numeric path (as shown by @command{tdl list}).
+@item by negative numeric path (which is used to refer to a node counted from
+the end of its parent)
+@item by the start of the node's text, as long as enough is specified to
+identify the node unambiguously amongst its peers.  In this case, the node must
+be preceded by a single '.' for many commands.
+@end itemize
+
+For example, if the database contains
+@example
+tdl> list
+1 foo
+   1.1 aaa
+   1.2 bbb
+2 fee
+   2.1 ccc
+   2.2 ddd
+3 bar
+   3.1 eee
+   3.2 fff
+@end example
+
+then @samp{tdl list 1} will show
+
+@example
+tdl> list
+1 foo
+   1.1 aaa
+   1.2 bbb
+@end example
+
+and @samp{tdl list .fe} will show
+@example
+.fe fee
+   .fe.1 ccc
+   .fe.2 ddd
+@end example
+
+@samp{tdl list .f} will fail since it is ambiguous which node (1 or 2) is being referenced.
+
+@samp{tdl list -- -1} will show
+@example
+-1 bar
+   -1.1 eee
+   -1.2 fff
+@end example
+
+(note that @samp{tdl list -1} has a different meaning (show 1 level and summarise subordinate levels), and shows
+@example
+1 [2/2] foo
+2 [2/2] fee
+3 [2/2] bar
+@end example
+
+@c }}}
+@c {{{ Command list
+@node Command list
+@section Alphabetical list of all commands
+This section describes each of the tdl subcommands.
+@menu
+* above command::   Move entries above (before) another entry
+* add command::     Add a new entry to the database
+* after command::   Move entries after (below) another entry
+* before command::  Move entries before (above) another entry
+* below command::   Move entries below (after) another entry
+* clone command::   Make a deep copy of one or more entries
+* copyto command::  Insert a deep copy of one or more entries into an existing entry
+* create command::  Create a new database in the current directory
+* defer command::   Modify starting time of one or more entries
+* delete command::  Remove 1 or more entries from the database
+* done command::    Mark 1 or more entries as done
+* edit command::    Change the text and/or start time of an entry
+* exit command::    Exit program, saving database
+* export command::  Export entries to another database
+* help command::    Display help information
+* ignore command::  Postpone/partially-remove 1 or more entries
+* import command::  Import entries from another database
+* into command::    Move entries to end of new parent
+* list command::    List entries in database (default from top node)
+* ls command::      List entries in database (default from top node)
+* log command::     Add a new entry to the database, mark it done as well
+* moveto command::  Move entries to end of new parent
+* narrow command::  Restrict actions to part of the database
+* open command::    Move 1 or more entries out of postponed state
+* postpone command::Make 1 or more entries postponed
+* priority command::Change the priority of 1 or more entries
+* purge command::   Remove old done entries in subtrees
+* quit command::    Exit program, NOT saving database
+* remove command::  Remove 1 or more entries from the database
+* report command::  Report completed tasks in interval
+* revert command::  Discard changes and reload previous database from disc
+* save command::    Save the database to disc and continue working
+* undo command::    Mark 1 or more entries as not done (cancel effect of 'done')
+* usage command::   Display help information
+* version command:: Display program version
+* which command::   Display filename of database being used
+* widen command::   Widen the part of the database to which actions apply
+@end menu
+@c }}}
+@c {{{ <--COMMANDS-->
+@c {{{ above command
+@node above command
+@subsection above command
+The @command{above} command is one of the commands used for re-ordering the
+entries in the database.  The @command{above} and @command{before} commands are
+synonymous.
+
+The arguments of the @command{above} comamnd are:
+
+@example
+tdl> above <index_to_insert_above> <index_to_move> ...
+@end example
+
+The first argument is the index of the entry above which the other entries are
+to be moved.  The entries corresponding the 2nd index onwards will be placed in
+argument order above the first entry.
+
+An example:
+@example
+tdl> list
+1 Task A
+2 Task B
+3 Task C
+4 Task D
+tdl> above 1 2 4 3
+tdl> list
+1 Task B
+2 Task D
+3 Task C
+4 Task A
+tdl>
+@end example
+
+You can move entries between levels in the hierarchy, with the restriction that
+you cannot move a node so that its new parent would be a descendent of itself.
+
+If you want to move entries to the end of the list (i.e. above the bottom of
+the list), you can use a zero as the index of the reference entry, for example
+
+@example
+tdl> list
+1 Task A
+   1.1 Task A_A
+   1.2 Task A_B
+2 Task B
+3 Task C
+tdl> above 1.0 3 2
+tdl> list
+1 Task A
+   1.1 Task A_A
+   1.2 Task A_A
+   1.3 Task C
+   1.4 Task B
+tdl>
+@end example
+
+@c }}}
+@c {{{ add command
+@node add command
+@subsection add command
+The @command{add} command is run as follows
+
+@example
+tdl> add [@@datespec] [parent-index] [priority] "Text for node"
+@end example
+
+In the simplest case of adding a new top-level entry to the database, with
+normal priority, starting now, this could be
+
+@example
+tdl> add "Wash the dog"
+@end example
+
+In a more complex case, to add a high priority entry underneath entry index 1,
+with the new entry coming live at 11a.m. next Friday, this would be
+
+@example
+tdl> add @@+fri-11 1 hi "Wash the dog"
+@end example
+
+If you have several entries to add at once, you can go into an @emph{add} mode.
+Enter a blank line to get back to the @t{tdl>} prompt.
+
+@example
+tdl> add
+add> Wash the dog
+add> Wash the car
+add>
+tdl>
+@end example
+
+To add an entry direct from your shell, there is an additional shortcut
+(assuming the appropriate symbolic link was created during the installation
+process):
+
+@example
+% tdla "Wash the dog"
+%
+@end example
+
+@c }}}
+@c {{{ after command
+@node after command
+@subsection after command
+The @command{after} and @command{below} commands are synonymous.  See the
+description of @command{below} (@pxref{below command}).
+@c }}}
+@c {{{ before command
+@node before command
+@subsection before command
+The @command{above} and @command{before} commands are synonymous.  See the
+description of @command{above} (@pxref{above command}).
+@c }}}
+@c {{{ below command
+@node below command
+@subsection below command
+The @command{below} command is one of the commands used for re-ordering the
+entries in the database.  The @command{below} and @command{after} commands are
+synonymous.
+
+The arguments of the @command{below} command are:
+
+@example
+tdl> below <index_to_insert_below> <index_to_move> ...
+@end example
+
+The first argument is the index of the entry below which the other entries are
+to be moved.  The entries corresponding the 2nd index onwards will be placed in
+argument order above the first entry.
+
+An example:
+@example
+tdl> list
+1 Task A
+2 Task B
+3 Task C
+4 Task D
+tdl> below 4 2 1 3
+tdl> list
+1 Task D
+2 Task B
+3 Task A
+4 Task C
+tdl>
+@end example
+
+You can move entries between levels in the hierarchy, with the restriction that
+you cannot move a node so that its new parent would be a descendent of itself.
+This is similar to the description for the @command{above} command
+(@pxref{above command}).
+
+@c }}}
+@c {{{ clone command
+@node clone command
+@subsection clone command
+The @command{clone} command can be used to make a deep copy of one or more
+entries and add them as new top-level entries in the database.  You might use
+this if you have a task with a set of subtasks, and find that the same subtasks
+apply to some new task.  You could copy the first task, and edit the new
+top-level task to change its text.
+
+The arguments of the @command{clone} command are:
+
+@example
+tdl> clone <index_to_clone> ...
+@end example
+
+An example is:
+@example
+tdl> list
+1 Wash things
+  1.1 Car
+  1.2 Dog
+tdl> clone 1
+tdl> edit 2 "Polish things"
+tdl> list
+1 Wash things
+  1.1 Car
+  1.2 Dog
+2 Polish things
+  2.1 Car
+  2.2 Dog
+@end example
+
+If you want the cloned entries to be children of an existing entry, use the
+@command{copyto} command (@pxref{copyto command}).
+@c }}}
+@c {{{ copyto command
+@node copyto command
+@subsection copyto command
+The @command{copyto} command is very similar to the @command{clone} command
+(@pxref{clone command}).  The difference is that @command{copyto} inserts the
+newly created entries as children of an existing entry, rather than making them
+new top level entries.
+
+The arguments of the @command{copyto} command are:
+
+@example
+tdl> copyto <new_parent_index> <index_to_clone> ...
+@end example
+
+An example is:
+@example
+tdl> list
+1 Household jobs
+  1.1 Wash things
+    1.1.1 Car
+    1.1.2 Dog
+tdl> copyto 1 1.1
+tdl> edit 1.2 "Polish things"
+@end example
+@c }}}
+@c {{{ create command
+@node create command
+@subsection create command
+The @command{create} command can only be used direct from the shell command
+line.  It is @strong{not} supported when tdl is used in its interactive
+mode.@footnote{This is to avoid confusion over which database file is being
+accessed if @command{create} were used after other commands had already
+been used in the same session.}
+
+Usually, the @command{create} command will create a new @file{.tdldb} file in
+the current directory.  However, if the @var{TDL_DATABASE} environment
+variable is set when tdl is run, the path specified by that variable
+will be used instead and the database will be created there.  In both
+cases, the @command{create} command will refuse to over-write an
+existing database; an error message will be generated if that is
+attempted.
+
+@example
+% tdl create
+@end example
+@c }}}
+@c {{{ defer command
+@node defer command
+@subsection defer command
+The @command{defer} command is used to modify the start-time of one or more existing entries.  Its argument structure is
+
+@example
+tdl> defer @@<datespec> <entry_index>[...] ...
+@end example
+
+where the @samp{@@} on the datespec is optional because the argument is
+required, although the @samp{@@} can be included for consistency with other
+commands where a datespec is optional.
+
+An example of use is 
+
+@example
+tdl> defer @@+fri 1 2.1... 5
+@end example
+
+which defers entries 1, 2.1 and all its children, and 5 until the following
+Friday.
+
+To list deferred entries, use @command{list -p}.
+
+To defer entries indefinitely, see @ref{postpone command}.
+
+To re-activate deferred or postponed entries, see @ref{open command}.
+
+@c }}}
+@c {{{ delete command
+@node delete command
+@subsection delete command
+This command is synonymous with the @command{remove} command (@pxref{remove command}).
+
+The argument structure is
+@example
+delete <entry_index>[...] ...
+@end example
+
+@c }}}
+@c {{{ done command
+@node done command
+@subsection done command
+The @command{done} command is run as follows
+
+@example
+done [@@<datespec>] <entry_index>[...] ...
+@end example
+
+The @command{done} command is used to mark one or more tasks as completed.  Any
+number of task indices may be specified.
+
+The effects are as follows:
+
+@itemize @bullet
+@item
+The entries no longer appear on the default listing (produced by the
+@command{list} command without the @samp{-a} option).
+@item
+The entries are eligible to appear on the report list (@pxref{report command})
+@item
+The entries are eligible for removal by the purge command (@pxref{purge
+command})
+@end itemize
+
+If the string "..." is appended to an index, it means that entry and all its
+descendents.  This provides a quick way to mark a whole sub-tree of tasks as
+being completed.
+
+No entry may be marked 'done' if it has any children that are still 'open'
+(i.e. not marked 'done').  (The @samp{...} form of the command marks the
+deepest entries first to bypass this.)
+
+@c }}}
+@c {{{ edit command
+@node edit command
+@subsection edit command
+The @command{edit} command is used to modify the text of an existing entry.
+Its argument structure is
+
+@example
+tdl> edit <entry_index> [<new_text>]
+@end example
+
+A single <entry-index> must be given.  If <new-text> is provided, this replaces
+the text describing the specified entry.  If no <new-text> is provided, you
+will be prompted with the old text to edit interactively.  (This is only useful
+if the GNU readline library has been linked in.)
+
+An examples follows.
+
+To change the text for the entry with index 1,
+@example
+tdl> edit 1 "New description"
+@end example
+
+@example
+tdl> list
+1 Wash the dog
+tdl> edit 1
+edit (1)> Wash the dog   (edit 'dog' to 'cat')
+tdl> list
+1 Wash the cat
+tdl>
+@end example
+
+(Note, in earlier versions, @command{edit} could be used to change the
+start-time of one or more entries.  This is now handled by the @command{defer}
+command (@pxref{defer command}).)
+
+@c }}}
+@c {{{ exit command
+@node exit command
+@subsection exit command
+The @command{exit} command is used to exit from tdl when it is used in
+interactive mode.  The @command{exit} command is not available in the command
+line mode, where it would not make sense.  An example:
+
+@example
+tdl> exit
+%
+@end example
+
+The @command{exit} command writes any pending updates to the database before
+exiting.  (Compare the @command{quit} command (@pxref{quit command}), which
+@strong{loses} all updates made during the current tdl run.)
+@c }}}
+@c {{{ export command
+@node export command
+@subsection export command
+The @command{export} command is run as follows
+
+@example
+export <filename> <entry_index> ...
+@end example
+
+It is used to export one or more tasks (and their subtasks) to another tdl
+database file.  Perhaps you were keeping all your projects' to-do lists in one
+combined file, and decide you want to separate the list for a particular
+project.
+
+An example would be
+
+@example
+tdl> list
+1 Tasks for project X
+2 Tasks for project Y
+   2.1 Write manual
+   2.2 Write release notes
+tdl> export /home/foobar/project_y/.tdldb 2.1 2.2
+tdl> remove 2...
+tdl> exit
+@end example
+
+@c }}}
+@c {{{ help command
+@node help command
+@subsection help command
+The @command{help} command displays help information.  When run without
+arguments, a list of valid commands is produced.  Note, this list is slightly
+different depending on whether the @command{help} command is used through the
+interactive readline interface or straight from the shell.
+
+@example
+tdl> help
+tdl, Copyright (C) 2001-2004 Richard P. Curnow
+tdl comes with ABSOLUTELY NO WARRANTY.
+This is free software, and you are welcome to redistribute it
+under certain conditions; see the GNU General Public License for details.
+
+above    : Move entries above (before) another entry
+add      : Add a new entry to the database
+after    : Move entries after (below) another entry
+below    : Move entries below (after) another entry
+before   : Move entries before (above) another entry
+done     : Mark 1 or more entries as done
+edit     : Change the text and/or start time of an entry
+exit     : Exit program, saving database
+export   : Export entries to another database
+help     : Display help information
+import   : Import entries from another database
+into     : Move entries to end of new parent
+list     : List entries in database (default from top node)
+log      : Add a new entry to the database, mark it done as well
+priority : Change the priority of 1 or more entries
+purge    : Remove old done entries in subtrees
+quit     : Exit program, NOT saving database
+remove   : Remove 1 or more entries from the database
+report   : Report completed tasks in interval
+undo     : Mark 1 or more entries as not done (cancel effect of 'done')
+usage    : Display help information
+version  : Display program version
+which    : Display filename of database being used
+
+Enter 'help <command-name>' for more help on a particular command
+
+tdl>
+@end example
+
+If the @command{help} command is passed the name of a sub-command, it shows help for that command.
+
+@example
+tdl> help add
+Description
+  Add a new entry to the database
+
+Synopsis
+  add [@@<datespec>] [<parent_index>] [<priority>] <entry_text>
+
+<index>    : 1, 1.1 etc (see output of 'tdl list')
+<priority> : urgent|high|normal|low|verylow
+<datespec> : [-|+][0-9]+[shdwmy][-hh[mm[ss]]]  OR
+             [-|+](sun|mon|tue|wed|thu|fri|sat)[-hh[mm[ss]]] OR
+             [[[cc]yy]mm]dd[-hh[mm[ss]]]
+<text>     : Any text (you'll need to quote it if >1 word)
+
+tdl>
+@end example
+
+The @command{help} command is synonymous with the @command{usage} command.
+
+@c }}}
+@c {{{ ignore command
+@node ignore command
+@subsection ignore command
+The @command{ignore} command puts one or more entries into an @emph{ignored}
+state.  It is actually implemented in the same way as marking them as
+@emph{done}, but as though they were @emph{done} a very long time ago.  Thus,
+ignored entries will be deleted by any subsequent purge operation.
+
+I added this feature because, when applying @command{remove} to several
+entries, I kept getting tripped up by the indices changing below the entry that
+was removed (I kept removing the wrong entries later by not using the revised
+indices).  Instead, I can @command{ignore} them and rely on a periodic
+@command{purge} to clean up the database.
+
+Another use for the @emph{ignore} command would be to move moribund entries
+into a @emph{wastebasket} to stop them cluttering up the normal listing, but
+without removing them entirely in case you need to reprieve them later.
+
+The @command{ignore} command is run as follows
+
+@example
+ignore <entry_index>[...] ...
+@end example
+
+An example is
+@example
+ignore 20 21.6.3 25... 28.1
+@end example
+
+If you need to @emph{un-ignore} an entry, just @command{undo} it (@pxref{undo
+command}).
+
+@c }}}
+@c {{{ import command
+@node import command
+@subsection import command
+The @command{import} command is used as follows:
+
+@example
+import <filename>
+@end example
+
+This command is used to merge entries from the TDL database @file{filename}
+into the default database (i.e. the one that most of the other commands would
+be accessing).
+
+You might use this command if you had a number of separate TDL databases, and
+wanted to merge their entries to form one combo database.
+@c }}}
+@c {{{ into command
+@node into command
+@subsection into command
+The @command{into} command is used to make one or more entries into sub-entries
+of another entry.  Its usage is
+
+@example
+into <new_parent_index> <index_to_move> ...
+@end example
+
+The following example shows it use
+@example
+tdl> list
+1 Task A
+2 Task B
+3 Task C
+4 Task D
+tdl> into 1 3 2
+tdl> list
+1 Task A
+   1.1 Task C
+   1.2 Task B
+2 Task D
+tdl>
+@end example
+
+The @command{into} command is closely related to @command{above},
+@command{after}, @command{before} and @command{below}.  In fact the following
+three commands are equivalent
+
+@example
+tdl> into N <indices> ...
+and
+tdl> above N.0 <indices> ...
+and
+tdl> before N.0 <indices> ...
+@end example
+
+@c }}}
+@c {{{ list command
+@node list command
+@subsection list command
+The @command{list} command is used to display the tasks in the database.  Its argument structure is:
+
+@example
+list [-v] [-a] [-m] [-p] [-1..9]
+     [<min-priority>]
+     [<parent_index>|<search_condition>...]
+@end example
+
+When run with no arguments, the list contains all tasks that are
+@itemize @bullet
+@item not marked 'done', 'ignored', 'deferred' or 'postponed',
+@item at any levels of the database (i.e. any number of components in the task's index), and
+@item of priority normal or higher.
+@end itemize
+
+The arguments have the following functions:
+
+@table @samp
+@item -v
+This stands for @emph{verbose}.  It means that more information will be shown
+for each task.
+@item -a
+This stands for @emph{all}.  It means that tasks which are 'done', or which are
+'deferred' or 'postponed' (by having arrival times in the future), will be
+shown as well as open tasks.
+@item -p
+This stands for @emph{postponed}.  It means that tasks which are 'deferred' or
+'postponed' are shown as well as open tasks.
+@item -m
+This stands for @emph{monochrome}.  Normally, ANSI escape sequences are
+inserted to show the list with colour coding.  This flag stops these sequences
+being generated.
+
+If you always want this option, set the @var{TDL_LIST_MONOCHROME} environment
+variable to any value.  If this variable exists, a monochrome listing will be
+generated.
+@item -1, -2, ..., -9
+These options restrict the depth of the tree which is generated.  For example,
+with the @samp{-1} argument, only the first-level entries in the database are
+shown.  Sub-entries of these are shown by summary totals.  See below for an
+example.
+@item <min-priority>
+Normally (at least, if no indices are specified), only tasks with a priority of
+at least @samp{normal} are shown in the list.  If you specify this option,
+tasks of at least the specified priority are shown.  The values you can use are
+@samp{urgent}, @samp{high}, @samp{normal}, @samp{low} or @samp{verylow}.  Each
+can be abbreviated to just its initial letter.
+@item <parent_index...>
+If you want to list just part of the database, you can specify the indices of
+the leading entries for the parts you want to see.  Any number of entries can
+be included (you could even include an index twice if you wanted to for some
+reason.)
+
+When you specify indices, the behaviour regarding priority changes.  If no
+@samp{<min-priority>} argument was given, the priority of a sub-entry must be
+at least that of the entry with the given index for it to be shown.  This is
+most useful when the entire sub-tree has a lower than normal priority.
+
+If you use the @samp{-1} ... @samp{-9} options with this option, the depth is
+counted relative to the depth of the indexed node that you specify.
+@item <search_condition>
+Each search condition specifies a case-insensitive substring match that is
+applied to all parent indices further on in the arguments.  (If no parent
+indices are given, all the search conditions are and'ed together and applied to
+filter all the nodes that would be shown according to the depth, priority etc
+arguments).
+
+Each search condition takes one of the following forms
+@example
+/substring
+/substring/1
+@end example
+
+In each case, an entry will match if @samp{substring} is actually a substring
+of the text of that entry.  In the second form (where the number may be 0, 1, 2
+or 3), a match occurs if there are up to that many errors in the substring.  An
+@dfn{error} is a single character inserted, removed or changed.
+
+This option is most useful if you have a large database and can remember you
+have an entry somewhere containing particular word(s), but can't remember where
+it is.
+
+If you need regular expression matching, the best approach would be to run
+@command{tdll} from the shell and pipe the output to @command{grep}.  The
+internal matching does approximate matches with keys up to 31 characters.
+
+@end table
+
+The command is best illustrated by examples.  Suppose the database contains
+these entries
+
+@example
+1 bibble
+   1.1 zzzz
+      1.1.1 (DONE) yyyy
+      1.1.2 wwww
+2 wibble
+   2.1 xxxx
+3 wubble
+@end example
+
+where @samp{wibble} and @samp{xxxx} have priority @samp{low}.  The following
+examples show the list command's behaviour.
+
+@example
+tdl> list
+1 bibble
+   1.1 zzzz
+      1.1.2 wwww
+3 wubble
+@end example
+
+(Entry 2 and its child are omitted due to priority.  Entry 1.1.1 is omitted
+because it is done.)
+
+@example
+1 bibble
+   1.1 zzzz
+      1.1.1 (DONE) yyyy
+      1.1.2 wwww
+2 wibble
+   2.1 xxxx
+3 wubble
+@end example
+
+(Passing these arguments shows the missing entries)
+
+@example
+tdl> list -1
+1 [1/1] bibble
+3 wubble
+@end example
+
+(The list is limited to top level entries.  The @samp{[1/1]} indices that entry
+1 has 1 open child out of a total of 1 children.)
+
+@example
+tdl> list -1 1.1
+1.1 [1/2] zzzz
+@end example
+
+(This lists the 1.1 subtree, showing that there is 1 open child out of a total
+of 2 children.)
+
+@example
+tdl> list /ww
+      1.1.2 wwww
+tdl> list /ww/1
+      1.1.2 wwww
+3 wubble
+tdl>
+@end example
+
+(In the first case the substring @samp{ww} must occur exactly in the entry's
+text.  In the second case, the string @samp{wu} in @samp{wubble} matches
+@samp{ww} with a single error so a match occurs.)
+@c }}}
+@c {{{ ls command
+@node ls command
+@subsection ls command
+This is synonymous with the @command{list} command (@pxref{list command}).  It
+is provided for people who are too used to typing @command{ls} in their shell.
+@c }}}
+@c {{{ log command
+@node log command
+@subsection log command
+The @command{log} command is very similar to the @command{add} command, except
+that it immediately marks the new entry as done.It is the equivalent of using
+@command{add} followed by @command{done} on the new entry.  It is run as follows:
+
+@example
+log [@@<datespec>] [<parent_index>] [<priority>] <entry_text>
+@end example
+
+You might use the @command{log} command if complete a new task immediately but
+want to make sure you log it for use in producing your weekly report (using
+the @command{report} command, @xref{report command}.)
+
+If you have several entries to add at once, you can go into an @emph{log} mode.
+Enter a blank line to get back to the @t{tdl>} prompt.
+
+@example
+tdl> log
+log> Wash the dog
+log> Wash the car
+log>
+tdl>
+@end example
+
+To add an entry direct from your shell, there is an additional shortcut
+(assuming the appropriate symbolic link was created during the installation
+process):
+
+@example
+% tdlg "Wash the dog"
+%
+@end example
+
+(the ending @samp{g} on the shortcut was chosen as the final letter of the word
+@samp{log}.  The shortcut @samp{tdll} was already allocated for the
+@command{list} subcommand.)
+
+@c }}}
+@c {{{ moveto command
+@node moveto command
+@subsection moveto command
+The @command{moveto} and @command{into} commands are synonymous.  See the
+description of @command{into} (@pxref{into command}).
+@c }}}
+@c {{{ narrow command
+@node narrow command
+@subsection narrow command
+The @command{narrow} command can be used to limit the effects of later commands
+to operate within a particular sub-tree of your database.  Because the indices
+you specify for the later operations have the common prefix omitted, this can
+save typing if you have many changes to make within the same subtree.
+
+The usage of the @command{narrow} command is
+
+@example
+narrow <new_root_index>
+@end example
+
+The following example illustrates:
+@example
+tdl> add a
+tdl> add b
+tdl> add -1 c
+tdl> add -1 d
+tdl> add -1 e
+tdl> list
+1 a
+2 b
+   2.1 c
+   2.2 d
+   2.3 e
+tdl> narrow 2
+tdl[2]> list
+2 b
+2.1 c
+2.2 d
+2.3 e
+tdl[2]> done 1 2
+tdl[2]> list
+2 b
+2.3 e
+tdl[2]> widen
+tdl>
+@end example
+
+If your listings are in colour, the common prefix is coloured in blue whilst
+the paths below the root of the sub-tree are shown in the usual green.  (In
+monochrome mode, there is no distinction.)
+
+Whilst your view is narrowed, the index of the sub-tree root is shown in square
+brackets between @samp{tdl} and @samp{>} (i.e. @samp{[2]} in the example
+above).
+
+If you want to operate on the sub-tree root entry itself whilst you are
+narrowed, you can use @samp{.} to specify its index (think: current directory
+in Unix.)
+
+To reverse the effects of the @command{narrow} command, use the @command{widen}
+command (@pxref{widen command}).
+
+This command is only available when @command{tdl} is being run interactively,
+i.e. when you have a @samp{tdl} prompt.  It is not available directly from the
+shell (where it wouldn't make much sense).
+@c }}}
+@c {{{ open command
+@node open command
+@subsection open command
+The @command{open} command is used to reverse the effect of the @command{defer}
+command (@pxref{defer command}) and @command{postpone} command (@pxref{postpone
+command}).  Its effect is actually to set the arrival time of the entries to
+the current time.
+
+The @command{open} command is used as follows
+@example
+open <index_to_reopen>[...] ...
+@end example
+
+An example is
+@example
+open 20 21... 25.2
+@end example
+
+@c }}}
+@c {{{ postpone command
+@node postpone command
+@subsection postpone command
+The @command{postpone} command is used to make 1 more more entries postponed
+indefinitely.  Its effect is actually to set the arrival time of the entries a
+long way in the future (i.e. it's an extreme form of the 'deferred' feature
+available in the @command{add} and @command{defer} commands.) Postponed entries
+can be re-activated with the @command{open} command (@pxref{open command}).
+
+The @command{postpone} command is used as follows
+@example
+postpone <index_to_postpone>[...] ...
+@end example
+
+An example is
+@example
+postpone 20 21... 25.2
+@end example
+
+To postpone an entry until a specific time in the future, use the
+@command{defer} command (@pxref{defer command}).
+
+@c }}}
+@c {{{ priority command
+@node priority command
+@subsection priority command
+The @command{priority} command is used to modify the priority of one or more entries.  Its argument structure is
+
+@example
+priority <new_priority> <entry_index>[...] ...
+@end example
+
+The @samp{new_priority} argument is the new priority to be assigned.  The
+values you can use are @samp{urgent}, @samp{high}, @samp{normal}, @samp{low} or
+@samp{verylow}.  Each can be abbreviated to just its initial letter.
+
+You can specify at least one @samp{entry_index}.  If an index is followed by
+@samp{...}, the whole sub-tree under the referenced entry will be modified too.
+For example,
+
+@example
+tdl> priority high 2 4.1...
+@end example
+
+will modify the priority of entry 2, entry 4.1, and all the entries under 4.1,
+to high.
+@c }}}
+@c {{{ purge command
+@node purge command
+@subsection purge command
+The @command{purge} command is used to remove outdated done tasks from the
+database.  For example, you might want to automatically remove everything you
+completed more than 2 months ago.
+
+Its argument structure is
+@example
+purge <since_datespec> [<ancestor_index> ...]
+@end example
+
+The @samp{since_datespec} argument specifies a date.  All entries which were
+marked 'done' before this date will be completely removed from the database.
+
+The default is to scan the entire database for done tasks that meet the date
+constraint.  You may specify one or more indices to limit the behaviour.  In
+this case, only the sub-trees headed by these indices will be considered for
+purge.
+
+An example:
+@example
+tdl> purge -2m 3.1 5
+@end example
+
+will purge all entries underneath entries 3.1 and 5 which were marked 'done'
+more than 2 months ago.
+
+@c }}}
+@c {{{ quit command
+@node quit command
+@subsection quit command
+The @command{quit} command is used to exit from tdl when it is used in
+interactive mode.  The @command{quit} command is not available in the command
+line mode, where it would not make sense.  An example
+
+@example
+tdl> quit
+%
+@end example
+
+The @command{quit} command @strong{DOES NOT} write any pending updates to the database before
+exiting.  (Compare the @command{exit} command (@pxref{exit command}), which
+does write all updates made during the current tdl run.)
+
+The main use for the @command{quit} command would be to avoid damaging the
+database if a serious error had been made.
+@c }}}
+@c {{{ remove command
+@node remove command
+@subsection remove command
+The @command{remove} command is used to remove entries from the database.  It
+differs from purge @pxref{purge command} in that there is no date constraint,
+and entries do not have to be marked 'done' to be removed.  You might use
+@command{remove} if an open tasks no longer needs to be performed.
+
+The argument structure is
+@example
+remove <entry_index>[...] ...
+@end example
+
+You can specify one or more entry indices to remove.  If an index is followed
+by @samp{...}, tdl will remove the whole sub-tree based at that index.
+
+You cannot remove an entry that has sub-entries below it.  (The @samp{...}
+handling for indices removes the deepest entries first to bypass this.)
+
+An example:
+@example
+tdl> remove 1.5.4 19... 20
+@end example
+
+will remove entries 1.5.4 and 20, as well as everything whose index starts with
+19.
+
+The @command{remove} command acts immediately to remove the specified entries
+from the database.  A less aggressive command with similar effects is
+@command{ignore} (@pxref{ignore command}).
+
+The @command{delete} command (@pxref{delete command}) is provided as a synonmym
+for the @command{remove} command.
+
+@c }}}
+@c {{{ report command
+@node report command
+@subsection report command
+The @command{report} command is used to show entries that were marked 'done'
+within a specific time interval.  Its argument structure is
+
+@example
+report <start_datespec> [<end_datespec>]
+@end example
+
+One or two date specifications may be given.  If only the start_datespec is
+provided, the end_datespec defaults to the present time.  The format of both
+arguments is described separately.  @xref{Datespec}.
+
+For example, suppose you have to write a report on what you have done in the
+last week.  You could use
+
+@example
+tdl> report 1w
+@end example
+
+The report is produced in a tree structure, mirroring the database structure.
+Entries which have 'done' children, but which haven't themselves been marked
+done, have their text surrounded by @samp{[[} and @samp{]]}.  For example
+
+@example
+tdl> list -a
+1 bibble
+   1.1 zzzz
+      1.1.1 (DONE) yyyy
+      1.1.2 wwww
+2 wibble
+   2.1 xxxx
+tdl> report 1w
+- [[bibble]]
+   - [[zzzz]]
+      - yyyy
+@end example
+
+You could cut-and-paste this text into your report, as a starting point that
+you can reformat into a report.  Alternatively, if you run the command direct
+from the shell prompt line, you can redirect the output to a file,
+
+@example
+% tdl report 1w > report.txt
+%
+@end example
+
+@c }}}
+@c {{{ revert command
+@node revert command
+@subsection revert command
+The @command{revert} command discards any changes made in the session and
+reloads the in-memory database from disc.  If you have used the @command{save}
+command (@pxref{save command}) in the session, the database will revert to its
+state at the most recent @command{save}.  Otherwise it will revert to its state
+when @command{tdl} was initially run.
+
+The @command{revert} command does not take any arguments.
+@c }}}
+@c {{{ save command
+@node save command
+@subsection save command
+
+The @command{save} command can be used to write the current in-memory database
+out to the disc database file.  The behaviour is currently equivalent to the
+command @command{exit} followed by re-running @samp{tdl} from the shell.
+
+This command is useful if you tend to do long interactive tdl sessions.  It guards against the risks of
+@enumerate
+@item accidentally typing @samp{quit} when you meant @samp{exit}
+@item machine crashes
+@item running @samp{tdl} in another window and seeing a stale copy of the database file.
+@end enumerate
+
+The @command{save} command does not take any arguments.
+@c }}}
+@c {{{ undo command
+@node undo command
+@subsection undo command
+
+The @command{undo} command reverses the action of the @command{done} command
+@pxref{done command}.  You can use it to re-open entries, e.g. if you marked
+them 'done' by mistake.
+
+Its argument structure is
+@example
+undo <entry_index>[...] ...
+@end example
+
+You can specify one or more indices to act on.  If an index is suffixed by
+@samp{...}, tdl will re-open the entire sub-tree based at that index.
+
+@c }}}
+@c {{{ usage command
+@node usage command
+@subsection usage command
+The @command{usage} command is synonymous with the @command{help} command.
+@xref{help command}.
+@c }}}
+@c {{{ version command
+@node version command
+@subsection version command
+The @command{version} command shows the program version.
+
+@example
+tdl> version
+tdl V1.1
+tdl>
+@end example
+@c }}}
+@c {{{ which command
+@node which command
+@subsection which command
+The @command{which} command displays the name of the current database file that
+tdl is using.  An example:
+
+@example
+tdl> which
+./.tdldb
+tdl>
+@end example
+@c }}}
+@c {{{ widen command
+@node widen command
+@subsection widen command
+
+This command reverses the effects of the @command{narrow} command
+(@pxref{narrow command}).
+
+Its usage is:
+
+@example
+tdl> widen [<n_levels>]
+@end example
+
+The optional @samp{n_levels} parameter tells tdl how many levels to widen the
+view.  If the parameter is not specified, it defaults to 1.  If you try to
+widen more levels than the depth of the current sub-tree root node, the
+widening will be silently limited to its depth.
+
+This command is only available when @command{tdl} is being run interactively,
+i.e. when you have a @samp{tdl} prompt.  It is not available directly from the
+shell (where it wouldn't make much sense).
+
+@c }}}
+@c }}}
+@c {{{ shortcuts
+@node Shortcuts
+@section Shortcut forms of certain commands
+For convenience, several shortcut commands are provided to access the most
+common tdl operations.  (These can be used from the shell command line.)
+
+@table @samp
+@item tdla
+This is a shortcut for @command{tdl add}, e.g.
+
+@example
+% tdla "A new item"
+@end example
+
+@item tdld
+This is a shortcut for @command{tdl delete}.
+
+@item tdlg
+This is a shortcut for @command{tdl log}.
+
+@item tdll
+This is a shortcut for @command{tdl list}.
+
+@item tdls
+This is another shortcut for @command{tdl list}.
+
+@end table
+@c }}}
+@c {{{ interrupting
+@node Interrupting
+@section Interrupting
+Most programs will stop where they are if you hit @key{Ctrl-C}.  This is also
+true (normally) for tdl.  However, tdl intercepts signals like this so that it
+can try to write out the database when it is safe to do so.  This avoids the
+loss of updates made to the database during the current session.
+
+Because of this approach, it is possible that tdl can get stuck in an endless
+loop if a bug arises.  For this reason, tdl will exit forcibly if @key{Ctrl-C}
+is pressed 4 times.  After the third press, a warning message is printed.
+
+If tdl has to be terminated in this way, the database will not be written back
+out.  Therefore, any edits made in the current session will be lost.  Also, a
+stale lock file will be left behind.  The @samp{-u} command line flag should be
+used on the next run of tdl to force the removal of this stale lock file.
+@c }}}
+@c {{{ Location
+@node Location
+@section How tdl finds the databse file to use
+If the @var{TDL_DATABASE} environment variable is set, its value is taken to be
+the name of the database to use.  This allows for two distinct modes of use:
+@itemize @bullet
+@item If you set @var{TDL_DATABASE} to a fixed filename (with a full path), you
+can use a single database regardless of your current working directory.
+@item If you set @var{TDL_DATABASE} to a relative path (e.g. @file{./.tdldb}),
+you will always use a database in the current working directory.
+@end itemize
+
+If this environment variable is not set, tdl finds the database by searching up
+through the directory tree until it finds a file called @file{.tdldb}.  The
+idea is to allow for one database per project, by placing the database in the
+parent directory of the project.  Then as long as your current working
+directory is anywhere within the project tree, the database will be found
+during the search.
+
+The only exception to this is the @command{create} command, which always
+operates in the current directory (unless @var{TDL_DATABASE} is set, in which
+case this variable's value defines the path to use.)
+
+If you wish to share databases between directory trees in some other way, the
+recommended method is to use symbolic (or hard) links to make a single database
+appear to be in more than one directory.
+
+@c }}}
+
+@c {{{ Completion
+@node Completion
+@section Completion facilities
+When tdl has been compiled to use the @emph{readline} library, the interactive
+mode supports a number of completion functions, activated with the @key{tab}
+key.
+
+In particular, the following are supported:
+
+@itemize @bullet
+
+@item @strong{Command completion}.  If @key{tab} is pressed when the command
+line is empty, a list of possible commands will be shown.  If @key{tab} is
+pressed when a partial command has been typed, the command will be completed
+immediately if possible, otherwise a list of commands matching the
+already-typed prefix will be shown.
+
+@item @strong{Help completion}.  If @command{help} or @command{usage} is
+already in the buffer, a list of commands will be shown(as above).  The
+@key{tab} completion works in the same way to complete the name of the command
+you want a help summary for.
+
+@item @strong{Priority completion}.  If @command{list} or @command{priority} is
+at the start of the input buffer and the current word starts with a letter, tdl
+will try to complete the name of a priority level if @key{tab} is pressed.
+
+@item @strong{Open task completion}.  If @command{done} is at the start of the
+input buffer, hitting @key{tab} will show a list of task indices that are still
+open.  If part of an index has already been typed, the open task indices for
+which the typed characters are a prefix will be shown.
+
+@item @strong{Postpone completion}.  If @command{postpone} is at the start of
+the input buffer, hitting @key{tab} will show a list of tasks that may be
+postponed.  Tasks marked @emph{done} are excluded.  If @command{open} is at the
+start of the input buffer, hitting @key{tab} will show a list of tasks that may
+be opened.
+
+@item @strong{Parameter hints}.  If some other command is at the start of the
+input buffer and @key{tab} is pressed, tdl will show a one-line summary of that
+command's parameters.
+
+@end itemize
+
+
+@c }}}
+@c {{{ Datespec
+@node Datespec
+@section How dates are specified
+@multitable @columnfractions .33 .66
+@item @strong{Date specification} @tab @strong{Meaning}
+@item -1h @tab exactly 1 hour ago
+@item -2d @tab exactly 2 days ago
+@item +1w @tab exactly 1 week in the future
+@item +1m @tab exactly 1 month (30 days) in the future
+@item +2y @tab exactly 2 years in the future
+@item -1d-0815 @tab 08:15am yesterday
+@item +1d-08 @tab 8am tomorrow
+@item +1w-08 @tab 8am on the same day as today next week
+@item +6h-08 @tab 8am on the day containing the time 6 hours ahead of now
+@item .-08 @tab 8am today
+@item .-20 @tab 8pm today
+@item 20011020 @tab absolute : 12 noon on 20th October 2001
+@item 011020 @tab absolute : 12 noon on 20th October 2001 (current century)
+@item 1020 @tab absolute : 12 noon on 20th October 2001 (current century and year)
+@item 20 @tab absolute : 12 noon on 20th October 2001 (current century, year and month)
+@item 20011020-081500 @tab absolute : 08:15am on 20th October 2001
+@item 20011020-0815 @tab absolute : 08:15am on 20th October 2001 (seconds=0)
+@item 20011020-08 @tab absolute : 08:00am on 20th October 2001 (minutes=seconds=0)
+@item 011020-08 @tab absolute : 08:00am on 20th October 2001 (minutes=seconds=0, current century)
+@item etc @tab (see below)
+@item -sun @tab 12 noon on the previous Sunday
+@item +sat @tab 12 noon on the following Saturday
+@item +sat-08 @tab 8am on the following Saturday
+@item -tue-0815 @tab 08:15am on the previous Tuesday
+@item etc @tab (see below)
+@end multitable
+
+In the 'all-numeric' format, the rule is that dates can have fields omitted
+from the start (assumed to be the current value), and times can have fields
+omitted from the end (assumed to be zero, except if the hours figure is missing
+it is assumed to be 12, since most work is done in the day.)
+
+In the 'weekday and time' format, the time rule is the same: missing minutes
+and seconds are taken as zero and missing hours as 12.  If the weekday is the
+same as today, the offset is always 7 days in the required direction.  If the
+weekday is not the same as today, the offset will always be less than 7 days in
+the required direction.
+
+In the 'relative' format, when a time is included as well, the procedure is as
+follows.  First the time is determined which is the given number of hours, days
+etc away from the current time.  Then the specified time on that day is used.
+The main use for this is to specify times like '8am yesterday'.  Obviously some
+of the possible uses of this mode are rather far-fetched.
+
+For the weekday and relative formats, the sign is actually optional.  The
+default sign (implying past (-) or future (+)) will then be assumed depending on
+the command as shown below:
+
+@multitable @columnfractions .2 .2 .6
+@item @strong{Command} @tab @strong{Default} @tab @strong{Reason}
+@item add @tab + @tab Add entries with deferred start times
+@item edit @tab + @tab Add entries with deferred start times
+@item defer @tab + @tab Modify start times of entries
+@item done @tab - @tab Entries have been completed at some time in the past
+@item log @tab - @tab Entries have been completed at some time in the past
+@item report @tab - @tab Reporting on earlier completed tasks not future ones
+@item purge @tab - @tab Tasks won't be completed in the future, so no need to purge future ones
+@end multitable
+@c }}}
+@c {{{ Backup file
+@node Backup file
+@section How tdl saves a backup copy of the database
+Whenever tdl writes a modified database to disk, it renames the previous
+database by adding @file{.bak} on the end of the filename.  Thus @file{.tdldb}
+is renamed to @file{.tdldb.bak}.
+
+If you need to restore the previous @file{.tdldb} for any reason (e.g. a gross
+mistake during editing, or if a bug causes it to be corrupted), you can
+manually rename @file{.tdldb.bak} to @file{.tdldb}.
+@c }}}
+@c {{{ Index specification
+@node Index specification
+@section Index specification
+Indices may usually be specified as negative values.  This counts from the end of the list.  The commonest use for this is to add children to an entry you've just typed, e.g.
+
+@example
+tdl> add "Parent entry"
+tdl> add -1 "Child entry"
+tdl> add -1 "Child entry"
+tdl> add -1.-1 "Grandchild entry"
+@end example
+
+There is one example where negative indices are not handled in the usual way.
+This is with the @command{list} command (@pxref{list command}).  Here, a
+negative index could be misinterpreted as a depth option.  If you really want
+negative indices for the @command{list} command, terminate the options with
+@samp{--} before providing indices and search patterns.
+@c }}}
+
+@bye
+@c vim:cms=@c\ %s:fdm=marker:fdc=5:syntax=off
+
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..bcd25be
--- /dev/null
+++ b/util.c
@@ -0,0 +1,297 @@
+/*
+   $Header: /cvs/src/tdl/util.c,v 1.8.2.1 2004/01/07 00:09:05 richard Exp $
+  
+   tdl - A console program for managing to-do lists
+   Copyright (C) 2001,2002,2003,2004,2005  Richard P. Curnow
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+   */
+
+#include <ctype.h>
+#include "tdl.h"
+
+int count_args(char **x)/*{{{*/
+{
+  int n = 0;
+  while (*x) {
+    n++;
+    x++;
+  }
+  return n;
+}
+/*}}}*/
+int include_descendents(char *x)/*{{{*/
+{
+  /* Check if string ends in ... . If it does, truncate that off. */
+  int len;
+  int result = 0;
+  len = strlen(x);
+  if (len >= 4) {
+    if (!strcmp(x + (len-3), "...")) {
+      result = 1;
+      x[len-3] = 0;
+    }
+  }
+  return result;
+}
+/*}}}*/
+static char *ordinal(int n)/*{{{*/
+{
+  int nn;
+  nn = n % 10;
+  switch (nn) {
+    case 1:  return "st";
+    case 2:  return "nd";
+    case 3:  return "rd";
+    default: return "th";
+  }
+}
+/*}}}*/
+struct node *lookup_node(char *path, int allow_zero_index, struct node **parent)/*{{{*/
+{
+  char *p = path;
+  int n, nc, idx, tidx, aidx, ncomp;
+  int direction;
+  struct links *x;
+  struct node *y = NULL;
+  struct node *narrow_top;
+
+  narrow_top = get_narrow_top();
+  if (narrow_top) {
+    /* Special case to allow user to do operations on the node to which the
+     * view is currently narrowed. (This doesn't apply to 'top' which is just a
+     * skeleton entry.) */
+    if (!strcmp(path, ".")) {
+      return narrow_top;
+    }
+    x = &(narrow_top->kids);
+  } else {
+    x = &top;
+  }
+  
+  ncomp = 1;
+  if (parent) *parent = NULL;
+
+  /* Skip leading '.', if any. */
+  if (*p == '.') p++;
+
+  while (*p) {
+    if ((p[0] == '-') || isdigit(p[0])) {
+      n = sscanf(p, "%d%n", &idx, &nc);
+      if (n != 1) {
+        fprintf(stderr, "Bad path expression found, starting [%s]\n", p);
+        return NULL;
+      }
+      p += nc;
+
+      if (idx > 0) {
+        direction = 1;
+        aidx = idx;
+      } else if (idx < 0) {
+        direction = 0;
+        aidx = -idx;
+      } else {
+        if (allow_zero_index) {
+          if (*p) {
+            fprintf(stderr, "Zero index only allowed as last component\n");
+            return NULL;
+          } else {
+            /* This is a special cheat to allow inserting entries at
+               the start or end of a chain for the 'above' and
+               'below' commands */
+            return (struct node *) x;
+          }
+        } else {
+          fprintf(stderr, "Zero in index not allowed\n");
+          return NULL;
+        }
+      }
+
+      if (x->next == (struct node *) x) {
+        fprintf(stderr, "Path [%s] doesn't exist - tree not that deep\n", path);
+        return NULL;
+      }
+
+      for (y = direction ? x->next : x->prev, tidx = aidx; --tidx;) {
+
+        y = direction ? y->chain.next : y->chain.prev;
+
+        if (y == (struct node *) x) {
+          fprintf(stderr, "Can't find entry %d for %d%s component of path %s\n",
+              idx, ncomp, ordinal(ncomp), path);
+          return NULL;
+        }
+      }
+    } else {
+      /* Lookup by start of node text. */
+      char *dot;
+      int len;
+      struct node *match;
+      dot = strchr(p, '.');
+      if (!dot) { /* final component. */
+        len = strlen(p);
+      } else {
+        len = dot - p;
+      }
+      match = NULL;
+      for (y = x->next; y != (struct node *) x; y = y->chain.next) {
+        if (!strncasecmp(y->text, p, len)) {
+          if (match) {
+            fprintf(stderr, "Ambiguous match for %d%s component (",
+                ncomp, ordinal(ncomp));
+            fwrite(p, 1, len, stderr);
+            fprintf(stderr, ") of path %s\n", path);
+            return NULL;
+          }
+          match = y;
+        }
+      }
+      if (!match) {
+        fprintf(stderr, "Can't find entry for %d%s component (",
+            ncomp, ordinal(ncomp));
+        fwrite(p, 1, len, stderr);
+        fprintf(stderr, ") of path %s\n", path);
+      }
+
+      y = match;
+      p += len;
+    }
+
+    if (*p == '.') {
+      p++;
+      x = &y->kids;
+      if (parent) *parent = y;
+    }
+
+    ncomp++;
+  }
+
+  return y;
+}
+/*}}}*/
+enum Priority parse_priority(char *priority, int *error)/*{{{*/
+{
+  enum Priority result;
+  int is_digit;
+  
+  if (!priority) {
+       *error = -1;
+    return PRI_UNKNOWN;
+  } else {
+  
+       is_digit = isdigit(priority[0]);
+  
+       if (is_digit) {
+       int value = atoi(priority);
+       result = (value >= PRI_URGENT) ? PRI_URGENT :
+                (value <= PRI_VERYLOW) ? PRI_VERYLOW :
+                (enum Priority) value;
+       } else {
+      int len = strlen(priority);
+       if (!strncmp(priority, "urgent", len)) {
+        result = PRI_URGENT;
+       } else if (!strncmp(priority, "high", len)) {
+               result = PRI_HIGH;
+       } else if (!strncmp(priority, "normal", len)) {
+               result = PRI_NORMAL;
+       } else if (!strncmp(priority, "low", len)) {
+               result = PRI_LOW;
+       } else if (!strncmp(priority, "verylow", len)) {
+        result = PRI_VERYLOW;
+       } else {
+        fprintf(stderr, "Can't parse priority '%s'\n", priority);
+        *error = -1;
+               return PRI_UNKNOWN; /* bogus */
+       }
+       }
+  }
+  *error = 0;
+  return result;
+}
+/*}}}*/
+void clear_flags(struct links *x)/*{{{*/
+{
+  struct node *y;
+  for (y = x->next; y != (struct node *) x; y = y->chain.next) {
+    y->flag = 0;
+    if (has_kids(y)) {
+      clear_flags(&y->kids);
+    }
+  }
+}
+/*}}}*/
+void mark_all_descendents(struct node *n)/*{{{*/
+{
+  struct node *y;
+  for (y = n->kids.next; y != (struct node *) &n->kids; y = y->chain.next) {
+    y->flag = 1;
+    if (has_kids(y)) {
+      mark_all_descendents(y);
+    }
+  }
+}
+/*}}}*/
+int has_kids(struct node *x)/*{{{*/
+{
+  return (x->kids.next != (struct node *) &x->kids);
+}
+/*}}}*/
+struct node *new_node(void)/*{{{*/
+{
+  struct node *result = new (struct node);
+  result->parent = NULL;
+  result->text = NULL;
+  result->priority = PRI_NORMAL;
+  result->arrived = result->required_by = result->done = 0U;
+  result->kids.next = result->kids.prev = (struct node *) &result->kids;
+  result->chain.next = result->chain.prev = (struct node *) &result->chain;
+  return result;
+}
+/*}}}*/
+void free_node(struct node *x)/*{{{*/
+{
+  /* FIXME : To be written */
+
+
+}
+/*}}}*/
+void append_node(struct node *n, struct links *l)/*{{{*/
+{
+  n->chain.next = l->next;
+  n->chain.prev = (struct node *) l;
+  l->next->chain.prev = n;
+  l->next = n;
+}
+/*}}}*/
+void prepend_node(struct node *n, struct links *l)/*{{{*/
+{
+  n->chain.prev = l->prev;
+  n->chain.next = (struct node *) l;
+  l->prev->chain.next = n;
+  l->prev = n;
+}
+/*}}}*/
+void prepend_child(struct node *child, struct node *parent)/*{{{*/
+{
+  child->parent = parent;
+  if (parent) {
+    prepend_node(child, &parent->kids);
+  } else {
+    struct node *narrow_top;
+    narrow_top = get_narrow_top();
+    prepend_node(child, narrow_top ? &narrow_top->kids : &top);
+  }
+}
+/*}}}*/
diff --git a/version.h b/version.h
new file mode 100644 (file)
index 0000000..01c8327
--- /dev/null
+++ b/version.h
@@ -0,0 +1,4 @@
+#ifndef VERSION_H
+#define VERSION_H 1
+#define PROGRAM_VERSION "1.6-pre1"
+#endif /* VERSION_H */
diff --git a/version.txt b/version.txt
new file mode 100644 (file)
index 0000000..282768f
--- /dev/null
@@ -0,0 +1 @@
+1.6-pre1
This page took 0.344357 seconds and 4 git commands to generate.