From: Joshua Wise Date: Tue, 22 Mar 2011 07:31:22 +0000 (-0400) Subject: Initial import of tdl-1.6-pre1 X-Git-Url: http://git.joshuawise.com/tdl.git/commitdiff_plain/7024e37b2ce8baad2236d13746dead68678846b6 Initial import of tdl-1.6-pre1 --- 7024e37b2ce8baad2236d13746dead68678846b6 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 [|