From 7024e37b2ce8baad2236d13746dead68678846b6 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Tue, 22 Mar 2011 03:31:22 -0400 Subject: [PATCH] Initial import of tdl-1.6-pre1 --- .arch-inventory | 4 + COPYING | 339 +++++++++ INSTALL | 27 + Makefile.in | 120 ++++ NEWS | 144 ++++ README | 75 ++ add.c | 326 +++++++++ configure | 445 ++++++++++++ dates.c | 301 ++++++++ done.c | 147 ++++ impexp.c | 179 +++++ inter.c | 678 ++++++++++++++++++ io.c | 167 +++++ list.c | 594 +++++++++++++++ main.c | 912 +++++++++++++++++++++++ memory.h | 32 + mkversion | 15 + move.c | 142 ++++ narrow.c | 151 ++++ purge.c | 109 +++ remove.c | 77 ++ report.c | 108 +++ tdl.1 | 969 +++++++++++++++++++++++++ tdl.h | 187 +++++ tdl.spec | 41 ++ tdl.texi | 1831 +++++++++++++++++++++++++++++++++++++++++++++++ util.c | 297 ++++++++ version.h | 4 + version.txt | 1 + 29 files changed, 8422 insertions(+) create mode 100644 .arch-inventory create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 Makefile.in create mode 100644 NEWS create mode 100644 README create mode 100644 add.c create mode 100755 configure create mode 100644 dates.c create mode 100644 done.c create mode 100644 impexp.c create mode 100644 inter.c create mode 100644 io.c create mode 100644 list.c create mode 100644 main.c create mode 100644 memory.h create mode 100755 mkversion create mode 100644 move.c create mode 100644 narrow.c create mode 100644 purge.c create mode 100644 remove.c create mode 100644 report.c create mode 100644 tdl.1 create mode 100644 tdl.h create mode 100644 tdl.spec create mode 100644 tdl.texi create mode 100644 util.c create mode 100644 version.h create mode 100644 version.txt diff --git a/.arch-inventory b/.arch-inventory new file mode 100644 index 0000000..7f0136a --- /dev/null +++ b/.arch-inventory @@ -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 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. + + 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.) + +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. + + 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. + + 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 + + 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. + + + Copyright (C) 19yy + + 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. + + , 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 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 index 0000000..6385dde --- /dev/null +++ b/Makefile.in @@ -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 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 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 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 +#include +#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 [@] [] [] \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 ...\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 []\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 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 < +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 or + + # As for linking, do we need -ltermcap, -lncurses etc + + printf "Testing what to include for readline : " + cat >docheck.c < +#include +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 "\n" + READLINE_DEFINE="-DBARE_READLINE_H=1" + else + rm -f docheck.c + cat >docheck.c < +#include +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 "\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 < +#if BARE_READLINE_H +#include +#else +#include +#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 < +#if BARE_READLINE_H +#include +#else +#include +#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 < +#if BARE_READLINE_H +#include +#else +#include +#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 < if you have libraries in a + nonstandard directory + +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 . +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 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 +#include + +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 + +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 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 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; ichain.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; ikids : &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 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 +#include +#include +#include + +#ifdef USE_READLINE +#if BARE_READLINE_H +#include +#include +#else +#include +#include +#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; i0; ) { + 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 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 +#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; iprev = (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 +#include +#include +#include +#include +#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 "" +#define GREEN "" +#define YELLOW "" +#define BLUE "" +#define MAGENTA "" +#define CYAN "" +#define NORMAL "" +#define DIM "" +#define DIMCYAN "" + +/* 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 0) ? 43200 : -43200)) / 86400; + timestamp2 = (time_t) timestamp; + strftime(buffer, sizeof(buffer), "%a %d %b %Y %H:%M", + localtime(×tamp2)); + 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; iiscratch]) { + 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 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_DOTLOCK +#include +#include +#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[] = " ..."; +static char synop_add[] = "[@] [] [] "; +static char synop_after[] = " ..."; +static char synop_before[] = " ..."; +static char synop_below[] = " ..."; +static char synop_clone[] = " ..."; +static char synop_copyto[] = " ..."; +static char synop_create[] = ""; +static char synop_defer[] = "[@] {...] ..."; +static char synop_delete[] = "[...] ..."; +static char synop_done[] = "[@] [...] ..."; +static char synop_edit[] = " []"; +static char synop_exit[] = ""; +static char synop_export[] = " ..."; +static char synop_help[] = "[]"; +static char synop_ignore[] = "[...] ..."; +static char synop_import[] = ""; +static char synop_into[] = " ..."; +static char synop_list[] = "[-v] [-a] [-p] [-m] [-1..9] [] [|/...]\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" + " : word to match on"; +static char synop_log[] = "[@] [] [] "; +static char synop_moveto[] = " ..."; +static char synop_narrow[] = ""; +static char synop_open[] = "[...] ..."; +static char synop_postpone[] = "[...] ..."; +static char synop_priority[] = " [...] ..."; +static char synop_purge[] = " [ ...]"; +static char synop_quit[] = ""; +static char synop_remove[] = "[...] ..."; +static char synop_report[] = " []\n" + "(end defaults to now)"; +static char synop_revert[] = ""; +static char synop_save[] = ""; +static char synop_undo[] = "[...] ..."; +static char synop_usage[] = "[]"; +static char synop_version[] = ""; +static char synop_which[] = ""; +static char synop_widen[] = "[]"; +/* }}} */ + +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= 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= 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" + " : urgent|high|normal|low|verylow\n" + " : [-|+][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" + " : 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' for more help on a particular command\n"); + } else { + fprintf(stdout, "\nEnter 'tdl help ' 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.next = (struct node *) ⊤ + + if (argc > 1) { + for (i=1; i\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 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 + +#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 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 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 ...\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; ichain.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 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 \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"); + return -1; + } + if (argc > 0) { + if (sscanf(x[0], "%d", &n_levels) != 1) { + fprintf(stderr, "Usage : widen []\n"); + return -1; + } + } else { + n_levels = 1; + } + + new_narrow_top = narrow_top; + if (!new_narrow_top) goto widen_to_top; + + for (i=0; iparent; + 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 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 +#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 : ⊤ + + argc = count_args(x); + if (argc < 1) { + fprintf(stderr, "Usage: purge ...\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 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 +#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 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 +#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 []\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 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 + 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 (i.e. end of file on stdin) +.br +* Hitting , 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 [] +.I [|...] +.br +.B tdll +.I [\-v] +.I [\-a] +.I [\-p] +.I [\-m] +.I [\-1...9] +.I [] +.I [|] +.I [|