diff --git a/AUTHORS b/AUTHORS index 6e55fde6e..1a9c81146 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,6 +3,7 @@ The development of task was made possible by the significant contributions of th Federico Hernandez (Package Maintainer & Contributing Author) David J Patrick (Designer) John Florian (Contributing Author) + Cory Donnelly (Contributing Author) The following submitted code, packages or analysis, and deserve special thanks: Damian Glenny @@ -18,7 +19,6 @@ The following submitted code, packages or analysis, and deserve special thanks: Johan Friis Steven de Brouwer Pietro Cerutti - Cory Donnelly Thanks to the following, who submitted detailed bug reports and excellent suggestions: Eugene Kramer diff --git a/ChangeLog b/ChangeLog index deae5c454..bcf84f960 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,11 @@ ------ current release --------------------------- 1.9.0 () + + Added feature #283 that makes it possible to control the verbosity + of the output of annotations. + + Added feature #254 (#295) which gives task a second date format to be + used in the reports with more conversion sequences like weekday name + or weeknumber. The date format is set with variable "reportdateformat". + Added feature #292 that permits alternate line coloration in reports (thanks to Richard Querin). + Added feature #307 that provides vim with syntax highlighting for .taskrc. @@ -19,8 +24,11 @@ + Added new 'config' command to display the configuration settings of task. As a consequence 'version' now only shows the version number and legal information. - + The 'config' command now complains about use of deprecated color names and - duplicate entries in .taskrc. + + The 'config' command now complains about use of deprecated color names in + your .taskrc file. + + Added feature #296, that allows the 'config' command to modify your .taskrc + settings directly, with the command 'task config ', or + 'task config ' to remove the setting. + Task now supports nested .taskrc files using the "include /path" directive. + The 'entry', 'start' and 'end' columns now have equivalents that include the time, and are called 'entry_time', 'start_time', and 'end_time', for use in diff --git a/Makefile.am b/Makefile.am index 79858c98f..42096b174 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,7 +14,7 @@ zshscriptsdir = $(docdir) nobase_dist_zshscripts_DATA = scripts/zsh/_task vimscriptsdir = $(docdir) -nobase_dist_vimscripts_DATA = scripts/vim/README scripts/vim/ftdetect/task.vim scripts/vim/syntax/taskdata.vim scripts/vim/syntax/taskedit.vim +nobase_dist_vimscripts_DATA = scripts/vim/README scripts/vim/ftdetect/task.vim scripts/vim/syntax/taskdata.vim scripts/vim/syntax/taskedit.vim scripts/vim/syntax/taskrc.vim i18ndir = $(docdir) nobase_dist_i18n_DATA = i18n/strings.de-DE i18n/strings.en-US i18n/strings.es-ES i18n/strings.fr-FR i18n/strings.nl-NL i18n/strings.sv-SE i18n/tips.de-DE i18n/tips.en-US i18n/tips.sv-SE diff --git a/NEWS b/NEWS index d9c6f8212..25e266810 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ New Features in task 1.9 - Supports nested .taskrc files with the new "include" statement" - New columns that include the time as well as date - New attribute modifiers + - New date format for reports - Improved .taskrc validation - Improved calendar report with custom coloring and optional task details output diff --git a/doc/man/task-faq.5 b/doc/man/task-faq.5 index 0092b7997..bf09aa7a3 100644 --- a/doc/man/task-faq.5 +++ b/doc/man/task-faq.5 @@ -1,24 +1,32 @@ .TH task-faq 5 2010-01-03 "task 1.9.0" "User Manuals" +.SH NAME +task-faq \- A FAQ for the task(1) command line todo manager. + +.SH DESCRIPTION +Task is a command line TODO list manager. It maintains a list of tasks that you +want to do, allowing you to add/remove, and otherwise manipulate them. Task +has a rich list of commands that allow you to do various things with it. + .SH WELCOME Welcome to the task FAQ. If you have would like to see a question answered here, please send us a note at . .TP -.B Where does task store the data? +.B Q: Where does task store the data? By default, task creates a .taskrc file in your home directory and populates it with defaults. Task also creates a .task directory in your home directory and puts data files there. .TP -.B Can I edit that data? +.B Q: Can I edit that data? Of course you can. It is a simple text file, and looks somewhat like the JSON format, and if you are careful not to break the format, there is no reason not to edit it. But task provides a rich command set to do that manipulation for you, so it is probably best to leave those files alone. .TP -.B How do I restore my .taskrc file to defaults? +.B Q: How do I restore my .taskrc file to defaults? If you delete (or rename) your .taskrc file, task will offer to create a default one for you. Another way to do this is with the command: @@ -28,14 +36,51 @@ Task will create 'new-file' if it doesn't already exist. Note that this is a good way to learn about new configuration settings, if your .taskrc file was created by an older version of task. +.TP +.B Q: Do I need to back up my task data? +Yes. You should back up your ~/.task directory, and probably your ~/.taskrc +file too. +.TP +.B Q: Can I share my tasks between different machines? +.TP +.B Q: The undo.data file gets very large - do I need it? +You need it if you want the undo capability. But if it gets large, you can +certainly truncate it to save space, just be careful to delete lines from the +top of the file, up to and including a separator '---'. Note that it does not +slow down task, because task never reads it until you want to undo. Otherwise +task only appends to the file. +.TP +.B Q: How do I know whether my terminal support 256 colors? +The easiest way is to just try it! With task 1.9 or later, you simply run + $ task color +and a full color palette is displayed, if you look at it and see lots of +different colors, then your terminal supports 256 colors. If you see only +8 or 16 colors, many of them repeated, with blank areas then your terminal +does not support 256 colors. xterm does. iTerm does. +.TP +.B Q: How can I make task put the command in the terminal window title? +You cannot. But you can make the shell do it, and you can make the shell +call the task program. Here is a Bash script that does this: + #! /bin/bash + printf "\033]0;task $*\a" + /usr/local/bin/task $* + +You just need to run the script, and let the script run task. Here is a Bash +function that does the same thing: + + t () + { + printf "\033]0;task $*\a" + /usr/local/bin/task $* + } .SH "CREDITS & COPYRIGHTS" task was written by P. Beckingham . diff --git a/doc/man/task-tutorial.5 b/doc/man/task-tutorial.5 index b4aa2754c..f3a12ac8a 100644 --- a/doc/man/task-tutorial.5 +++ b/doc/man/task-tutorial.5 @@ -1,4 +1,4 @@ -.TH task-tutorial 5 2009-09-07 "task 1.9.0" "User Manuals" +.TH task-tutorial 5 2010-01-03 "task 1.9.0" "User Manuals" .SH NAME task-tutorial \- A tutorial for the task(1) command line todo manager. diff --git a/doc/man/task.1 b/doc/man/task.1 index 0749bb227..065c2c8d9 100644 --- a/doc/man/task.1 +++ b/doc/man/task.1 @@ -1,4 +1,4 @@ -.TH task 1 2009-09-07 "task 1.9.0" "User Manuals" +.TH task 1 2010-01-03 "task 1.9.0" "User Manuals" .SH NAME task \- A command line todo manager. @@ -129,8 +129,22 @@ Displays all possible colors, or a sample. Shows the task version number .TP -.B config -Shows the current settings in the task configuration file. +.B config [name [value | '']] +Shows the current settings in the task configuration file. Also supports +directly modifying the .taskrc file. This command either modifies +the 'name' setting with a new value of 'value', or adds a new entry that +is equivalent to 'name=value': + + task config name value + +This command sets a blank value. This has the effect of suppressing any +default value: + + task config name '' + +Finally, this command removes any 'name=...' entry from the .taskrc file: + + task config name .TP .B help @@ -157,9 +171,13 @@ Shows all tasks matching the specified criteria that are completed. .TP -.B ls [tags] [attrs] [description] +.B minimal [tags] [attrs] [description] Provides a minimal listing of tasks with specified criteria. +.TP +.B ls [tags] [attrs] [description] +Provides a short listing of tasks with specified criteria. + .TP .B list [tags] [attrs] [description] Provides a more detailed listing of tasks with specified criteria. diff --git a/doc/man/taskrc.5 b/doc/man/taskrc.5 index b38462dec..c3f705b1e 100644 --- a/doc/man/taskrc.5 +++ b/doc/man/taskrc.5 @@ -1,4 +1,4 @@ -.TH taskrc 5 2009-09-07 "task 1.9.0" "User Manuals" +.TH taskrc 5 2010-01-03 "task 1.9.0" "User Manuals" .SH NAME taskrc \- Configuration file for the task(1) command @@ -115,6 +115,10 @@ May be "yes" or "no", and determines whether task will ask for confirmation befo .B echo.command=yes May be "yes" or "no", and causes task to display the ID and description of any task when you run the start, stop, do, undo or delete commands. The default value is "yes". +.TP +.B annotation.details=2 +Controls the output of annotations in reports. Defaults to 2 - all annotations are displayed. Set to 1 only the last (youngest) annotation is displayed and if there are more than one present for a task a "+" sign is added to the description. Set to 0 the output of annotations is disabled and a "+" sign will be added if there are any annotations present. + .TP .B next=2 Is a number, defaulting to 2, which is the number of tasks for each project that are shown in the @@ -148,33 +152,59 @@ tag names you have used, or just the ones used in active tasks. .TP .B dateformat=m/d/Y -This is a string of characters that define how task formats dates. The default value is: m/d/Y. -The string should contain the characters +.TP +.B reportdateformat=m/d/Y +This is a string of characters that define how task formats dates. If +.B reportdateformat +is set it will be used for the due date in the output of reports and "task info". + +The default value is: m/d/Y. The string should contain the characters .RS -m minimal-digit month, for example 1 or 12 +m minimal-digit month, for example 1 or 12 .br -d minimal-digit day, for example 1 or 30 +d minimal-digit day, for example 1 or 30 .br -y two-digit year, for example 09 +y two-digit year, for example 09 .br -D two-digit day, for example 01 or 30 +D two-digit day, for example 01 or 30 .br -M two-digit month, for example 01 or 12 +M two-digit month, for example 01 or 12 .br -Y four-digit year, for example 2009 +Y four-digit year, for example 2009 +.br +a short name of weekday, for example Mon or Wed +.br +A long name of weekday, for example Monday or Wednesday +.br +b short name of month, for example Jan or Aug +.br +B long name of month, for example January or August +.br +V weeknumber, for example 03 or 37 .RE The string may also contain other characters to act as spacers, or formatting. Examples for other -variable values: +values of dateformat: .RS .br -d/m/Y would output 24/7/2009 +d/m/Y would use for input and output 24/7/2009 .br -YMD would output 20090724 +yMD would use for input and output 090724 .br -m-d-y would output 07-24-09 +M-D-Y would use for input and output 07-24-2009 +.RE + +Examples for other values of reportdateformat: + +.RS +.br +a D b Y (V) would do an output as "Fri 24 Jul 2009 (30)" +.br +A, B D, Y would do an output as "Friday, July 24, 2009" +.br +vV a Y-M-D would do an output as "v30 Fri 2009-07.24" .RE .TP diff --git a/doc/misc/script-color.txt b/doc/misc/script-color.txt new file mode 100644 index 000000000..2d669ed09 --- /dev/null +++ b/doc/misc/script-color.txt @@ -0,0 +1,34 @@ + Hello. This is a demonstration of the + task program color capabilities coming + in version 1.9. + +task color The color command shows the various + supported colors. For this you will + need an xterm with 256-color support, + or an equivalent. + + This demo uses iTerm running on Snow + Leopard. + +task add Prepare 1.9 for release Let's create a few tasks, to illustrate +task add Update the various docs the features. Five should be enough. +task add Run the regression tests +task add Make the packages +task add Upload to distributions + +--- NOTES + +16-color mode +upgrade +blending +alternate lines + +--- NOTES + +task ls Okay, let's color any tasks that + mention tests a nice medium blue. + +echo 'color.keyword.test=color23' >> ~/.taskrc + + + diff --git a/doc/rc/dark-16.theme b/doc/rc/dark-16.theme new file mode 100644 index 000000000..99b48867b --- /dev/null +++ b/doc/rc/dark-16.theme @@ -0,0 +1 @@ +# Sample task 1.9 (or later) color theme diff --git a/doc/rc/dark-256.theme b/doc/rc/dark-256.theme new file mode 100644 index 000000000..99b48867b --- /dev/null +++ b/doc/rc/dark-256.theme @@ -0,0 +1 @@ +# Sample task 1.9 (or later) color theme diff --git a/doc/rc/light-16.theme b/doc/rc/light-16.theme new file mode 100644 index 000000000..99b48867b --- /dev/null +++ b/doc/rc/light-16.theme @@ -0,0 +1 @@ +# Sample task 1.9 (or later) color theme diff --git a/doc/rc/light-256.theme b/doc/rc/light-256.theme new file mode 100644 index 000000000..99b48867b --- /dev/null +++ b/doc/rc/light-256.theme @@ -0,0 +1 @@ +# Sample task 1.9 (or later) color theme diff --git a/package-config/osx/binary/COPYING.txt b/package-config/osx/binary/COPYING.txt deleted file mode 100644 index 627d3eb39..000000000 --- a/package-config/osx/binary/COPYING.txt +++ /dev/null @@ -1,281 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 - diff --git a/package-config/osx/binary/README.txt b/package-config/osx/binary/README.txt deleted file mode 100644 index de698bba5..000000000 --- a/package-config/osx/binary/README.txt +++ /dev/null @@ -1,24 +0,0 @@ - -Thank you for taking a look at task! - -Task is a GTD, todo list, task management, command line utility with a multitude -of features. It is a portable, well supported, very active project, and it is -Open Source. Task has binary distributions, online documentation, demonstration -movies, and you'll find all the details at the site: - - http://taskwarrior.org - -At the site you'll find a wiki, discussion forums, downloads, news and more. - - -Your contributions are especially welcome. Whether it comes in the form of -code patches, ideas, discussion, bug reports or just encouragement, your input -is needed. - -Please send your support questions and code patches to: - - support@taskwarrior.org - -Consider joining taskwarrior.org and participating in the future of task. - ---- diff --git a/src/Att.cpp b/src/Att.cpp index 515dd7b4f..466af6e1a 100644 --- a/src/Att.cpp +++ b/src/Att.cpp @@ -331,7 +331,7 @@ bool Att::validNameValue ( { // Validate and convert to epoch. if (value != "") - value = Date (value, context.config.get ("dateformat", "m/d/Y")).toEpochString (); + value = Date (value, context.config.get ("dateformat")).toEpochString (); } else if (name == "recur") @@ -571,7 +571,7 @@ bool Att::match (const Att& other) const } else if (which == "date") { - Date literal (mValue.c_str (), context.config.get ("dateformat", "m/d/Y")); + Date literal (mValue.c_str (), context.config.get ("dateformat")); Date variable ((time_t)atoi (other.mValue.c_str ())); if (other.mValue == "" || ! (variable < literal)) return false; @@ -601,7 +601,7 @@ bool Att::match (const Att& other) const } else if (which == "date") { - Date literal (mValue.c_str (), context.config.get ("dateformat", "m/d/Y")); + Date literal (mValue.c_str (), context.config.get ("dateformat")); Date variable ((time_t)atoi (other.mValue.c_str ())); if (! (variable > literal)) return false; diff --git a/src/Config.cpp b/src/Config.cpp index 1366ce831..6d4a2e0b2 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -27,30 +27,231 @@ #include #include #include +#include #include #include #include #include #include +#include "Directory.h" +#include "File.h" #include "Config.h" #include "text.h" #include "util.h" //////////////////////////////////////////////////////////////////////////////// -// These are default (but overridable) reports. These entries are necessary -// because these three reports were converted from hard-coded reports to custom -// reports, and therefore need these config file entries. However, users are -// already used to seeing these five reports, but do not have these entries. -// The choice was a) make users edit their .taskrc files, b) write a .taskrc -// upgrade program to make the change, or c) this. +// This string is used in two ways: +// 1) It is used to create a new .taskrc file, by copying it directly to disk. +// 2) It is parsed and used as default values for all Config.get calls. +std::string Config::defaults = + "# Task program configuration file.\n" + "# For more documentation, see http://taskwarrior.org or try 'man task' and 'man taskrc'\n" + "\n" + "# Files\n" + "data.location=~/.task\n" + "locking=on # Use file-level locking\n" + "\n" + "# Terminal\n" + "curses=on # Use ncurses library to determine terminal width\n" + "defaultwidth=80 # Without ncurses, assumed width\n" + "#editor=vi # Preferred text editor\n" + "\n" + "# Miscellaneous\n" + "confirmation=yes # Confirmation on delete, big changes\n" + "echo.command=yes # Details on command just run\n" + "annotation.details=2 # Level of verbosity for annotations in reports\n" + "next=2 # How many tasks per project in next report\n" + "bulk=2 # > 2 tasks considered 'a lot', for confirmation\n" + "nag=You have higher priority tasks. # Nag message to keep you honest\n" // TODO + "\n" + "# Dates\n" + "dateformat=m/d/Y # Preferred input and display date format\n" + "#reportdateformat=m/d/Y # Preferred display date format for repors\n" + "weekstart=Sunday # Sunday or Monday only\n" // TODO + "displayweeknumber=yes # Show week numbers on calendar\n" // TODO + "due=7 # Task is considered due in 7 days\n" + "#calendar.details=yes # Calendar shows information for tasks w/due dates\n" + "#calendar.details.report=list # Report to use when showing task information in cal\n" // TODO + "#monthsperline=3 # Number of calendar months on a line\n" // TODO + "\n" + "# Color controls.\n" + "color=on # Enable color\n" + "color.overdue=bold red # Color of overdue tasks\n" + "color.due=bold yellow # Color of due tasks\n" + "color.pri.H=bold # Color of priority:H tasks\n" + "#color.pri.M=on yellow # Color of priority:M tasks\n" + "#color.pri.L=on green # Color of priority:L tasks\n" + "#color.pri.none=white on blue # Color of priority: tasks\n" + "color.active=bold cyan # Color of active tasks\n" + "color.tagged=yellow # Color of tagged tasks\n" + "#color.tag.bug=yellow # Color of +bug tasks\n" + "#color.project.garden=on green # Color of project:garden tasks\n" + "#color.keyword.car=on blue # Color of description.contains:car tasks\n" + "#color.recurring=on red # Color of recur.any: tasks\n" + "#color.header=bold green # Color of header messages\n" + "#color.footnote=bold green # Color of footnote messages\n" + "#color.alternate=on rgb253 # Alternate color for line coloring\n" + "color.calendar.today=black on cyan # Color of today in calendar\n" + "color.calendar.due=black on green # Color of days with due tasks in calendar\n" + "color.calendar.overdue=black on red # Color of days with overdue tasks in calendar\n" + "color.calendar.weekend=black on white # Color of weekend days in calendar\n" + "#color.debug=magenta # Color of diagnostic output\n" + "color.pri.H=bold # Color of priority:H tasks\n" + "color.history.add=on red # Color of added tasks in the history reports\n" + "color.history.delete=on yellow # Color of deleted tasks in the history reports\n" + "color.history.done=on green # Color of completed tasks in the history reports\n" + "\n" + "# Shadow file support\n" + "#shadow.file=/tmp/shadow.txt # Location of shadow file\n" + "#shadow.command=list # Task command for shadow file\n" + "#shadow.notify=on # Footnote when updated\n" + "\n" + "#default.project=foo # Default project for 'add' command\n" + "#default.priority=M # Default priority for 'add' command\n" + "default.command=list # When no arguments are specified\n" // TODO + "\n" + "_forcecolor=no # Forces color to be on, even for non TTY output\n" + "blanklines=true # Use more whitespace in output\n" + "complete.all.projects=no # Include old project names in 'projects' command\n" // TODO + "complete.all.tags=no # Include old tag names in 'tags' command\n" // TODO + "debug=no # Display diagnostics\n" + "fontunderline=yes # Uses underlines rather than -------\n" + "shell.prompt=task> # Prompt used by the shell command\n" // TODO + "\n" + "# Import heuristics - alternate names for fields (comma-separated list of names)\n" + "#import.synonym.bg=?\n" + "#import.synonym.description=?\n" + "#import.synonym.due=?\n" + "#import.synonym.end=?\n" + "#import.synonym.entry=?\n" + "#import.synonym.fg=?\n" + "#import.synonym.id=?\n" + "#import.synonym.priority=?\n" + "#import.synonym.project=?\n" + "#import.synonym.recur=?\n" + "#import.synonym.start=?\n" + "#import.synonym.status=?\n" + "#import.synonym.tags=?\n" + "#import.synonym.uuid=?\n" + "\n" + "# Aliases - alternate names for commands\n" + "alias.rm=delete # Alias for the delete command\n" + "\n" + "# Fields: id,uuid,project,priority,priority_long,entry,entry_time,\n" // TODO + "# start,entry_time,due,recur,recurrence_indicator,age,\n" // TODO + "# age_compact,active,tags,tag_indicator,description,\n" // TODO + "# description_only,end,end_time\n" // TODO + "# Description: This report is ...\n" + "# Sort: due+,priority-,project+\n" + "# Filter: pro:x pri:H +bug limit:10\n" + "\n" + "# task long\n" + "report.long.description=Lists all task, all data, matching the specified criteria\n" + "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n" + "report.long.labels=ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description\n" + "report.long.sort=due+,priority-,project+\n" + "report.long.filter=status:pending\n" + "\n" + "# task list\n" + "report.list.description=Lists all tasks matching the specified criteria\n" + "report.list.columns=id,project,priority,due,active,age,description\n" + "report.list.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.list.sort=due+,priority-,project+\n" + "report.list.filter=status:pending\n" + "\n" + "# task ls\n" + "report.ls.description=Minimal listing of all tasks matching the specified criteria\n" + "report.ls.columns=id,project,priority,description\n" + "report.ls.labels=ID,Project,Pri,Description\n" + "report.ls.sort=priority-,project+\n" + "report.ls.filter=status:pending\n" + "\n" + "# task minimal\n" + "report.minimal.description=A really minimal listing\n" + "report.minimal.columns=id,project,description\n" + "report.minimal.labels=ID,Project,Description\n" + "report.minimal.sort=project+,description+\n" + "report.minimal.filter=status:pending\n" + "\n" + "# task newest\n" + "report.newest.description=Shows the newest tasks\n" + "report.newest.columns=id,project,priority,due,active,age,description\n" + "report.newest.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.newest.sort=id-\n" + "report.newest.filter=status:pending limit:10\n" + "\n" + "# task oldest\n" + "report.oldest.description=Shows the oldest tasks\n" + "report.oldest.columns=id,project,priority,due,active,age,description\n" + "report.oldest.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.oldest.sort=id+\n" + "report.oldest.filter=status:pending limit:10\n" + "\n" + "# task overdue\n" + "report.overdue.description=Lists overdue tasks matching the specified criteria\n" + "report.overdue.columns=id,project,priority,due,active,age,description\n" + "report.overdue.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.overdue.sort=due+,priority-,project+\n" + "report.overdue.filter=status:pending due.before:today\n" + "\n" + "# task active\n" + "report.active.description=Lists active tasks matching the specified criteria\n" + "report.active.columns=id,project,priority,due,active,age,description\n" + "report.active.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.active.sort=due+,priority-,project+\n" + "report.active.filter=status:pending start.any:\n" + "\n" + "# task completed\n" + "report.completed.description=Lists completed tasks matching the specified criteria\n" + "report.completed.columns=end,project,priority,age,description\n" + "report.completed.labels=Complete,Project,Pri,Age,Description\n" + "report.completed.sort=end+,priority-,project+\n" + "report.completed.filter=status:completed\n" + "\n" + "# task recurring\n" + "report.recurring.description=Lists recurring tasks matching the specified criteria\n" + "report.recurring.columns=id,project,priority,due,recur,active,age,description\n" + "report.recurring.labels=ID,Project,Pri,Due,Recur,Active,Age,Description\n" + "report.recurring.sort=due+,priority-,project+\n" + "report.recurring.filter=status:pending parent.any:\n" + "\n" + "# task waiting\n" + "report.waiting.description=Lists all waiting tasks matching the specified criteria\n" + "report.waiting.columns=id,project,priority,wait,age,description\n" + "report.waiting.labels=ID,Project,Pri,Wait,Age,Description\n" + "report.waiting.sort=wait+,priority-,project+\n" + "report.waiting.filter=status:waiting\n" + "\n" + "# task all\n" + "report.all.description=Lists all tasks matching the specified criteria\n" + "report.all.columns=id,project,priority,due,active,age,description\n" + "report.all.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.all.sort=due+,priority-,project+\n" + "\n" + "# task next\n" + "report.next.description=Lists the most urgent tasks\n" + "report.next.columns=id,project,priority,due,active,age,description\n" + "report.next.labels=ID,Project,Pri,Due,Active,Age,Description\n" + "report.next.sort=due+,priority-,project+\n" + "report.next.filter=status:pending\n" + "\n"; + +//////////////////////////////////////////////////////////////////////////////// +// DO NOT CALL Config::setDefaults. +// +// This is a default constructor, and as such is only used to: +// a) initialize a default Context constructor +// b) run unit tests +// +// In all real use cases, Config::load is called. Config::Config () { - setDefaults (); } //////////////////////////////////////////////////////////////////////////////// Config::Config (const std::string& file) { + setDefaults (); load (file); } @@ -62,346 +263,117 @@ Config::Config (const std::string& file) // Nested files are now supported, with the following construct: // include /absolute/path/to/file // -bool Config::load (const std::string& file, int nest /* = 1 */) +void Config::load (const std::string& file, int nest /* = 1 */) { if (nest > 10) throw std::string ("Configuration file nested to more than 10 levels deep" " - this has to be a mistake."); - std::ifstream in; - in.open (file.c_str (), std::ifstream::in); - if (in.good ()) + // First time in, load the default values. + if (nest == 1) { - std::string line; - while (getline (in, line)) - { - // Remove comments. - std::string::size_type pound = line.find ("#"); // no i18n - if (pound != std::string::npos) - line = line.substr (0, pound); - - line = trim (line, " \t"); // no i18n - - // Skip empty lines. - if (line.length () > 0) - { - std::string::size_type equal = line.find ("="); // no i18n - if (equal != std::string::npos) - { - std::string key = trim (line.substr (0, equal), " \t"); // no i18n - std::string value = trim (line.substr (equal+1, line.length () - equal), " \t"); // no i18n - - (*this)[key] = value; - sequence.push_back (key); - } - else - { - std::string::size_type include = line.find ("include"); // no i18n. - if (include != std::string::npos) - { - std::string included = expandPath ( trim ( line.substr (include + 7), " \t")); - if (isAbsolutePath (included)) - { - if (!access (included.c_str (), F_OK | R_OK)) - this->load (included, nest + 1); - else - throw std::string ("Could not read include file '") + included + "'"; - } - else - throw std::string ("Can only include files with absolute paths, not '") + included + "'"; - } - else - throw std::string ("Malformed entry in ") + file + ": '" + line + "'"; - } - } - } - - in.close (); - return true; + setDefaults (); + original_file = File (file); } - return false; + // Read the file, then parse the contents. + std::string contents; + if (File::read (file, contents) && contents.length ()) + parse (contents, nest); +} + +//////////////////////////////////////////////////////////////////////////////// +void Config::parse (const std::string& input, int nest /* = 1 */) +{ + // Shortcut case for default constructor. + if (input.length () == 0) + return; + + // Split the input into lines. + std::vector lines; + split (lines, input, "\n"); + + // Parse each line. + std::vector ::iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + std::string line = *it; + + // Remove comments. + std::string::size_type pound = line.find ("#"); // no i18n + if (pound != std::string::npos) + line = line.substr (0, pound); + + line = trim (line, " \t"); // no i18n + + // Skip empty lines. + if (line.length () > 0) + { + std::string::size_type equal = line.find ("="); // no i18n + if (equal != std::string::npos) + { + std::string key = trim (line.substr (0, equal), " \t"); // no i18n + std::string value = trim (line.substr (equal+1, line.length () - equal), " \t"); // no i18n + + (*this)[key] = value; + } + else + { + std::string::size_type include = line.find ("include"); // no i18n. + if (include != std::string::npos) + { + Path included (trim (line.substr (include + 7), " \t")); + if (included.is_absolute ()) + { + if (included.readable ()) + this->load (included, nest + 1); + else + throw std::string ("Could not read include file '") + included.data + "'"; + } + else + throw std::string ("Can only include files with absolute paths, not '") + included.data + "'"; + } + else + throw std::string ("Malformed entry '") + line + "'"; + } + } + } } //////////////////////////////////////////////////////////////////////////////// void Config::createDefaultRC (const std::string& rc, const std::string& data) { - // Create a sample .taskrc file. - std::stringstream contents; - contents << "# Task program configuration file.\n" - << "# For more documentation, see http://taskwarrior.org or try 'man task' and 'man taskrc'\n" - << "\n" - << "# Files\n" - << "data.location=" << data << "\n" - << "locking=on # Use file-level locking\n" - << "\n" - << "# Terminal\n" - << "curses=on # Use ncurses library to determine terminal width\n" - << "#defaultwidth=80 # Without ncurses, assumed width\n" - << "#editor=vi # Preferred text editor\n" - << "\n" - << "# Miscellaneous\n" - << "confirmation=yes # Confirmation on delete, big changes\n" - << "echo.command=yes # Details on command just run\n" - << "next=2 # How many tasks per project in next report\n" - << "bulk=2 # > 2 tasks considered 'a lot', for confirmation\n" - << "nag=You have higher priority tasks. # Nag message to keep you honest\n" - << "\n" - << "# Dates\n" - << "dateformat=m/d/Y # Preferred input and display date format\n" - << "weekstart=Sunday # Sunday or Monday only\n" - << "displayweeknumber=yes # Show week numbers on calendar\n" - << "due=7 # Task is considered due in 7 days\n" - << "#calendar.details=yes # Calendar shows information for tasks w/due dates\n" - << "#calendar.details.report=list # Report to use when showing task information in cal\n" - << "#monthsperline=2 # Number of calendar months on a line\n" - << "\n" - << "# Color controls.\n" - << "color=on # Use color\n" - << "color.overdue=bold_red # Color of overdue tasks\n" - << "color.due=bold_yellow # Color of due tasks\n" - << "color.pri.H=bold # Color of priority:H tasks\n" - << "#color.pri.M=on_yellow # Color of priority:M tasks\n" - << "#color.pri.L=on_green # Color of priority:L tasks\n" - << "#color.pri.none=white on_blue # Color of priority: tasks\n" - << "color.active=bold_cyan # Color of active tasks\n" - << "color.tagged=yellow # Color of tagged tasks\n" - << "#color.tag.bug=yellow # Color of +bug tasks\n" - << "#color.project.garden=on_green # Color of project:garden tasks\n" - << "#color.keyword.car=on_blue # Color of description.contains:car tasks\n" - << "#color.recurring=on_red # Color of recur.any: tasks\n" - << "#color.header=bold_green # Color of header messages\n" - << "#color.footnote=bold_green # Color of footnote messages\n" - << "#color.alternate=on_rgb253 # Alternate color for line coloring\n" - << "color.calendar.today=black on cyan # Color of today in calendar\n" - << "color.calendar.due=black on green # Color of days with due tasks in calendar\n" - << "color.calendar.overdue=black on red # Color of days with overdue tasks in calendar\n" - << "color.calendar.weekend=black on white # Color of weekend days in calendar\n" - << "\n" - << "#shadow.file=/tmp/shadow.txt # Location of shadow file\n" - << "#shadow.command=list # Task command for shadow file\n" - << "#shadow.notify=on # Footnote when updated\n" - << "\n" - << "#default.project=foo # Unless otherwise specified\n" - << "#default.priority=M # Unless otherwise specified\n" - << "default.command=list # Unless otherwise specified\n" - << "\n" - << "alias.rm=delete\n" - << "\n" - << "# Fields: id,uuid,project,priority,priority_long,entry,entry_time,\n" - << "# start,entry_time,due,recur,recurrence_indicator,age,\n" - << "# age_compact,active,tags,tag_indicator,description,\n" - << "# description_only,end,end_time\n" - << "# Description: This report is ...\n" - << "# Sort: due+,priority-,project+\n" - << "# Filter: pro:x pri:H +bug limit:10\n" - << "\n" - << "# task long\n" - << "report.long.description=Lists all task, all data, matching the specified criteria\n" - << "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n" - << "report.long.labels=ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description\n" - << "report.long.sort=due+,priority-,project+\n" - << "report.long.filter=status:pending\n" - << "\n" - << "# task list\n" - << "report.list.description=Lists all tasks matching the specified criteria\n" - << "report.list.columns=id,project,priority,due,active,age,description\n" - << "report.list.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.list.sort=due+,priority-,project+\n" - << "report.list.filter=status:pending\n" - << "\n" - << "# task ls\n" - << "report.ls.description=Minimal listing of all tasks matching the specified criteria\n" - << "report.ls.columns=id,project,priority,description\n" - << "report.ls.labels=ID,Project,Pri,Description\n" - << "report.ls.sort=priority-,project+\n" - << "report.ls.filter=status:pending\n" - << "\n" - << "# task minimal\n" - << "report.minimal.description=A really minimal listing\n" - << "report.minimal.columns=id,project,description\n" - << "report.minimal.labels=ID,Project,Description\n" - << "report.minimal.sort=project+,description+\n" - << "report.minimal.filter=status:pending\n" - << "\n" - << "# task newest\n" - << "report.newest.description=Shows the newest tasks\n" - << "report.newest.columns=id,project,priority,due,active,age,description\n" - << "report.newest.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.newest.sort=id-\n" - << "report.newest.filter=status:pending limit:10\n" - << "\n" - << "# task oldest\n" - << "report.oldest.description=Shows the oldest tasks\n" - << "report.oldest.columns=id,project,priority,due,active,age,description\n" - << "report.oldest.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.oldest.sort=id+\n" - << "report.oldest.filter=status:pending limit:10\n" - << "\n" - << "# task overdue\n" - << "report.overdue.description=Lists overdue tasks matching the specified criteria\n" - << "report.overdue.columns=id,project,priority,due,active,age,description\n" - << "report.overdue.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.overdue.sort=due+,priority-,project+\n" - << "report.overdue.filter=status:pending due.before:today\n" - << "\n" - << "# task active\n" - << "report.active.description=Lists active tasks matching the specified criteria\n" - << "report.active.columns=id,project,priority,due,active,age,description\n" - << "report.active.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.active.sort=due+,priority-,project+\n" - << "report.active.filter=status:pending start.any:\n" - << "\n" - << "# task completed\n" - << "report.completed.description=Lists completed tasks matching the specified criteria\n" - << "report.completed.columns=end,project,priority,age,description\n" - << "report.completed.labels=Complete,Project,Pri,Age,Description\n" - << "report.completed.sort=end+,priority-,project+\n" - << "report.completed.filter=status:completed\n" - << "\n" - << "# task recurring\n" - << "report.recurring.description=Lists recurring tasks matching the specified criteria\n" - << "report.recurring.columns=id,project,priority,due,recur,active,age,description\n" - << "report.recurring.labels=ID,Project,Pri,Due,Recur,Active,Age,Description\n" - << "report.recurring.sort=due+,priority-,project+\n" - << "report.recurring.filter=status:pending parent.any:\n" - << "\n" - << "# task waiting\n" - << "report.waiting.description=Lists all waiting tasks matching the specified criteria\n" - << "report.waiting.columns=id,project,priority,wait,age,description\n" - << "report.waiting.labels=ID,Project,Pri,Wait,Age,Description\n" - << "report.waiting.sort=wait+,priority-,project+\n" - << "report.waiting.filter=status:waiting\n" - << "\n" - << "# task all\n" - << "report.all.description=Lists all tasks matching the specified criteria\n" - << "report.all.columns=id,project,priority,due,active,age,description\n" - << "report.all.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.all.sort=due+,priority-,project+\n" - << "\n" - << "# task next\n" - << "report.next.description=Lists the most urgent tasks\n" - << "report.next.columns=id,project,priority,due,active,age,description\n" - << "report.next.labels=ID,Project,Pri,Due,Active,Age,Description\n" - << "report.next.sort=due+,priority-,project+\n" - << "report.next.filter=status:pending\n" - << "\n"; + // Override data.location in the defaults. + std::string::size_type loc = defaults.find ("data.location=~/.task"); + // loc+0^ +14^ +21^ - spit (rc, contents.str ()); + std::string contents = defaults.substr (0, loc + 14) + + data + + defaults.substr (loc + 21, std::string::npos); + + // Write out the new file. + if (! File::write (rc, contents)) + throw std::string ("Could not write to '") + rc + "'"; } //////////////////////////////////////////////////////////////////////////////// void Config::createDefaultData (const std::string& data) { - if (access (data.c_str (), F_OK)) - mkdir (data.c_str (), S_IRWXU); + Directory d (data); + if (! d.exists ()) + d.create (); } //////////////////////////////////////////////////////////////////////////////// void Config::setDefaults () { - set ("report.long.description", "Lists all task, all data, matching the specified criteria"); // TODO i18n - set ("report.long.columns", "id,project,priority,entry,start,due,recur,age,tags,description"); // TODO i18n - set ("report.long.labels", "ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description"); // TODO i18n - set ("report.long.sort", "due+,priority-,project+"); // TODO i18n - set ("report.long.filter", "status:pending"); // TODO i18n - - set ("report.list.description", "Lists all tasks matching the specified criteria"); // TODO i18n - set ("report.list.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.list.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.list.sort", "due+,priority-,project+"); // TODO i18n - set ("report.list.filter", "status:pending"); // TODO i18n - - set ("report.ls.description", "Short listing of all tasks matching the specified criteria"); // TODO i18n - set ("report.ls.columns", "id,project,priority,description"); // TODO i18n - set ("report.ls.labels", "ID,Project,Pri,Description"); // TODO i18n - set ("report.ls.sort", "priority-,project+"); // TODO i18n - set ("report.ls.filter", "status:pending"); // TODO i18n - - set ("report.minimal.description", "A really minimal listing"); // TODO i18n - set ("report.minimal.columns", "id,project,description"); // TODO i18n - set ("report.minimal.labels", "ID,Project,Description"); // TODO i18n - set ("report.minimal.sort", "project+,description+"); // TODO i18n - set ("report.minimal.filter", "status:pending"); // TODO i18n - - set ("report.newest.description", "Shows the newest tasks"); // TODO i18n - set ("report.newest.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.newest.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.newest.sort", "id-"); // TODO i18n - set ("report.newest.filter", "status:pending limit:10"); // TODO i18n - - set ("report.oldest.description", "Shows the oldest tasks"); // TODO i18n - set ("report.oldest.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.oldest.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.oldest.sort", "id+"); // TODO i18n - set ("report.oldest.filter", "status:pending limit:10"); // TODO i18n - - set ("report.overdue.description", "Lists overdue tasks matching the specified criteria"); // TODO i18n - set ("report.overdue.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.overdue.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.overdue.sort", "due+,priority-,project+"); // TODO i18n - set ("report.overdue.filter", "status:pending due.before:today"); // TODO i18n - - set ("report.active.description", "Lists active tasks matching the specified criteria"); // TODO i18n - set ("report.active.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.active.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.active.sort", "due+,priority-,project+"); // TODO i18n - set ("report.active.filter", "status:pending start.any:"); // TODO i18n - - set ("report.completed.description", "Lists completed tasks matching the specified criteria"); // TODO i18n - set ("report.completed.columns", "end,project,priority,age,description"); // TODO i18n - set ("report.completed.labels", "Complete,Project,Pri,Age,Description"); // TODO i18n - set ("report.completed.sort", "end+,priority-,project+"); // TODO i18n - set ("report.completed.filter", "status:completed"); // TODO i18n - - set ("report.recurring.description", "Lists recurring tasks matching the specified criteria"); // TODO i18n - set ("report.recurring.columns", "id,project,priority,due,recur,active,age,description"); // TODO i18n - set ("report.recurring.labels", "ID,Project,Pri,Due,Recur,Active,Age,Description"); // TODO i18n - set ("report.recurring.sort", "due+,priority-,project+"); // TODO i18n - set ("report.recurring.filter", "status:pending parent.any:"); // TODO i18n - - set ("report.waiting.description", "Lists all waiting tasks matching the specified criteria"); // TODO i18n - set ("report.waiting.columns", "id,project,priority,wait,age,description"); // TODO i18n - set ("report.waiting.labels", "ID,Project,Pri,Wait,Age,Description"); // TODO i18n - set ("report.waiting.sort", "wait+,priority-,project+"); // TODO i18n - set ("report.waiting.filter", "status:waiting"); // TODO i18n - - set ("report.all.description", "Lists all tasks matching the specified criteria"); // TODO i18n - set ("report.all.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.all.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.all.sort", "due+,priority-,project+"); // TODO i18n - - set ("report.next.description", "Lists the most urgent tasks"); // TODO i18n - set ("report.next.columns", "id,project,priority,due,active,age,description"); // TODO i18n - set ("report.next.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n - set ("report.next.sort", "due+,priority-,project+"); // TODO i18n - set ("report.next.filter", "status:pending"); // TODO i18n - - set ("alias.rm", "delete"); // TODO i18n + parse (defaults); } //////////////////////////////////////////////////////////////////////////////// void Config::clear () { std::map ::clear (); - sequence.clear (); -} - -//////////////////////////////////////////////////////////////////////////////// -// Return the configuration value given the specified key. -const std::string Config::get (const char* key) -{ - return this->get (std::string (key)); -} - -//////////////////////////////////////////////////////////////////////////////// -// Return the configuration value given the specified key. If a default_value -// is present, it will be the returned value in the event of a missing key. -const std::string Config::get ( - const char* key, - const char* default_value) -{ - return this->get (std::string (key), std::string (default_value)); } //////////////////////////////////////////////////////////////////////////////// @@ -412,56 +384,42 @@ const std::string Config::get (const std::string& key) } //////////////////////////////////////////////////////////////////////////////// -// Return the configuration value given the specified key. If a default_value -// is present, it will be the returned value in the event of a missing key. -const std::string Config::get ( - const std::string& key, - const std::string& default_value) +const int Config::getInteger (const std::string& key) { if ((*this).find (key) != (*this).end ()) - return (*this)[key]; + return atoi ((*this)[key].c_str ()); - return default_value; + return 0; } //////////////////////////////////////////////////////////////////////////////// -bool Config::get (const std::string& key, const bool default_value) +const double Config::getReal (const std::string& key) +{ + if ((*this).find (key) != (*this).end ()) + return atof ((*this)[key].c_str ()); + + return 0.0; +} + +//////////////////////////////////////////////////////////////////////////////// +const bool Config::getBoolean (const std::string& key) { if ((*this).find (key) != (*this).end ()) { std::string value = lowerCase ((*this)[key]); - if (value == "t" || // TODO i18n value == "true" || // TODO i18n value == "1" || // no i18n + value == "+" || // no i18n + value == "y" || // TODO i18n value == "yes" || // TODO i18n value == "on" || // TODO i18n value == "enable" || // TODO i18n value == "enabled") // TODO i18n return true; - - return false; } - return default_value; -} - -//////////////////////////////////////////////////////////////////////////////// -int Config::get (const std::string& key, const int default_value) -{ - if ((*this).find (key) != (*this).end ()) - return atoi ((*this)[key].c_str ()); - - return default_value; -} - -//////////////////////////////////////////////////////////////////////////////// -double Config::get (const std::string& key, const double default_value) -{ - if ((*this).find (key) != (*this).end ()) - return atof ((*this)[key].c_str ()); - - return default_value; + return false; } //////////////////////////////////////////////////////////////////////////////// @@ -494,40 +452,6 @@ void Config::all (std::vector& items) items.push_back (i->first); } -//////////////////////////////////////////////////////////////////////////////// -void Config::getSequence (std::vector& items) -{ - items = sequence; -} - -//////////////////////////////////////////////////////////////////////////////// -std::string Config::checkForDuplicates () -{ - std::vector duplicates; - std::map unique; - - foreach (i, sequence) - { - if (unique.find (*i) != unique.end ()) - duplicates.push_back (*i); - else - unique[*i] = 0; - } - - std::stringstream out; - if (duplicates.size ()) - { - out << "Found duplicate entries for:" << std::endl; - - foreach (i, duplicates) - out << " " << *i << std::endl; - - out << std::endl; - } - - return out.str (); -} - //////////////////////////////////////////////////////////////////////////////// std::string Config::checkForDeprecatedColor () { diff --git a/src/Config.h b/src/Config.h index f30c4e10f..925b72c21 100644 --- a/src/Config.h +++ b/src/Config.h @@ -30,6 +30,7 @@ #include #include #include +#include "File.h" class Config : public std::map { @@ -40,30 +41,31 @@ public: Config (const Config&); Config& operator= (const Config&); - bool load (const std::string&, int nest = 1); + void load (const std::string&, int nest = 1); + void parse (const std::string&, int nest = 1); + void createDefaultRC (const std::string&, const std::string&); void createDefaultData (const std::string&); void setDefaults (); void clear (); - const std::string get (const char*); - const std::string get (const char*, const char*); - const std::string get (const std::string&); - const std::string get (const std::string&, const std::string&); - bool get (const std::string&, const bool); - int get (const std::string&, const int); - double get (const std::string&, const double); + const std::string get (const std::string&); + const int getInteger (const std::string&); + const double getReal (const std::string&); + const bool getBoolean (const std::string&); + void set (const std::string&, const int); void set (const std::string&, const double); void set (const std::string&, const std::string&); void all (std::vector &); - void getSequence (std::vector&); - std::string checkForDuplicates (); std::string checkForDeprecatedColor (); +public: + File original_file; + private: - std::vector sequence; + static std::string defaults; }; #endif diff --git a/src/Context.cpp b/src/Context.cpp index 192acd0d2..a4a9485ad 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -32,6 +32,8 @@ #include #include #include "Context.h" +#include "Directory.h" +#include "File.h" #include "Timer.h" #include "text.h" #include "util.h" @@ -98,16 +100,16 @@ void Context::initialize () { config.set ("curses", "off"); - if (! config.get (std::string ("_forcecolor"), false)) + if (! config.getBoolean ("_forcecolor")) config.set ("color", "off"); } - if (config.get ("color", true)) + if (config.getBoolean ("color")) initializeColorRules (); // Load appropriate stringtable as soon after the config file as possible, to // allow all subsequent messages to be localizable. - std::string location = expandPath (config.get ("data.location")); + Directory location (config.get ("data.location")); std::string locale = config.get ("locale"); // If there is a locale variant (en-US.), then strip it. @@ -116,7 +118,7 @@ void Context::initialize () locale = locale.substr (0, period); if (locale != "") - stringtable.load (location + "/strings." + locale); + stringtable.load (location.data + "/strings." + locale); // TODO Handle "--version, -v" right here? @@ -125,7 +127,7 @@ void Context::initialize () std::vector all; split (all, location, ','); foreach (path, all) - tdb.location (expandPath (*path)); + tdb.location (*path); } //////////////////////////////////////////////////////////////////////////////// @@ -154,16 +156,16 @@ int Context::run () } // Dump all debug messages. - if (config.get (std::string ("debug"), false)) + if (config.getBoolean ("debug")) foreach (d, debugMessages) - if (config.get ("color", true) || config.get (std::string ("_forcecolor"), false)) + if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeDebug (*d) << std::endl; else std::cout << *d << std::endl; // Dump all headers. foreach (h, headers) - if (config.get ("color", true) || config.get (std::string ("_forcecolor"), false)) + if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeHeader (*h) << std::endl; else std::cout << *h << std::endl; @@ -173,7 +175,7 @@ int Context::run () // Dump all footnotes. foreach (f, footnotes) - if (config.get ("color", true) || config.get (std::string ("_forcecolor"), false)) + if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeFootnote (*f) << std::endl; else std::cout << *f << std::endl; @@ -244,8 +246,8 @@ int Context::dispatch (std::string &out) void Context::shadow () { // Determine if shadow file is enabled. - std::string shadowFile = expandPath (config.get ("shadow.file")); - if (shadowFile != "") + File shadowFile (config.get ("shadow.file")); + if (shadowFile.data != "") { inShadow = true; // Prevents recursion in case shadow command writes. @@ -268,8 +270,10 @@ void Context::shadow () // Run report. Use shadow.command, using default.command as a fallback // with "list" as a default. - std::string command = config.get ("shadow.command", - config.get ("default.command", "list")); + std::string command = config.get ("shadow.command"); + if (command == "") + command = config.get ("default.command"); + split (args, command, ' '); initialize (); @@ -279,21 +283,21 @@ void Context::shadow () parse (); std::string result; (void)dispatch (result); - std::ofstream out (shadowFile.c_str ()); + std::ofstream out (shadowFile.data.c_str ()); if (out.good ()) { out << result; out.close (); } else - throw std::string ("Could not write file '") + shadowFile + "'"; + throw std::string ("Could not write file '") + shadowFile.data + "'"; config.set ("curses", oldCurses); config.set ("color", oldColor); // Optionally display a notification that the shadow file was updated. - if (config.get (std::string ("shadow.notify"), false)) - footnote (std::string ("[Shadow file '") + shadowFile + "' updated]"); + if (config.getBoolean ("shadow.notify")) + footnote (std::string ("[Shadow file '") + shadowFile.data + "' updated]"); inShadow = false; } @@ -353,8 +357,10 @@ void Context::loadCorrectConfigFile () "Could not read home directory from the passwd file.")); std::string home = pw->pw_dir; - std::string rc = home + "/.taskrc"; - std::string data = home + "/.task"; +// std::string rc = home + "/.taskrc"; +// std::string data = home + "/.task"; + File rc (home + "/.taskrc"); + Directory data (home + "./task"); // Is there an file_override for rc:? foreach (arg, args) @@ -364,28 +370,27 @@ void Context::loadCorrectConfigFile () else if (arg->substr (0, 3) == "rc:") { file_override = *arg; - rc = arg->substr (3); + rc = File (arg->substr (3)); home = rc; - std::string::size_type last_slash = rc.rfind ("/"); + std::string::size_type last_slash = rc.data.rfind ("/"); if (last_slash != std::string::npos) - home = rc.substr (0, last_slash); + home = rc.data.substr (0, last_slash); else home = "."; args.erase (arg); - header ("Using alternate .taskrc file " + rc); // TODO i18n + header ("Using alternate .taskrc file " + rc.data); // TODO i18n break; } } // Load rc file. config.clear (); // Dump current values. - config.setDefaults (); // Add in the custom reports. - config.load (rc); // Load new file. + config.load (rc); // Load new file. if (config.get ("data.location") != "") - data = config.get ("data.location"); + data = Directory (config.get ("data.location")); // Are there any var_overrides for data.location? foreach (arg, args) @@ -395,20 +400,20 @@ void Context::loadCorrectConfigFile () else if (arg->substr (0, 17) == "rc.data.location:" || arg->substr (0, 17) == "rc.data.location=") { - data = arg->substr (17); - header ("Using alternate data.location " + data); // TODO i18n + data = Directory (arg->substr (17)); + header ("Using alternate data.location " + data.data); // TODO i18n break; } } // Do we need to create a default rc? - if (access (rc.c_str (), F_OK)) + if (! rc.exists ()) { if (confirm ("A configuration file could not be found in " // TODO i18n + home + "\n\n" + "Would you like a sample " - + rc + + rc.data + " created, so task can proceed?")) { config.createDefaultRC (rc, data); @@ -420,10 +425,11 @@ void Context::loadCorrectConfigFile () // Create data location, if necessary. config.createDefaultData (data); + // TODO find out why this was done twice - see tw #355 // Load rc file. - config.clear (); // Dump current values. - config.setDefaults (); // Add in the custom reports. - config.load (rc); // Load new file. + //config.clear (); // Dump current values. + //config.setDefaults (); // Add in the custom reports. + //config.load (rc); // Load new file. // Apply overrides of type: "rc.name:value", or "rc.name=value". std::vector filtered; diff --git a/src/Date.cpp b/src/Date.cpp index 7971f075e..35f53cfa9 100644 --- a/src/Date.cpp +++ b/src/Date.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2010, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham, Federico Hernandez. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -33,6 +33,9 @@ #include "Date.h" #include "text.h" #include "util.h" +#include "Context.h" + +extern Context context; //////////////////////////////////////////////////////////////////////////////// // Defaults to "now". @@ -85,7 +88,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) if (i >= mdy.length () || ! isdigit (mdy[i])) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (m)."; } if (i + 1 < mdy.length () && @@ -106,7 +109,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) if (i >= mdy.length () || ! isdigit (mdy[i])) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (d)."; } if (i + 1 < mdy.length () && @@ -125,11 +128,11 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) // Double digit. case 'y': - if (i + 1 >= mdy.length () || + if (i + 1 >= mdy.length () || ! isdigit (mdy[i + 0]) || ! isdigit (mdy[i + 1])) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (y)."; } year = atoi (mdy.substr (i, 2).c_str ()) + 2000; @@ -141,7 +144,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) ! isdigit (mdy[i + 0]) || ! isdigit (mdy[i + 1])) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (M)."; } month = atoi (mdy.substr (i, 2).c_str ()); @@ -153,13 +156,24 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) ! isdigit (mdy[i + 0]) || ! isdigit (mdy[i + 1])) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (D)."; } day = atoi (mdy.substr (i, 2).c_str ()); i += 2; break; + case 'V': + if (i + 1 >= mdy.length () || + ! isdigit (mdy[i + 0]) || + ! isdigit (mdy[i + 1])) + { + throw std::string ("\"") + mdy + "\" is not a valid date (V)."; + } + + i += 2; + break; + // Quadruple digit. case 'Y': if (i + 3 >= mdy.length () || @@ -168,18 +182,70 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) ! isdigit (mdy[i + 2]) || ! isdigit (mdy[i + 3])) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (Y)."; } year = atoi (mdy.substr (i, 4).c_str ()); i += 4; break; + // Short names with 3 characters + case 'a': + if (i + 2 >= mdy.length () || + isdigit (mdy[i + 0]) || + isdigit (mdy[i + 1]) || + isdigit (mdy[i + 2])) + { + throw std::string ("\"") + mdy + "\" is not a valid date (a)."; + } + + i += 3; + break; + + case 'b': + if (i + 2 >= mdy.length () || + isdigit (mdy[i + 0]) || + isdigit (mdy[i + 1]) || + isdigit (mdy[i + 2])) + { + throw std::string ("\"") + mdy + "\" is not a valid date (b)."; + } + + month = Date::monthOfYear (mdy.substr (i, 3).c_str()); + i += 3; + break; + + // Long names + case 'A': + if (i + 2 >= mdy.length () || + isdigit (mdy[i + 0]) || + isdigit (mdy[i + 1]) || + isdigit (mdy[i + 2])) + { + throw std::string ("\"") + mdy + "\" is not a valid date (A)."; + } + + i += Date::dayName( Date::dayOfWeek (mdy.substr (i, 3).c_str()) ).size(); + break; + + case 'B': + if (i + 2 >= mdy.length () || + isdigit (mdy[i + 0]) || + isdigit (mdy[i + 1]) || + isdigit (mdy[i + 2])) + { + throw std::string ("\"") + mdy + "\" is not a valid date (B)."; + } + + month = Date::monthOfYear (mdy.substr (i, 3).c_str()); + i += Date::monthName(month).size(); + break; + default: if (i >= mdy.length () || mdy[i] != format[f]) { - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (DEFAULT)."; } ++i; break; @@ -190,7 +256,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */) throw std::string ("\"") + mdy + "\" is not a valid date in " + format + " format."; if (!valid (month, day, year)) - throw std::string ("\"") + mdy + "\" is not a valid date."; + throw std::string ("\"") + mdy + "\" is not a valid date (VALID)."; // Duplicate Date::Date (const int, const int, const int); struct tm t = {0}; @@ -256,13 +322,18 @@ const std::string Date::toString (const std::string& format /*= "m/d/Y" */) cons char c = localFormat[i]; switch (c) { - case 'm': sprintf (buffer, "%d", this->month ()); break; - case 'M': sprintf (buffer, "%02d", this->month ()); break; - case 'd': sprintf (buffer, "%d", this->day ()); break; - case 'D': sprintf (buffer, "%02d", this->day ()); break; - case 'y': sprintf (buffer, "%02d", this->year () % 100); break; - case 'Y': sprintf (buffer, "%d", this->year ()); break; - default: sprintf (buffer, "%c", c); break; + case 'm': sprintf (buffer, "%d", this->month ()); break; + case 'M': sprintf (buffer, "%02d", this->month ()); break; + case 'd': sprintf (buffer, "%d", this->day ()); break; + case 'D': sprintf (buffer, "%02d", this->day ()); break; + case 'y': sprintf (buffer, "%02d", this->year () % 100); break; + case 'Y': sprintf (buffer, "%d", this->year ()); break; + case 'a': sprintf (buffer, "%.3s", Date::dayName (dayOfWeek ()).c_str ()); break; + case 'A': sprintf (buffer, "%s", Date::dayName (dayOfWeek ()).c_str ()); break; + case 'b': sprintf (buffer, "%.3s", Date::monthName (month ()).c_str ()); break; + case 'B': sprintf (buffer, "%.9s", Date::monthName (month ()).c_str ()); break; + case 'V': sprintf (buffer, "%02d", Date::weekOfYear (Date::dayOfWeek (context.config.get ("weekstart")))); break; + default: sprintf (buffer, "%c", c); break; } formatted += buffer; @@ -437,13 +508,34 @@ int Date::dayOfWeek (const std::string& input) { std::string in = lowerCase (input); - if (in == "sunday") return 0; - if (in == "monday") return 1; - if (in == "tuesday") return 2; - if (in == "wednesday") return 3; - if (in == "thursday") return 4; - if (in == "friday") return 5; - if (in == "saturday") return 6; + if (in == "sunday" || in == "sun") return 0; + if (in == "monday" || in == "mon") return 1; + if (in == "tuesday" || in == "tue") return 2; + if (in == "wednesday" || in == "wed") return 3; + if (in == "thursday" || in == "thu") return 4; + if (in == "friday" || in == "fri") return 5; + if (in == "saturday" || in == "sat") return 6; + + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +int Date::monthOfYear (const std::string& input) +{ + std::string in = lowerCase (input); + + if (in == "january" || in == "jan") return 1; + if (in == "february" || in == "feb") return 2; + if (in == "march" || in == "mar") return 3; + if (in == "april" || in == "apr") return 4; + if (in == "may" || in == "may") return 5; + if (in == "june" || in == "jun") return 6; + if (in == "july" || in == "jul") return 7; + if (in == "august" || in == "aug") return 8; + if (in == "september" || in == "sep") return 9; + if (in == "october" || in == "oct") return 10; + if (in == "november" || in == "nov") return 11; + if (in == "december" || in == "dec") return 12; return -1; } diff --git a/src/Date.h b/src/Date.h index 68385515f..27d542f69 100644 --- a/src/Date.h +++ b/src/Date.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2010, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham, Federico Hernandez. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -58,6 +58,7 @@ public: static std::string dayName (int); static int weekOfYear (const std::string&); static int dayOfWeek (const std::string&); + static int monthOfYear (const std::string&); int month () const; int day () const; diff --git a/src/Directory.cpp b/src/Directory.cpp index 2f7599996..068eb973f 100644 --- a/src/Directory.cpp +++ b/src/Directory.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "Directory.h" //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Path.cpp b/src/Path.cpp index a6551e423..b6613a0b8 100644 --- a/src/Path.cpp +++ b/src/Path.cpp @@ -65,6 +65,12 @@ Path& Path::operator= (const Path& other) return *this; } +//////////////////////////////////////////////////////////////////////////////// +Path::operator std::string () const +{ + return data; +} + //////////////////////////////////////////////////////////////////////////////// std::string Path::name () const { @@ -121,6 +127,15 @@ bool Path::is_directory () const return false; } +//////////////////////////////////////////////////////////////////////////////// +bool Path::is_absolute () const +{ + if (data.length () && data.substr (0, 1) == "/") + return true; + + return false; +} + //////////////////////////////////////////////////////////////////////////////// bool Path::readable () const { @@ -187,7 +202,7 @@ std::vector Path::glob (const std::string& pattern) glob_t g; if (!::glob (pattern.c_str (), GLOB_ERR | GLOB_BRACE | GLOB_TILDE, NULL, &g)) - for (int i = 0; i < g.gl_matchc; ++i) + for (int i = 0; i < (int) g.gl_pathc; ++i) results.push_back (g.gl_pathv[i]); globfree (&g); diff --git a/src/Path.h b/src/Path.h index 9dc2ad440..b934d0ba3 100644 --- a/src/Path.h +++ b/src/Path.h @@ -39,11 +39,14 @@ public: virtual ~Path (); Path& operator= (const Path&); + operator std::string () const; + std::string name () const; std::string parent () const; std::string extension () const; bool exists () const; bool is_directory () const; + bool is_absolute () const; bool readable () const; bool writable () const; bool executable () const; diff --git a/src/Permission.cpp b/src/Permission.cpp index ec61a1916..7e2520f28 100644 --- a/src/Permission.cpp +++ b/src/Permission.cpp @@ -40,7 +40,7 @@ Permission::Permission () , quit (false) { // Turning confirmations off is the same as entering "all". - if (context.config.get ("confirmation", true) == false) + if (context.config.getBoolean ("confirmation") == false) allConfirmed = true; } diff --git a/src/TDB.cpp b/src/TDB.cpp index 5e31bc2ca..f2c8d4a4a 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -35,6 +35,8 @@ #include "text.h" #include "util.h" #include "TDB.h" +#include "Directory.h" +#include "File.h" #include "Table.h" #include "Timer.h" #include "Color.h" @@ -107,12 +109,13 @@ void TDB::clear () //////////////////////////////////////////////////////////////////////////////// void TDB::location (const std::string& path) { - if (access (expandPath (path).c_str (), F_OK)) + Directory d (path); + if (!d.exists ()) throw std::string ("Data location '") + path + "' does not exist, or is not readable and writable."; - mLocations.push_back (Location (path)); + mLocations.push_back (Location (d)); } //////////////////////////////////////////////////////////////////////////////// @@ -503,15 +506,15 @@ int TDB::nextId () //////////////////////////////////////////////////////////////////////////////// void TDB::undo () { - std::string location = expandPath (context.config.get ("data.location")); + Directory location (context.config.get ("data.location")); - std::string undoFile = location + "/undo.data"; - std::string pendingFile = location + "/pending.data"; - std::string completedFile = location + "/completed.data"; + std::string undoFile = location.data + "/undo.data"; + std::string pendingFile = location.data + "/pending.data"; + std::string completedFile = location.data + "/completed.data"; // load undo.data std::vector u; - slurp (undoFile, u); + File::read (undoFile, u); if (u.size () < 3) throw std::string ("There are no recorded transactions to undo."); @@ -643,7 +646,7 @@ void TDB::undo () // load pending.data std::vector p; - slurp (pendingFile, p); + File::read (pendingFile, p); // is 'current' in pending? foreach (task, p) @@ -665,15 +668,15 @@ void TDB::undo () } // Rewrite files. - spit (pendingFile, p); - spit (undoFile, u); + File::write (pendingFile, p); + File::write (undoFile, u); return; } } // load completed.data std::vector c; - slurp (completedFile, c); + File::read (completedFile, c); // is 'current' in completed? foreach (task, c) @@ -689,17 +692,17 @@ void TDB::undo () { c.erase (task); p.push_back (prior); - spit (completedFile, c); - spit (pendingFile, p); - spit (undoFile, u); + File::write (completedFile, c); + File::write (pendingFile, p); + File::write (undoFile, u); std::cout << "Modified task reverted." << std::endl; context.debug ("TDB::undo - task belongs in pending.data"); } else { *task = prior; - spit (completedFile, c); - spit (undoFile, u); + File::write (completedFile, c); + File::write (undoFile, u); std::cout << "Modified task reverted." << std::endl; context.debug ("TDB::undo - task belongs in completed.data"); } @@ -725,9 +728,10 @@ FILE* TDB::openAndLock (const std::string& file) // TODO Need provision here for read-only locations. // Check for access. - bool exists = access (file.c_str (), F_OK) ? false : true; + File f (file); + bool exists = f.exists (); if (exists) - if (access (file.c_str (), R_OK | W_OK)) + if (!f.readable () || !f.writable ()) throw std::string ("Task does not have the correct permissions for '") + file + "'."; @@ -755,8 +759,6 @@ FILE* TDB::openAndLock (const std::string& file) //////////////////////////////////////////////////////////////////////////////// void TDB::writeUndo (const Task& after, FILE* file) { - Timer t ("TDB::writeUndo"); - fprintf (file, "time %u\nnew %s---\n", (unsigned int) time (NULL), @@ -766,8 +768,6 @@ void TDB::writeUndo (const Task& after, FILE* file) //////////////////////////////////////////////////////////////////////////////// void TDB::writeUndo (const Task& before, const Task& after, FILE* file) { - Timer t ("TDB::writeUndo"); - fprintf (file, "time %u\nold %snew %s---\n", (unsigned int) time (NULL), diff --git a/src/Table.cpp b/src/Table.cpp index 06061d114..060b5e84b 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -52,6 +52,9 @@ #include "Timer.h" #include "text.h" #include "util.h" +#include "Context.h" + +extern Context context; //////////////////////////////////////////////////////////////////////////////// Table::Table () @@ -868,6 +871,50 @@ void Table::sort (std::vector & order) } break; + case ascendingDueDate: + { + if ((std::string)*left != "" && (std::string)*right == "") + break; + + else if ((std::string)*left == "" && (std::string)*right != "") + SWAP + + else + { + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); + + Date dl ((std::string)*left, format); + Date dr ((std::string)*right, format); + if (dl > dr) + SWAP + } + } + break; + + case descendingDueDate: + { + if ((std::string)*left != "" && (std::string)*right == "") + break; + + else if ((std::string)*left == "" && (std::string)*right != "") + SWAP + + else + { + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); + + Date dl ((std::string)*left, format); + Date dr ((std::string)*right, format); + if (dl < dr) + SWAP + } + } + break; + case ascendingPriority: if (((std::string)*left == "" && (std::string)*right != "") || ((std::string)*left == "M" && (std::string)*right == "L") || diff --git a/src/Table.h b/src/Table.h index a280854af..dfe18d04e 100644 --- a/src/Table.h +++ b/src/Table.h @@ -41,11 +41,13 @@ public: ascendingCharacter, ascendingPriority, ascendingDate, + ascendingDueDate, ascendingPeriod, descendingNumeric, descendingCharacter, descendingPriority, descendingDate, + descendingDueDate, descendingPeriod}; enum sizing {minimum = -1, flexible = 0}; diff --git a/src/command.cpp b/src/command.cpp index 3326f9d5a..cfadb7a36 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2010, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham, Federico Hernandez. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -37,6 +37,7 @@ #include #include "Permission.h" +#include "Directory.h" #include "text.h" #include "util.h" #include "main.h" @@ -70,12 +71,12 @@ int handleAdd (std::string &outs) // Override with default.project, if not specified. if (context.task.get ("project") == "") - context.task.set ("project", context.config.get ("default.project", "")); + context.task.set ("project", context.config.get ("default.project")); // Override with default.priority, if not specified. if (context.task.get ("priority") == "") { - std::string defaultPriority = context.config.get ("default.priority", ""); + std::string defaultPriority = context.config.get ("default.priority"); if (Att::validNameValue ("priority", "", defaultPriority)) context.task.set ("priority", defaultPriority); } @@ -96,7 +97,7 @@ int handleAdd (std::string &outs) // Only valid tasks can be added. context.task.validate (); - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); context.tdb.add (context.task); #ifdef FEATURE_NEW_ID @@ -123,7 +124,7 @@ int handleProjects (std::string &outs) context.filter.push_back (Att ("status", "pending")); std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); int quantity = context.tdb.loadPending (tasks, context.filter); context.tdb.commit (); context.tdb.unlock (); @@ -161,8 +162,8 @@ int handleProjects (std::string &outs) table.addColumn ("Pri:M"); table.addColumn ("Pri:H"); - if (context.config.get ("color", true) || - context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || + context.config.getBoolean ("_forcecolor")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -211,10 +212,10 @@ int handleProjects (std::string &outs) int handleCompletionProjects (std::string &outs) { std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; - if (context.config.get (std::string ("complete.all.projects"), false)) + if (context.config.getBoolean ("complete.all.projects")) context.tdb.load (tasks, filter); else context.tdb.loadPending (tasks, filter); @@ -246,7 +247,7 @@ int handleTags (std::string &outs) context.filter.push_back (Att ("status", "pending")); std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); int quantity = context.tdb.loadPending (tasks, context.filter); context.tdb.commit (); context.tdb.unlock (); @@ -273,8 +274,8 @@ int handleTags (std::string &outs) table.addColumn ("Tag"); table.addColumn ("Count"); - if (context.config.get ("color", true) || - context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || + context.config.getBoolean ("_forcecolor")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -312,10 +313,10 @@ int handleTags (std::string &outs) int handleCompletionTags (std::string &outs) { std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; - if (context.config.get (std::string ("complete.all.tags"), false)) + if (context.config.getBoolean ("complete.all.tags")) context.tdb.load (tasks, filter); else context.tdb.loadPending (tasks, filter); @@ -387,7 +388,7 @@ int handleCompletionVersion (std::string &outs) int handleCompletionIDs (std::string &outs) { std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); context.tdb.commit (); @@ -414,7 +415,7 @@ void handleUndo () { context.disallowModification (); - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); context.tdb.undo (); context.tdb.unlock (); } @@ -449,11 +450,11 @@ int handleVersion (std::string &outs) Color bold ("bold"); out << std::endl - << ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + << ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) ? bold.colorize (PACKAGE) : PACKAGE) << " " - << ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + << ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) ? bold.colorize (VERSION) : VERSION) << " built for " @@ -489,7 +490,7 @@ int handleVersion (std::string &outs) #endif << std::endl - << "Copyright (C) 2006 - 2010, P. Beckingham." + << "Copyright (C) 2006 - 2010, P. Beckingham, F. Hernandez." << std::endl << disclaimer.render () << link.render () @@ -504,6 +505,101 @@ int handleConfig (std::string &outs) { int rc = 0; std::stringstream out; + + // Support: + // task config name value # set name to value + // task config name "" # set name to blank + // task config name # remove name + if (context.args.size () >= 2) + { + std::string name = context.args[1]; + std::string value = ""; + + if (context.args.size () >= 3) + value = context.args[2]; + + if (name != "") + { + bool change = false; + + // Read .taskrc (or equivalent) + std::string contents; + File::read (context.config.original_file, contents); + + // task config name value + // task config name "" + if (context.args.size () >= 3) + { + // Find existing entry & overwrite + std::string::size_type pos = contents.find (name + "="); + if (pos != std::string::npos) + { + std::string::size_type eol = contents.find_first_of ("\r\f\n", pos); + if (eol == std::string::npos) + throw std::string ("Cannot find EOL after entry '") + name + "'"; + + if (confirm (std::string ("Are you sure you want to overwrite the value of '") + name + "' with '" + value + "'?")) + { + contents = contents.substr (0, pos) + + name + "=" + value + + contents.substr (eol); + change = true; + } + } + + // Not found, so append instead. + else + { + if (confirm (std::string ("Are you sure you want to add '") + name + "' with a value of '" + value + "'?")) + { + contents = contents + + "\n" + + name + "=" + value + + "\n"; + change = true; + } + } + } + + // task config name + else + { + // Remove name + std::string::size_type pos = contents.find (name + "="); + if (pos == std::string::npos) + throw std::string ("No entry named '") + name + "' found"; + + std::string::size_type eol = contents.find_first_of ("\r\f\n", pos); + if (eol == std::string::npos) + throw std::string ("Cannot find EOL after entry '") + name + "'"; + + if (confirm (std::string ("Are you sure you want to remove '") + name + "'?")) + { + contents = contents.substr (0, pos) + contents.substr (eol + 1); + change = true; + } + } + + // Write .taskrc (or equivalent) + if (change) + { + File::write (context.config.original_file, contents); + out << "Config file " + << context.config.original_file.data + << " modified." + << std::endl; + } + else + out << "No changes made." << std::endl; + } + else + throw std::string ("Specify the name of a config variable to modify."); + + outs = out.str (); + return rc; + } + + // No arguments - display config values instead. int width = context.getWidth (); std::vector all; @@ -512,11 +608,11 @@ int handleConfig (std::string &outs) // Create a table for output. Table table; table.setTableWidth (width); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); table.addColumn ("Config variable"); table.addColumn ("Value"); - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -533,12 +629,9 @@ int handleConfig (std::string &outs) foreach (i, all) { std::string value = context.config.get (*i); - if (value != "") - { - int row = table.addRow (); - table.addCell (row, 0, *i); - table.addCell (row, 1, value); - } + int row = table.addRow (); + table.addCell (row, 0, *i); + table.addCell (row, 1, value); } Color bold ("bold"); @@ -551,13 +644,13 @@ int handleConfig (std::string &outs) // These are the regular configuration variables. // Note that there is a leading and trailing space, to make searching easier. std::string recognized = - " blanklines bulk calendar.details calendar.details.report color color.active " - "color.due color.overdue color.pri.H color.pri.L color.pri.M color.pri.none " + " annotation.details blanklines bulk calendar.details calendar.details.report color " + "color.active color.due color.overdue color.pri.H color.pri.L color.pri.M color.pri.none " "color.recurring color.tagged color.footnote color.header color.debug color.alternate " "color.calendar.today color.calendar.due color.calendar.overdue color.calendar.weekend " - "confirmation curses data.location dateformat debug default.command default.priority " - "default.project defaultwidth due locale displayweeknumber echo.command " - "locking monthsperline nag next project shadow.command shadow.file " + "confirmation curses data.location dateformat reportdateformat debug default.command " + "default.priority default.project defaultwidth due locale displayweeknumber " + "echo.command locking monthsperline nag next project shadow.command shadow.file " "shadow.notify weekstart editor import.synonym.id import.synonym.uuid " "complete.all.projects complete.all.tags " #ifdef FEATURE_SHELL @@ -606,7 +699,6 @@ int handleConfig (std::string &outs) } out << context.config.checkForDeprecatedColor (); - out << context.config.checkForDuplicates (); // TODO Check for referenced but missing theme files. // TODO Check for referenced but missing string files. @@ -620,12 +712,14 @@ int handleConfig (std::string &outs) } else { - if (context.config.get ("data.location") == "") + Directory location (context.config.get ("data.location")); + + if (location.data == "") out << "Configuration error: data.location not specified in .taskrc " "file." << std::endl; - if (access (expandPath (context.config.get ("data.location")).c_str (), X_OK)) + if (! location.exists ()) out << "Configuration error: data.location contains a directory name" " that doesn't exist, or is unreadable." << std::endl; @@ -644,7 +738,7 @@ int handleDelete (std::string &outs) context.disallowModification (); std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -665,7 +759,7 @@ int handleDelete (std::string &outs) << task->get ("description") << "'?"; - if (!context.config.get (std::string ("confirmation"), false) || confirm (question.str ())) + if (!context.config.getBoolean ("confirmation") || confirm (question.str ())) { // Check for the more complex case of a recurring task. If this is a // recurring task, get confirmation to delete them all. @@ -685,7 +779,7 @@ int handleDelete (std::string &outs) sibling->set ("end", endTime); context.tdb.update (*sibling); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Deleting recurring task " << sibling->id << " '" @@ -718,7 +812,7 @@ int handleDelete (std::string &outs) task->set ("end", endTime); context.tdb.update (*task); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Deleting task " << task->id << " '" @@ -749,7 +843,7 @@ int handleStart (std::string &outs) context.disallowModification (); std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -767,7 +861,7 @@ int handleStart (std::string &outs) context.tdb.update (*task); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Started " << task->id << " '" @@ -805,7 +899,7 @@ int handleStop (std::string &outs) context.disallowModification (); std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -819,7 +913,7 @@ int handleStop (std::string &outs) task->remove ("start"); context.tdb.update (*task); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Stopped " << task->id << " '" @@ -854,7 +948,7 @@ int handleDone (std::string &outs) std::stringstream out; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -863,7 +957,7 @@ int handleDone (std::string &outs) context.filter.applySequence (tasks, context.sequence); Permission permission; - if (context.sequence.size () > (size_t) context.config.get ("bulk", 2)) + if (context.sequence.size () > (size_t) context.config.getInteger ("bulk")) permission.bigSequence (); bool nagged = false; @@ -895,7 +989,7 @@ int handleDone (std::string &outs) { context.tdb.update (*task); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Completed " << task->id << " '" @@ -924,7 +1018,7 @@ int handleDone (std::string &outs) context.tdb.commit (); context.tdb.unlock (); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Marked " << count << " task" @@ -961,7 +1055,7 @@ int handleExport (std::string &outs) // Get all the tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -987,7 +1081,7 @@ int handleModify (std::string &outs) std::stringstream out; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -996,7 +1090,7 @@ int handleModify (std::string &outs) context.filter.applySequence (tasks, context.sequence); Permission permission; - if (context.sequence.size () > (size_t) context.config.get ("bulk", 2)) + if (context.sequence.size () > (size_t) context.config.getInteger ("bulk")) permission.bigSequence (); foreach (task, tasks) @@ -1067,7 +1161,7 @@ int handleModify (std::string &outs) context.tdb.commit (); context.tdb.unlock (); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl; outs = out.str (); @@ -1081,7 +1175,7 @@ int handleAppend (std::string &outs) std::stringstream out; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -1090,7 +1184,7 @@ int handleAppend (std::string &outs) context.filter.applySequence (tasks, context.sequence); Permission permission; - if (context.sequence.size () > (size_t) context.config.get ("bulk", 2)) + if (context.sequence.size () > (size_t) context.config.getInteger ("bulk")) permission.bigSequence (); foreach (task, tasks) @@ -1118,7 +1212,7 @@ int handleAppend (std::string &outs) { context.tdb.update (*other); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Appended '" << context.task.get ("description") << "' to task " @@ -1135,7 +1229,7 @@ int handleAppend (std::string &outs) context.tdb.commit (); context.tdb.unlock (); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Appended " << count << " task" << (count == 1 ? "" : "s") << std::endl; outs = out.str (); @@ -1149,7 +1243,7 @@ int handlePrepend (std::string &outs) std::stringstream out; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -1158,7 +1252,7 @@ int handlePrepend (std::string &outs) context.filter.applySequence (tasks, context.sequence); Permission permission; - if (context.sequence.size () > (size_t) context.config.get ("bulk", 2)) + if (context.sequence.size () > (size_t) context.config.getInteger ("bulk")) permission.bigSequence (); foreach (task, tasks) @@ -1186,7 +1280,7 @@ int handlePrepend (std::string &outs) { context.tdb.update (*other); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Prepended '" << context.task.get ("description") << "' to task " @@ -1203,7 +1297,7 @@ int handlePrepend (std::string &outs) context.tdb.commit (); context.tdb.unlock (); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Prepended " << count << " task" << (count == 1 ? "" : "s") << std::endl; outs = out.str (); @@ -1217,7 +1311,7 @@ int handleDuplicate (std::string &outs) int count = 0; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -1260,7 +1354,7 @@ int handleDuplicate (std::string &outs) context.tdb.add (dup); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Duplicated " << task->id << " '" @@ -1270,7 +1364,7 @@ int handleDuplicate (std::string &outs) ++count; } - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) { out << "Duplicated " << count << " task" << (count == 1 ? "" : "s") << std::endl; #ifdef FEATURE_NEW_ID @@ -1295,7 +1389,7 @@ void handleShell () { // Display some kind of welcome message. Color bold (Color::nocolor, Color::nocolor, false, true, false); - std::cout << ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + std::cout << ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) ? bold.colorize (PACKAGE_STRING) : PACKAGE_STRING) << " shell" @@ -1319,7 +1413,7 @@ void handleShell () do { - std::cout << context.config.get ("shell.prompt", "task>") << " "; + std::cout << context.config.get ("shell.prompt") << " "; command = ""; std::getline (std::cin, command); @@ -1371,7 +1465,7 @@ int handleColor (std::string &outs) int rc = 0; std::stringstream out; - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { // If there is something in the description, then assume that is a color, // and display it as a sample. @@ -1516,7 +1610,7 @@ int handleAnnotate (std::string &outs) std::stringstream out; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); Filter filter; context.tdb.loadPending (tasks, filter); @@ -1524,7 +1618,7 @@ int handleAnnotate (std::string &outs) context.filter.applySequence (tasks, context.sequence); Permission permission; - if (context.sequence.size () > (size_t) context.config.get ("bulk", 2)) + if (context.sequence.size () > (size_t) context.config.getInteger ("bulk")) permission.bigSequence (); foreach (task, tasks) @@ -1538,7 +1632,7 @@ int handleAnnotate (std::string &outs) { context.tdb.update (*task); - if (context.config.get ("echo.command", true)) + if (context.config.getBoolean ("echo.command")) out << "Annotated " << task->id << " with '" diff --git a/src/custom.cpp b/src/custom.cpp index 89a0bc66b..1de5b6149 100644 --- a/src/custom.cpp +++ b/src/custom.cpp @@ -82,7 +82,7 @@ int handleCustomReport (const std::string& report, std::string &outs) // Get all the tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -161,7 +161,7 @@ int runCustomReport ( Table table; table.setTableWidth (context.getWidth ()); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); foreach (task, tasks) table.addRow (); @@ -251,7 +251,7 @@ int runCustomReport ( if (entered.length ()) { Date dt (::atoi (entered.c_str ())); - entered = dt.toString (context.config.get ("dateformat", "m/d/Y")); + entered = dt.toString (context.config.get ("dateformat")); table.addCell (row, columnCount, entered); } } @@ -270,7 +270,7 @@ int runCustomReport ( if (entered.length ()) { Date dt (::atoi (entered.c_str ())); - entered = dt.toStringWithTime (context.config.get ("dateformat", "m/d/Y")); + entered = dt.toStringWithTime (context.config.get ("dateformat")); table.addCell (row, columnCount, entered); } } @@ -289,7 +289,7 @@ int runCustomReport ( if (started.length ()) { Date dt (::atoi (started.c_str ())); - started = dt.toString (context.config.get ("dateformat", "m/d/Y")); + started = dt.toString (context.config.get ("dateformat")); table.addCell (row, columnCount, started); } } @@ -308,7 +308,7 @@ int runCustomReport ( if (started.length ()) { Date dt (::atoi (started.c_str ())); - started = dt.toStringWithTime (context.config.get ("dateformat", "m/d/Y")); + started = dt.toStringWithTime (context.config.get ("dateformat")); table.addCell (row, columnCount, started); } } @@ -327,7 +327,7 @@ int runCustomReport ( if (started.length ()) { Date dt (::atoi (started.c_str ())); - started = dt.toString (context.config.get ("dateformat", "m/d/Y")); + started = dt.toString (context.config.get ("dateformat")); table.addCell (row, columnCount, started); } } @@ -339,6 +339,8 @@ int runCustomReport ( table.setColumnWidth (columnCount, Table::minimum); table.setColumnJustification (columnCount, Table::right); + std::string format = context.config.get ("dateformat"); + std::string started; for (unsigned int row = 0; row < tasks.size(); ++row) { @@ -346,7 +348,7 @@ int runCustomReport ( if (started.length ()) { Date dt (::atoi (started.c_str ())); - started = dt.toStringWithTime (context.config.get ("dateformat", "m/d/Y")); + started = dt.toStringWithTime (format); table.addCell (row, columnCount, started); } } @@ -356,12 +358,16 @@ int runCustomReport ( { table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Due"); table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); + table.setColumnJustification (columnCount, Table::left); + + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); int row = 0; std::string due; foreach (task, tasks) - table.addCell (row++, columnCount, getDueDate (*task)); + table.addCell (row++, columnCount, getDueDate (*task, format)); dueColumn = columnCount; } @@ -508,7 +514,7 @@ int runCustomReport ( if (wait != "") { Date dt (::atoi (wait.c_str ())); - wait = dt.toString (context.config.get ("dateformat", "m/d/Y")); + wait = dt.toString (context.config.get ("dateformat")); table.addCell (row++, columnCount, wait); } } @@ -516,8 +522,8 @@ int runCustomReport ( // Common to all columns. // Add underline. - if ((context.config.get (std::string ("color"), true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) table.setColumnUnderline (columnCount); else table.setTableDashedUnderline (); @@ -550,13 +556,19 @@ int runCustomReport ( Table::ascendingPriority : Table::descendingPriority)); - else if (column == "entry" || column == "start" || column == "due" || - column == "wait" || column == "until" || column == "end") + else if (column == "entry" || column == "start" || column == "wait" || + column == "until" || column == "end") table.sortOn (columnIndex[column], (direction == '+' ? Table::ascendingDate : Table::descendingDate)); + else if (column == "due") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingDueDate : + Table::descendingDueDate)); + else if (column == "recur") table.sortOn (columnIndex[column], (direction == '+' ? @@ -571,10 +583,10 @@ int runCustomReport ( } // Now auto colorize all rows. - Color color_due (context.config.get ("color.due", "yellow")); - Color color_overdue (context.config.get ("color.overdue", "red")); - std::string due; + Color color_due (context.config.get ("color.due")); + Color color_overdue (context.config.get ("color.overdue")); + bool imminent; bool overdue; for (unsigned int row = 0; row < tasks.size (); ++row) @@ -593,7 +605,7 @@ int runCustomReport ( } } - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { Color c (tasks[row].get ("fg") + " " + tasks[row].get ("bg")); autoColorize (tasks[row], c); @@ -608,15 +620,15 @@ int runCustomReport ( } // If an alternating row color is specified, notify the table. - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { - Color alternate (context.config.get ("color.alternate", "")); + Color alternate (context.config.get ("color.alternate")); if (alternate.nontrivial ()) table.setTableAlternateColor (alternate); } // Limit the number of rows according to the report definition. - int maximum = context.config.get (std::string ("report.") + report + ".limit", (int)0); + int maximum = context.config.getInteger (std::string ("report.") + report + ".limit"); // If the custom report has a defined limit, then allow a numeric override. // This is an integer specified as a filter (limit:10). diff --git a/src/edit.cpp b/src/edit.cpp index 82ceea1a5..14ec35bd0 100644 --- a/src/edit.cpp +++ b/src/edit.cpp @@ -33,6 +33,8 @@ #include #include #include +#include "Directory.h" +#include "File.h" #include "Date.h" #include "Duration.h" #include "text.h" @@ -80,7 +82,7 @@ static std::string findDate ( if (value != "") { - Date dt (value, context.config.get ("dateformat", "m/d/Y")); + Date dt (value, context.config.get ("dateformat")); char epoch [16]; sprintf (epoch, "%d", (int)dt.toEpoch ()); return std::string (epoch); @@ -100,7 +102,7 @@ static std::string formatDate ( if (value.length ()) { Date dt (::atoi (value.c_str ())); - value = dt.toString (context.config.get ("dateformat", "m/d/Y")); + value = dt.toString (context.config.get ("dateformat")); } return value; @@ -162,7 +164,7 @@ static std::string formatTask (Task task) foreach (anno, annotations) { Date dt (::atoi (anno->name ().substr (11).c_str ())); - before << " Annotation: " << dt.toString (context.config.get ("dateformat", "m/d/Y")) + before << " Annotation: " << dt.toString (context.config.get ("dateformat")) << " " << anno->value () << std::endl; } @@ -499,7 +501,7 @@ static void parseTask (Task& task, const std::string& after) std::string::size_type gap = value.find (" "); if (gap != std::string::npos) { - Date when (value.substr (0, gap), context.config.get ("dateformat", "m/d/Y")); + Date when (value.substr (0, gap), context.config.get ("dateformat")); // This guarantees that if more than one annotation has the same date, // that the seconds will be different, thus unique, thus not squashed. @@ -521,20 +523,20 @@ static void parseTask (Task& task, const std::string& after) void editFile (Task& task) { // Check for file permissions. - std::string dataLocation = expandPath (context.config.get ("data.location")); - if (access (dataLocation.c_str (), X_OK)) + Directory location (context.config.get ("data.location")); + if (! location.writable ()) throw std::string ("Your data.location directory is not writable."); // Create a temp file name in data.location. std::stringstream file; - file << dataLocation << "/task." << getpid () << "." << task.id << ".task"; + file << location.data << "/task." << getpid () << "." << task.id << ".task"; // Format the contents, T -> text, write to a file. std::string before = formatTask (task); - spit (file.str (), before); + File::write (file.str (), before); // Determine correct editor: .taskrc:editor > $VISUAL > $EDITOR > vi - std::string editor = context.config.get ("editor", ""); + std::string editor = context.config.get ("editor"); char* peditor = getenv ("VISUAL"); if (editor == "" && peditor) editor = std::string (peditor); peditor = getenv ("EDITOR"); @@ -555,7 +557,7 @@ ARE_THESE_REALLY_HARMFUL: // Slurp file. std::string after; - slurp (file.str (), after, false); + File::read (file.str (), after); // Update task based on what can be parsed back out of the file, but only // if changes were made. @@ -582,7 +584,7 @@ ARE_THESE_REALLY_HARMFUL: // Preserve the edits. before = after; - spit (file.str (), before); + File::write (file.str (), before); if (confirm ("Task couldn't handle your edits. Would you like to try again?")) goto ARE_THESE_REALLY_HARMFUL; @@ -592,7 +594,7 @@ ARE_THESE_REALLY_HARMFUL: std::cout << "No edits were detected." << std::endl; // Cleanup. - unlink (file.str ().c_str ()); + File::remove (file.str ()); } //////////////////////////////////////////////////////////////////////////////// @@ -604,7 +606,7 @@ int handleEdit (std::string &outs) std::stringstream out; std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); Filter filter; context.tdb.loadPending (tasks, filter); diff --git a/src/import.cpp b/src/import.cpp index 2bba5e69f..c6389ae63 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -28,6 +28,7 @@ #include #include #include +#include "File.h" #include "Date.h" #include "text.h" #include "util.h" @@ -168,12 +169,12 @@ static void decorateTask (Task& task) task.setStatus (Task::pending); // Override with default.project, if not specified. - std::string defaultProject = context.config.get ("default.project", ""); + std::string defaultProject = context.config.get ("default.project"); if (!task.has ("project") && defaultProject != "") task.set ("project", defaultProject); // Override with default.priority, if not specified. - std::string defaultPriority = context.config.get ("default.priority", ""); + std::string defaultPriority = context.config.get ("default.priority"); if (!task.has ("priority") && defaultPriority != "" && Att::validNameValue ("priority", "", defaultPriority)) @@ -185,7 +186,7 @@ static std::string importTask_1_4_3 (const std::vector & lines) { std::vector failed; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) @@ -342,7 +343,7 @@ static std::string importTask_1_5_0 (const std::vector & lines) { std::vector failed; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) @@ -504,7 +505,7 @@ static std::string importTask_1_6_0 (const std::vector & lines) { std::vector failed; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) @@ -715,7 +716,7 @@ static std::string importTodoSh_2_0 (const std::vector & lines) { std::vector failed; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) @@ -840,7 +841,7 @@ static std::string importText (const std::vector & lines) std::vector failed; int count = 0; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) @@ -904,7 +905,7 @@ static std::string importCSV (const std::vector & lines) { std::vector failed; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); // Set up mappings. Assume no fields match. std::map mapping; @@ -1155,7 +1156,7 @@ int handleImport (std::string &outs) { // Load the file. std::vector all; - slurp (file, all, true); + File::read (file, all); std::vector lines; std::vector ::iterator it; diff --git a/src/interactive.cpp b/src/interactive.cpp index 3f3b44c8a..241f9cecd 100644 --- a/src/interactive.cpp +++ b/src/interactive.cpp @@ -137,10 +137,10 @@ int Context::interactive () int Context::getWidth () { // Determine window size, and set table accordingly. - int width = config.get ("defaultwidth", (int) 80); + int width = config.getInteger ("defaultwidth"); #ifdef HAVE_LIBNCURSES - if (config.get ("curses", true)) + if (config.getBoolean ("curses")) { #ifdef FEATURE_NCURSES_COLS initscr (); diff --git a/src/main.h b/src/main.h index 9e1f3f53a..a934d8c53 100644 --- a/src/main.h +++ b/src/main.h @@ -103,7 +103,7 @@ int handleReportCalendar (std::string &); int handleReportStats (std::string &); int handleReportTimesheet (std::string &); std::string getFullDescription (Task&); -std::string getDueDate (Task&); +std::string getDueDate (Task&, const std::string&); // custom.cpp int handleCustomReport (const std::string&, std::string &); diff --git a/src/recur.cpp b/src/recur.cpp index db4025b15..e4d31ac6f 100644 --- a/src/recur.cpp +++ b/src/recur.cpp @@ -390,7 +390,7 @@ int getDueState (const std::string& due) if (dt < thisDay) return 2; - int imminentperiod = context.config.get ("due", 7); + int imminentperiod = context.config.getInteger ("due"); if (imminentperiod == 0) return 1; @@ -406,7 +406,7 @@ int getDueState (const std::string& due) //////////////////////////////////////////////////////////////////////////////// bool nag (Task& task) { - std::string nagMessage = context.config.get ("nag", ""); + std::string nagMessage = context.config.get ("nag"); if (nagMessage != "") { // Load all pending tasks. diff --git a/src/report.cpp b/src/report.cpp index edad19730..69708aa15 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2010, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham, Federico Hernandez. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -37,6 +36,8 @@ #include #include "Context.h" +#include "Directory.h" +#include "File.h" #include "Date.h" #include "Table.h" #include "text.h" @@ -66,7 +67,7 @@ int shortUsage (std::string &outs) table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::flexible); table.setTableWidth (context.getWidth ()); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); int row = table.addRow (); table.addCell (row, 0, "Usage:"); @@ -186,8 +187,8 @@ int shortUsage (std::string &outs) table.addCell (row, 2, "Shows the task version number."); row = table.addRow (); - table.addCell (row, 1, "task config"); - table.addCell (row, 2, "Shows the task configuration."); + table.addCell (row, 1, "task config [name [value | '']]"); + table.addCell (row, 2, "Shows the task configuration, or can add, modify and remove settings."); row = table.addRow (); table.addCell (row, 1, "task help"); @@ -199,8 +200,9 @@ int shortUsage (std::string &outs) foreach (report, all) { std::string command = std::string ("task ") + *report + std::string (" [tags] [attrs] desc..."); - std::string description = context.config.get ( - std::string ("report.") + *report + ".description", std::string ("(missing description)")); + std::string description = context.config.get (std::string ("report.") + *report + ".description"); + if (description == "") + description = "(missing description)"; row = table.addRow (); table.addCell (row, 1, command); @@ -300,7 +302,7 @@ int handleInfo (std::string &outs) int rc = 0; // Get all the tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.loadPending (tasks, context.filter); context.tdb.commit (); @@ -315,13 +317,13 @@ int handleInfo (std::string &outs) { Table table; table.setTableWidth (context.getWidth ()); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); table.addColumn ("Name"); table.addColumn ("Value"); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -412,20 +414,24 @@ int handleInfo (std::string &outs) table.addCell (row, 0, "Due"); Date dt (atoi (task->get ("due").c_str ())); - std::string due = getDueDate (*task); + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); + + std::string due = getDueDate (*task, format); table.addCell (row, 1, due); overdue = (dt < now) ? true : false; - int imminentperiod = context.config.get ("due", 7); + int imminentperiod = context.config.getInteger ("due"); Date imminentDay = now + imminentperiod * 86400; imminent = dt < imminentDay ? true : false; - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { if (overdue) - table.setCellColor (row, 1, Color (context.config.get ("color.overdue", "red"))); + table.setCellColor (row, 1, Color (context.config.get ("color.overdue"))); else if (imminent) - table.setCellColor (row, 1, Color (context.config.get ("color.due", "green"))); + table.setCellColor (row, 1, Color (context.config.get ("color.due"))); } } @@ -435,7 +441,7 @@ int handleInfo (std::string &outs) row = table.addRow (); table.addCell (row, 0, "Waiting until"); Date dt (atoi (task->get ("wait").c_str ())); - table.addCell (row, 1, dt.toString (context.config.get ("dateformat", "m/d/Y"))); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); } // start @@ -444,7 +450,7 @@ int handleInfo (std::string &outs) row = table.addRow (); table.addCell (row, 0, "Start"); Date dt (atoi (task->get ("start").c_str ())); - table.addCell (row, 1, dt.toString (context.config.get ("dateformat", "m/d/Y"))); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); } // end @@ -453,7 +459,7 @@ int handleInfo (std::string &outs) row = table.addRow (); table.addCell (row, 0, "End"); Date dt (atoi (task->get ("end").c_str ())); - table.addCell (row, 1, dt.toString (context.config.get ("dateformat", "m/d/Y"))); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); } // tags ... @@ -478,7 +484,7 @@ int handleInfo (std::string &outs) row = table.addRow (); table.addCell (row, 0, "Entered"); Date dt (atoi (task->get ("entry").c_str ())); - std::string entry = dt.toString (context.config.get ("dateformat", "m/d/Y")); + std::string entry = dt.toString (context.config.get ("dateformat")); std::string age; std::string created = task->get ("entry"); @@ -531,7 +537,7 @@ int handleReportSummary (std::string &outs) int rc = 0; // Scan the pending tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -594,8 +600,8 @@ int handleReportSummary (std::string &outs) table.addColumn ("Complete"); table.addColumn ("0% 100%"); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -610,7 +616,7 @@ int handleReportSummary (std::string &outs) table.setColumnJustification (3, Table::right); table.sortOn (0, Table::ascendingCharacter); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); int barWidth = 30; foreach (i, allProjects) @@ -632,7 +638,7 @@ int handleReportSummary (std::string &outs) int completedBar = (c * barWidth) / (c + p); std::string bar; - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { bar = "\033[42m"; for (int b = 0; b < completedBar; ++b) @@ -726,7 +732,7 @@ int handleReportNext (std::string &outs) // Get all the tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -778,7 +784,7 @@ int handleReportHistory (std::string &outs) // Scan the pending tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -822,7 +828,7 @@ int handleReportHistory (std::string &outs) // Now build the table. Table table; - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); table.addColumn ("Year"); table.addColumn ("Month"); table.addColumn ("Added"); @@ -830,8 +836,8 @@ int handleReportHistory (std::string &outs) table.addColumn ("Deleted"); table.addColumn ("Net"); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -894,7 +900,7 @@ int handleReportHistory (std::string &outs) } table.addCell (row, 5, net); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && net) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && net) table.setCellColor (row, 5, net > 0 ? Color (Color::red) : Color (Color::green)); } @@ -905,7 +911,7 @@ int handleReportHistory (std::string &outs) row = table.addRow (); table.addCell (row, 1, "Average"); - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) table.setRowColor (row, Color (Color::nocolor, Color::nocolor, false, true, false)); table.addCell (row, 2, totalAdded / (table.rowCount () - 2)); table.addCell (row, 3, totalCompleted / (table.rowCount () - 2)); @@ -938,7 +944,7 @@ int handleReportGHistory (std::string &outs) // Scan the pending tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -984,13 +990,13 @@ int handleReportGHistory (std::string &outs) // Now build the table. Table table; - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); table.addColumn ("Year"); table.addColumn ("Month"); table.addColumn ("Number Added/Completed/Deleted"); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1049,7 +1055,7 @@ int handleReportGHistory (std::string &outs) unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; std::string bar = ""; - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { char number[24]; std::string aBar = ""; @@ -1109,7 +1115,7 @@ int handleReportGHistory (std::string &outs) << table.render () << std::endl; - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) out << "Legend: " << color_added.colorize ("added") << ", " @@ -1135,7 +1141,7 @@ int handleReportTimesheet (std::string &outs) { // Scan the pending tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -1145,7 +1151,7 @@ int handleReportTimesheet (std::string &outs) int width = context.getWidth (); // What day of the week does the user consider the first? - int weekStart = Date::dayOfWeek (context.config.get ("weekstart", "Sunday")); + int weekStart = Date::dayOfWeek (context.config.get ("weekstart")); if (weekStart != 0 && weekStart != 1) throw std::string ("The 'weekstart' configuration variable may " "only contain 'Sunday' or 'Monday'."); @@ -1164,7 +1170,7 @@ int handleReportTimesheet (std::string &outs) if (context.sequence.size () == 1) quantity = context.sequence[0]; - bool color = context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false); + bool color = context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"); std::stringstream out; for (int week = 0; week < quantity; ++week) @@ -1172,9 +1178,9 @@ int handleReportTimesheet (std::string &outs) Date endString (end); endString -= 86400; - std::string title = start.toString (context.config.get ("dateformat", "m/d/Y")) + std::string title = start.toString (context.config.get ("dateformat")) + " - " - + endString.toString (context.config.get ("dateformat", "m/d/Y")); + + endString.toString (context.config.get ("dateformat")); Color bold (Color::nocolor, Color::nocolor, false, true, false); out << std::endl @@ -1189,7 +1195,7 @@ int handleReportTimesheet (std::string &outs) completed.addColumn ("Due"); completed.addColumn ("Description"); - if (color && context.config.get (std::string ("fontunderline"), "true")) + if (color && context.config.getBoolean ("fontunderline")) { completed.setColumnUnderline (1); completed.setColumnUnderline (2); @@ -1218,7 +1224,7 @@ int handleReportTimesheet (std::string &outs) { int row = completed.addRow (); completed.addCell (row, 1, task->get ("project")); - completed.addCell (row, 2, getDueDate (*task)); + completed.addCell (row, 2, getDueDate (*task, context.config.get("dateformat"))); completed.addCell (row, 3, getFullDescription (*task)); if (color) @@ -1245,7 +1251,7 @@ int handleReportTimesheet (std::string &outs) started.addColumn ("Due"); started.addColumn ("Description"); - if (color && context.config.get (std::string ("fontunderline"), "true")) + if (color && context.config.getBoolean ("fontunderline")) { completed.setColumnUnderline (1); completed.setColumnUnderline (2); @@ -1274,7 +1280,7 @@ int handleReportTimesheet (std::string &outs) { int row = started.addRow (); started.addCell (row, 1, task->get ("project")); - started.addCell (row, 2, getDueDate (*task)); + started.addCell (row, 2, getDueDate (*task, context.config.get ("dateformat"))); started.addCell (row, 3, getFullDescription (*task)); if (color) @@ -1312,10 +1318,10 @@ std::string renderMonths ( int monthsPerLine) { Table table; - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); // What day of the week does the user consider the first? - int weekStart = Date::dayOfWeek (context.config.get ("weekstart", "Sunday")); + int weekStart = Date::dayOfWeek (context.config.get ("weekstart")); if (weekStart != 0 && weekStart != 1) throw std::string ("The 'weekstart' configuration variable may " "only contain 'Sunday' or 'Monday'."); @@ -1346,8 +1352,8 @@ std::string renderMonths ( table.addColumn ("Sa"); } - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { table.setColumnUnderline (i + 1); table.setColumnUnderline (i + 2); @@ -1418,7 +1424,7 @@ std::string renderMonths ( int dow = temp.dayOfWeek (); int woy = temp.weekOfYear (weekStart); - if (context.config.get ("displayweeknumber", true)) + if (context.config.getBoolean ("displayweeknumber")) table.addCell (row, (8 * mpl), woy); // Calculate column id. @@ -1431,12 +1437,12 @@ std::string renderMonths ( table.addCell (row, thisCol, d); - Color color_today (context.config.get ("color.calendar.today", "black on cyan")); - Color color_due (context.config.get ("color.calendar.due", "black on green")); - Color color_overdue (context.config.get ("color.calendar.overdue", "black on red")); - Color color_weekend (context.config.get ("color.calendar.weekend", "black on white")); + Color color_today (context.config.get ("color.calendar.today")); + Color color_due (context.config.get ("color.calendar.due")); + Color color_overdue (context.config.get ("color.calendar.overdue")); + Color color_weekend (context.config.get ("color.calendar.weekend")); - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { if (dow == 0 || dow == 6) table.setCellColor (row, thisCol, color_weekend); @@ -1454,7 +1460,7 @@ std::string renderMonths ( { Date due (atoi (task->get ("due").c_str ())); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && due.day () == d && due.month () == months[mpl] && due.year () == years[mpl]) @@ -1480,7 +1486,7 @@ int handleReportCalendar (std::string &outs) // Each month requires 28 text columns width. See how many will actually // fit. But if a preference is specified, and it fits, use it. int width = context.getWidth (); - int preferredMonthsPerLine = (context.config.get (std::string ("monthsperline"), 0)); + int preferredMonthsPerLine = (context.config.getInteger ("monthsperline")); int monthsThatFit = width / 26; int monthsPerLine = monthsThatFit; @@ -1490,7 +1496,7 @@ int handleReportCalendar (std::string &outs) // Get all the tasks. std::vector tasks; Filter filter; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.loadPending (tasks, filter); context.tdb.commit (); @@ -1529,7 +1535,7 @@ int handleReportCalendar (std::string &outs) // task cal 2010 monthsToDisplay = 12; mFrom = 1; - yFrom = atoi( context.args[1].data()); + yFrom = atoi (context.args[1].c_str ()); } } else if (numberOfArgs == 3) { @@ -1541,15 +1547,15 @@ int handleReportCalendar (std::string &outs) else { // task cal 8 2010 monthsToDisplay = monthsPerLine; - mFrom = atoi( context.args[1].data()); - yFrom = atoi( context.args[2].data()); + mFrom = atoi (context.args[1].c_str ()); + yFrom = atoi (context.args[2].c_str ()); } } else if (numberOfArgs == 4) { // task cal 8 2010 y monthsToDisplay = 12; - mFrom = atoi( context.args[1].data()); - yFrom = atoi( context.args[2].data()); + mFrom = atoi (context.args[1].c_str ()); + yFrom = atoi (context.args[2].c_str ()); } int countDueDates = 0; @@ -1640,12 +1646,12 @@ int handleReportCalendar (std::string &outs) } } - Color color_today (context.config.get ("color.calendar.today", "black on cyan")); - Color color_due (context.config.get ("color.calendar.due", "black on green")); - Color color_overdue (context.config.get ("color.calendar.overdue", "black on red")); - Color color_weekend (context.config.get ("color.calendar.weekend", "black on white")); + Color color_today (context.config.get ("color.calendar.today")); + Color color_due (context.config.get ("color.calendar.due")); + Color color_overdue (context.config.get ("color.calendar.overdue")); + Color color_weekend (context.config.get ("color.calendar.weekend")); - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) out << "Legend: " << color_today.colorize ("today") << ", " @@ -1658,7 +1664,7 @@ int handleReportCalendar (std::string &outs) << optionalBlankLine () << std::endl; - if (context.config.get (std::string ("calendar.details"), false)) + if (context.config.getBoolean ("calendar.details")) { --details_mFrom; if (details_mFrom == 0) @@ -1676,12 +1682,12 @@ int handleReportCalendar (std::string &outs) } Date date_after (details_mFrom, details_dFrom, details_yFrom); - std::string after = date_after.toString (context.config.get ("dateformat", "m/d/Y")); - - Date date_before (mTo, 1, yTo); - std::string before = date_before.toString (context.config.get ("dateformat", "m/d/Y")); + std::string after = date_after.toString (context.config.get ("dateformat")); - std::string report = context.config.get ("calendar.details.report", "list"); + Date date_before (mTo, 1, yTo); + std::string before = date_before.toString (context.config.get ("dateformat")); + + std::string report = context.config.get ("calendar.details.report"); std::string report_filter = context.config.get ("report." + report + ".filter"); report_filter += " due.after:" + after + " due.before:" + before; @@ -1711,30 +1717,26 @@ int handleReportStats (std::string &outs) // Go get the file sizes. size_t dataSize = 0; - struct stat s; - std::string location = expandPath (context.config.get ("data.location")); - std::string file = location + "/pending.data"; - if (!stat (file.c_str (), &s)) - dataSize += s.st_size; + Directory location (context.config.get ("data.location")); + File pending (location.data + "/pending.data"); + dataSize += pending.size (); - file = location + "/completed.data"; - if (!stat (file.c_str (), &s)) - dataSize += s.st_size; + File completed (location.data + "/completed.data"); + dataSize += completed.size (); - file = location + "/undo.data"; - if (!stat (file.c_str (), &s)) - dataSize += s.st_size; + File undo (location.data + "/undo.data"); + dataSize += undo.size (); - std::vector undo; - slurp (file, undo, false); + std::vector undoTxns; + File::read (undo, undoTxns); int undoCount = 0; - foreach (tx, undo) + foreach (tx, undoTxns) if (tx->substr (0, 3) == "---") ++undoCount; // Get all the tasks. std::vector tasks; - context.tdb.lock (context.config.get ("locking", true)); + context.tdb.lock (context.config.getBoolean ("locking")); handleRecurrence (); context.tdb.load (tasks, context.filter); context.tdb.commit (); @@ -1801,12 +1803,12 @@ int handleReportStats (std::string &outs) Table table; table.setTableWidth (context.getWidth ()); table.setTableIntraPadding (2); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); + table.setDateFormat (context.config.get ("dateformat")); table.addColumn ("Category"); table.addColumn ("Data"); - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1879,12 +1881,12 @@ int handleReportStats (std::string &outs) Date e (earliest); row = table.addRow (); table.addCell (row, 0, "Oldest task"); - table.addCell (row, 1, e.toString (context.config.get ("dateformat", "m/d/Y"))); + table.addCell (row, 1, e.toString (context.config.get ("dateformat"))); Date l (latest); row = table.addRow (); table.addCell (row, 0, "Newest task"); - table.addCell (row, 1, l.toString (context.config.get ("dateformat", "m/d/Y"))); + table.addCell (row, 1, l.toString (context.config.get ("dateformat"))); row = table.addRow (); table.addCell (row, 0, "Task used for"); @@ -1946,7 +1948,7 @@ void gatherNextTasks (std::vector & tasks) Date now; // How many items per project? Default 2. - int limit = context.config.get ("next", 2); + int limit = context.config.getInteger ("next"); // due:< 1wk, pri:* foreach (task, tasks) @@ -2113,24 +2115,44 @@ std::string getFullDescription (Task& task) std::vector annotations; task.getAnnotations (annotations); - foreach (anno, annotations) - { - Date dt (atoi (anno->name ().substr (11).c_str ())); - std::string when = dt.toString (context.config.get ("dateformat", "m/d/Y")); - desc += "\n" + when + " " + anno->value (); - } + + if (annotations.size () != 0) + switch (context.config.getInteger ("annotation.details")) + { + case 0: + desc = "+" + desc; + break; + case 1: + { + if (annotations.size () > 1) + desc = "+" + desc; + Att anno (annotations.back()); + Date dt (atoi (anno.name ().substr (11).c_str ())); + std::string when = dt.toString (context.config.get ("dateformat")); + desc += "\n" + when + " " + anno.value (); + } + break; + case 2: + foreach (anno, annotations) + { + Date dt (atoi (anno->name ().substr (11).c_str ())); + std::string when = dt.toString (context.config.get ("dateformat")); + desc += "\n" + when + " " + anno->value (); + } + break; + } return desc; } /////////////////////////////////////////////////////////////////////////////// -std::string getDueDate (Task& task) +std::string getDueDate (Task& task, const std::string& format) { std::string due = task.get ("due"); if (due.length ()) { Date d (atoi (due.c_str ())); - due = d.toString (context.config.get ("dateformat", "m/d/Y")); + due = d.toString (format); } return due; diff --git a/src/tests/add.t b/src/tests/add.t index f18443978..00541d9f1 100755 --- a/src/tests/add.t +++ b/src/tests/add.t @@ -33,7 +33,8 @@ use Test::More tests => 13; # Create the rc file. if (open my $fh, '>', 'add.rc') { - print $fh "data.location=.\n"; + print $fh "data.location=.\n", + "confirmation=off\n"; close $fh; ok (-r 'add.rc', 'Created add.rc'); } diff --git a/src/tests/annotate.t b/src/tests/annotate.t index 69f35412d..289699c3c 100755 --- a/src/tests/annotate.t +++ b/src/tests/annotate.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 9; +use Test::More tests => 37; # Create the rc file. if (open my $fh, '>', 'annotate.rc') @@ -36,6 +36,7 @@ if (open my $fh, '>', 'annotate.rc') # Note: Use 'rrr' to guarantee a unique report name. Using 'r' conflicts # with 'recurring'. print $fh "data.location=.\n", + "confirmation=off\n", "report.rrr.description=rrr\n", "report.rrr.columns=id,description\n", "report.rrr.sort=id+\n"; @@ -43,27 +44,76 @@ if (open my $fh, '>', 'annotate.rc') ok (-r 'annotate.rc', 'Created annotate.rc'); } -# Add two tasks, annotate one twice. +# Add four tasks, annotate one three times, one twice, one just once and one none. qx{../task rc:annotate.rc add one}; qx{../task rc:annotate.rc add two}; -qx{../task rc:annotate.rc annotate 1 foo}; -sleep 2; -qx{../task rc:annotate.rc annotate 1 bar}; +qx{../task rc:annotate.rc add three}; +qx{../task rc:annotate.rc add four}; +qx{../task rc:annotate.rc annotate 1 foo1}; +sleep 1; +qx{../task rc:annotate.rc annotate 1 foo2}; +sleep 1; +qx{../task rc:annotate.rc annotate 1 foo3}; +sleep 1; +qx{../task rc:annotate.rc annotate 2 bar1}; +sleep 1; +qx{../task rc:annotate.rc annotate 2 bar2}; +sleep 1; +qx{../task rc:annotate.rc annotate 3 baz1}; + my $output = qx{../task rc:annotate.rc rrr}; # ID Description # -- ------------------------------- # 1 one -# 3/24/2009 foo -# 3/24/2009 bar +# 3/24/2009 foo1 +# 3/24/2009 foo2 +# 3/24/2009 foo3 # 2 two +# 3/24/2009 bar1 +# 3/24/2009 bar2 +# 3 three +# 3/24/2009 baz1 +# 4 four # -# 2 tasks +# 4 tasks like ($output, qr/1 one/, 'task 1'); like ($output, qr/2 two/, 'task 2'); -like ($output, qr/one.+\d{1,2}\/\d{1,2}\/\d{4} foo/ms, 'first annotation'); -like ($output, qr/foo.+\d{1,2}\/\d{1,2}\/\d{4} bar/ms, 'second annotation'); -like ($output, qr/2 tasks/, 'count'); +like ($output, qr/3 three/, 'task 3'); +like ($output, qr/4 four/, 'task 4'); +like ($output, qr/one.+\d{1,2}\/\d{1,2}\/\d{4} foo1/ms, 'first annotation task 1'); +like ($output, qr/foo1.+\d{1,2}\/\d{1,2}\/\d{4} foo2/ms, 'second annotation task 1'); +like ($output, qr/foo2.+\d{1,2}\/\d{1,2}\/\d{4} foo3/ms, 'third annotation task 1'); +like ($output, qr/two.+\d{1,2}\/\d{1,2}\/\d{4} bar1/ms, 'first annotation task 2'); +like ($output, qr/bar1.+\d{1,2}\/\d{1,2}\/\d{4} bar2/ms, 'second annotation task 2'); +like ($output, qr/three.+\d{1,2}\/\d{1,2}\/\d{4} baz1/ms,'first annotation task 3'); +like ($output, qr/4 tasks/, 'count'); + +$output = qx{../task rc:annotate.rc rc.annotation.details:1 rrr}; +like ($output, qr/1 \+one/, 'task 1'); +like ($output, qr/2 \+two/, 'task 2'); +like ($output, qr/3 three/, 'task 3'); +like ($output, qr/4 four/, 'task 4'); +unlike ($output, qr/one.+\d{1,2}\/\d{1,2}\/\d{4} foo1/ms, 'first annotation task 1'); +unlike ($output, qr/foo1.+\d{1,2}\/\d{1,2}\/\d{4} foo2/ms, 'second annotation task 1'); +like ($output, qr/one.+\d{1,2}\/\d{1,2}\/\d{4} foo3/ms, 'third annotation task 1'); +unlike ($output, qr/two.+\d{1,2}\/\d{1,2}\/\d{4} bar1/ms, 'first annotation task 2'); +like ($output, qr/two.+\d{1,2}\/\d{1,2}\/\d{4} bar2/ms, 'second annotation task 2'); +like ($output, qr/three.+\d{1,2}\/\d{1,2}\/\d{4} baz1/ms, 'third annotation task 3'); +like ($output, qr/4 tasks/, 'count'); + +$output = qx{../task rc:annotate.rc rc.annotation.details:0 rrr}; +like ($output, qr/1 \+one/, 'task 1'); +like ($output, qr/2 \+two/, 'task 2'); +like ($output, qr/3 \+three/, 'task 3'); +like ($output, qr/4 four/, 'task 4'); +unlike ($output, qr/one.+\d{1,2}\/\d{1,2}\/\d{4} foo1/ms, 'first annotation task 1'); +unlike ($output, qr/foo1.+\d{1,2}\/\d{1,2}\/\d{4} foo2/ms, 'second annotation task 1'); +unlike ($output, qr/foo2.+\d{1,2}\/\d{1,2}\/\d{4} foo3/ms, 'third annotation task 1'); +unlike ($output, qr/two.+\d{1,2}\/\d{1,2}\/\d{4} bar1/ms, 'first annotation task 2'); +unlike ($output, qr/bar1.+\d{1,2}\/\d{1,2}\/\d{4} bar2/ms, 'second annotation task 2'); +unlike ($output, qr/three.+\d{1,2}\/\d{1,2}\/\d{4} baz1/ms, 'third annotation task 3'); +like ($output, qr/4 tasks/, 'count'); # Cleanup. unlink 'pending.data'; diff --git a/src/tests/att.t.cpp b/src/tests/att.t.cpp index 3721b98a4..66df3d8e5 100644 --- a/src/tests/att.t.cpp +++ b/src/tests/att.t.cpp @@ -155,8 +155,8 @@ int main (int argc, char** argv) t.ok (good, "Att::mod (noword)"); good = true; - try {a6.mod ("fartwizzle");} catch (...) {good = false;} - t.notok (good, "Att::mod (fartwizzle)"); + try {a6.mod ("unrecognized");} catch (...) {good = false;} + t.notok (good, "Att::mod (unrecognized)"); // Att::parse Nibbler n (""); diff --git a/src/tests/basic.t b/src/tests/basic.t index 3ff3d58d4..c33a569f0 100755 --- a/src/tests/basic.t +++ b/src/tests/basic.t @@ -33,7 +33,8 @@ use Test::More tests => 7; # Create the rc file. if (open my $fh, '>', 'basic.rc') { - print $fh "data.location=.\n"; + print $fh "data.location=.\n", + "default.command=\n"; close $fh; ok (-r 'basic.rc', 'Created basic.rc'); } diff --git a/src/tests/config.duplicate.t b/src/tests/config.duplicate.t deleted file mode 100755 index 996dbdcf2..000000000 --- a/src/tests/config.duplicate.t +++ /dev/null @@ -1,59 +0,0 @@ -#! /usr/bin/perl -################################################################################ -## task - a command line task list manager. -## -## Copyright 2006 - 2010, Paul Beckingham. -## All rights reserved. -## -## This program is free software; you can redistribute it and/or modify it under -## the terms of the GNU General Public License as published by the Free Software -## Foundation; either version 2 of the License, or (at your option) any later -## version. -## -## This program is distributed in the hope that it will be useful, but WITHOUT -## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -## details. -## -## You should have received a copy of the GNU General Public License along with -## this program; if not, write to the -## -## Free Software Foundation, Inc., -## 51 Franklin Street, Fifth Floor, -## Boston, MA -## 02110-1301 -## USA -## -################################################################################ - -use strict; -use warnings; -use Test::More tests => 6; - -# Create the rc file. -if (open my $fh, '>', 'duplicate.rc') -{ - print $fh "data.location=.\n", - "data.location=.\n", - "color=off\n"; - close $fh; - ok (-r 'duplicate.rc', 'Created duplicate.rc'); -} - -# Test the add command. -my $output = qx{../task rc:duplicate.rc config}; -like ($output, qr/data\.location/ms, 'Duplicate entry detected'); -unlike ($output, qr/colorl/ms, 'Single entry not ignored'); - -# Cleanup. -unlink 'pending.data'; -ok (!-r 'pending.data', 'Removed pending.data'); - -unlink 'undo.data'; -ok (!-r 'undo.data', 'Removed undo.data'); - -unlink 'duplicate.rc'; -ok (!-r 'duplicate.rc', 'Removed duplicate.rc'); - -exit 0; - diff --git a/src/tests/config.t.cpp b/src/tests/config.t.cpp index 40d6c6ecf..3191218e3 100644 --- a/src/tests/config.t.cpp +++ b/src/tests/config.t.cpp @@ -33,69 +33,47 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (18); + UnitTest t (11); - // void set (const std::string&, const int); - // int get (const std::string&, const int); Config c; - c.set ("int1", 0); - t.is (c.get ("int1", 9), 0, "Config::set/get int"); - - c.set ("int2", 3); - t.is (c.get ("int2", 9), 3, "Config::set/get int"); - - c.set ("int3", -9); - t.is (c.get ("int3", 9), -9, "Config::set/get int"); - - // void set (const std::string&, const double); - // double get (const std::string&, const double); - c.set ("double1", 0.0); - t.is (c.get ("double1", 9.0), 0.0, "Config::set/get double"); - - c.set ("double2", 3.0); - t.is (c.get ("double2", 9.0), 3.0, "Config::set/get double"); - - c.set ("double3", -9.0); - t.is (c.get ("double3", 9.0), -9.0, "Config::set/get double"); // void set (const std::string&, const std::string&); - c.set ("str1", "one"); - t.is (c.get ("str1", ""), "one", "Config::set/get std::string"); - - c.set ("str1", ""); - t.is (c.get ("str1", "no"), "", "Config::set/get std::string"); - - // const std::string get (const char*); - c.set ("str1", "one"); - t.is (c.get ((char*) "str1"), (char*)"one", "Config::set/get char*"); - - // const std::string get (const char*, const char*); - c.set ("str1", "one"); - t.is (c.get ((char*)"str1", (char*)""), "one", "Config::set/get char*"); - - c.set ("str1", ""); - t.is (c.get ((char*)"str1", (char*)"no"), "", "Config::set/get char*"); - // const std::string get (const std::string&); c.set ("str1", "one"); - t.is (c.get (std::string ("str1")), "one", "Config::set/get std::string"); + t.is (c.get ("str1"), "one", "Config::set/get std::string"); c.set ("str1", ""); - t.is (c.get (std::string ("str1")), "", "Config::set/get std::string"); + t.is (c.get ("str1"), "", "Config::set/get std::string"); - // const std::string get (const std::string&, const std::string&); - c.set ("str1", "one"); - t.is (c.get (std::string ("str1"), std::string ("no")), "one", "Config::set/get std::string"); + // void set (const std::string&, const int); + // const int getInteger (const std::string&); + c.set ("int1", 1); + t.is (c.getInteger ("int1"), 1, "Config::set/get int"); - c.set ("str1", ""); - t.is (c.get (std::string ("str1"), std::string ("no")), "", "Config::set/get std::string"); + c.set ("int2", 3); + t.is (c.getInteger ("int2"), 3, "Config::set/get int"); - // bool get (const std::string&, const bool); + c.set ("int3", -9); + t.is (c.getInteger ("int3"), -9, "Config::set/get int"); + + // void set (const std::string&, const double); + // const double getReal (const std::string&); + c.set ("double1", 1.0); + t.is (c.getReal ("double1"), 1.0, "Config::set/get double"); + + c.set ("double2", 3.0); + t.is (c.getReal ("double2"), 3.0, "Config::set/get double"); + + c.set ("double3", -9.0); + t.is (c.getReal ("double3"), -9.0, "Config::set/get double"); + + // void set (const std::string&, const bool); + // const bool getBoolean (const std::string&); c.set ("bool1", false); - t.is (c.get (std::string ("bool1"), (bool)true), false, "Config::set/get bool"); + t.is (c.getBoolean ("bool1"), false, "Config::set/get bool"); c.set ("bool1", true); - t.is (c.get (std::string ("bool1"), (bool)false), true, "Config::set/get bool"); + t.is (c.getBoolean ("bool1"), true, "Config::set/get bool"); // void all (std::vector &); std::vector all; diff --git a/src/tests/date.t.cpp b/src/tests/date.t.cpp index 5714c19cc..391dcb8c1 100644 --- a/src/tests/date.t.cpp +++ b/src/tests/date.t.cpp @@ -34,7 +34,7 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (102); + UnitTest t (111); try { @@ -180,6 +180,21 @@ int main (int argc, char** argv) t.is (fromString7.day (), 1, "ctor (std::string) -> d"); t.is (fromString7.year (), 2008, "ctor (std::string) -> y"); + Date fromString8 ("Tue 01 Jan 2008 (01)", "a D b Y (V)"); + t.is (fromString8.month (), 1, "ctor (std::string) -> m"); + t.is (fromString8.day (), 1, "ctor (std::string) -> d"); + t.is (fromString8.year (), 2008, "ctor (std::string) -> y"); + + Date fromString9 ("Tuesday, January 1, 2008", "A, B d, Y"); + t.is (fromString9.month (), 1, "ctor (std::string) -> m"); + t.is (fromString9.day (), 1, "ctor (std::string) -> d"); + t.is (fromString9.year (), 2008, "ctor (std::string) -> y"); + + Date fromString10 ("v01 Tue 2008-01-01", "vV a Y-M-D"); + t.is (fromString10.month (), 1, "ctor (std::string) -> m"); + t.is (fromString10.day (), 1, "ctor (std::string) -> d"); + t.is (fromString10.year (), 2008, "ctor (std::string) -> y"); + // Relative dates. Date r1 ("today"); t.ok (r1.sameDay (now), "today = now"); diff --git a/src/tests/dateformat.t b/src/tests/dateformat.t index 62df90a4b..e6d8d9066 100755 --- a/src/tests/dateformat.t +++ b/src/tests/dateformat.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 9; +use Test::More tests => 14; # Create the rc file. if (open my $fh, '>', 'date1.rc') @@ -47,6 +47,16 @@ if (open my $fh, '>', 'date2.rc') ok (-r 'date2.rc', 'Created date2.rc'); } +if (open my $fh, '>', 'date3.rc') +{ + print $fh "data.location=.\n", + "dateformat=m/d/y\n", + "weekstart=Monday\n", + "reportdateformat=A D B Y (vV)\n"; + close $fh; + ok (-r 'date3.rc', 'Created date3.rc'); +} + qx{../task rc:date1.rc add foo due:20091231}; my $output = qx{../task rc:date1.rc info 1}; like ($output, qr/\b20091231\b/, 'date format YMD parsed'); @@ -58,6 +68,15 @@ qx{../task rc:date2.rc add foo due:12/1/09}; $output = qx{../task rc:date2.rc info 1}; like ($output, qr/\b12\/1\/09\b/, 'date format m/d/y parsed'); +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +qx{../task rc:date3.rc add foo due:4/8/10}; +$output = qx{../task rc:date3.rc list}; +like ($output, qr/Thursday 08 April 2010 \(v14\)/, 'date format A D B Y (vV) parsed'); +$output = qx{../task rc:date3.rc rc.reportdateformat:"D b Y - a" list}; +like ($output, qr/08 Apr 2010 - Thu/, 'date format D b Y - a parsed'); + # Cleanup. unlink 'pending.data'; ok (!-r 'pending.data', 'Removed pending.data'); @@ -71,5 +90,8 @@ ok (!-r 'date1.rc', 'Removed date1.rc'); unlink 'date2.rc'; ok (!-r 'date2.rc', 'Removed date2.rc'); +unlink 'date3.rc'; +ok (!-r 'date3.rc', 'Removed date3.rc'); + exit 0; diff --git a/src/tests/delete.t b/src/tests/delete.t index cf3f56042..97d992b52 100755 --- a/src/tests/delete.t +++ b/src/tests/delete.t @@ -34,6 +34,7 @@ use Test::More tests => 17; if (open my $fh, '>', 'delete.rc') { print $fh "data.location=.\n", + "confirmation=no\n", "echo.command=no\n"; close $fh; ok (-r 'delete.rc', 'Created delete.rc'); diff --git a/src/tests/directory.t.cpp b/src/tests/directory.t.cpp index 26418b969..584a45935 100644 --- a/src/tests/directory.t.cpp +++ b/src/tests/directory.t.cpp @@ -25,6 +25,7 @@ // //////////////////////////////////////////////////////////////////////////////// +#include #include #include #include @@ -33,7 +34,7 @@ Context context; int main (int argc, char** argv) { - UnitTest t (20); + UnitTest t (21); // Directory (const File&); // Directory (const Path&); @@ -55,6 +56,9 @@ int main (int argc, char** argv) Directory d5 = d4; t.is (d5.data, "/tmp/test_directory", "Directory::operator="); + // operator (std::string) const; + t.is ((std::string) d3, "/tmp", "Directory::operator (std::string) const"); + // virtual bool create (); t.ok (d5.create (), "Directory::create /tmp/test_directory"); t.ok (d5.exists (), "Directory::exists /tmp/test_directory"); @@ -67,12 +71,14 @@ int main (int argc, char** argv) // std::vector list (); std::vector files = d5.list (); + std::sort (files.begin (), files.end ()); t.is ((int)files.size (), 2, "Directory::list 1 file"); t.is (files[0], "/tmp/test_directory/dir", "file[0] is /tmp/test_directory/dir"); t.is (files[1], "/tmp/test_directory/f0", "file[1] is /tmp/test_directory/f0"); // std::vector listRecursive (); files = d5.listRecursive (); + std::sort (files.begin (), files.end ()); t.is ((int)files.size (), 2, "Directory::list 1 file"); t.is (files[0], "/tmp/test_directory/dir/f1", "file is /tmp/test_directory/dir/f1"); t.is (files[1], "/tmp/test_directory/f0", "file is /tmp/test_directory/f0"); diff --git a/src/tests/file.t.cpp b/src/tests/file.t.cpp index 905aa265e..af0b3b9a5 100644 --- a/src/tests/file.t.cpp +++ b/src/tests/file.t.cpp @@ -33,7 +33,7 @@ Context context; int main (int argc, char** argv) { - UnitTest t (5); + UnitTest t (6); File::write ("/tmp/file.t.txt", "This is a test\n"); File f6 ("/tmp/file.t.txt"); @@ -41,6 +41,9 @@ int main (int argc, char** argv) t.ok (f6.mode () & S_IRUSR, "File::mode /tmp/file.t.txt good"); t.ok (File::remove ("/tmp/file.t.txt"), "File::remove /tmp/file.t.txt good"); + // operator (std::string) const; + t.is ((std::string) f6, "/tmp/file.t.txt", "File::operator (std::string) const"); + t.ok (File::create ("/tmp/file.t.create"), "File::create /tmp/file.t.create good"); t.ok (File::remove ("/tmp/file.t.create"), "File::remove /tmp/file.t.create good"); diff --git a/src/tests/path.t.cpp b/src/tests/path.t.cpp index 4fc679a1e..462598fac 100644 --- a/src/tests/path.t.cpp +++ b/src/tests/path.t.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2009, Paul Beckingham. +// Copyright 2006 - 2010, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -33,7 +33,7 @@ Context context; int main (int argc, char** argv) { - UnitTest t (26); + UnitTest t (32); // Path (); Path p0; @@ -54,6 +54,9 @@ int main (int argc, char** argv) Path p3_copy (p3); t.is (p3.data, p3_copy.data, "Path::Path (Path&)"); + // operator (std::string) const; + t.is ((std::string) p3, "/tmp", "Path::operator (std::string) const"); + // std::string name () const; Path p4 ("/a/b/c/file.ext"); t.is (p4.name (), "file.ext", "/a/b/c/file.ext name is file.ext"); @@ -101,6 +104,13 @@ int main (int argc, char** argv) t.ok (out.size () == 1, "/[s-u]mp -> 1 result"); t.is (out[0], "/tmp", "/[s-u]mp -> /tmp"); + // bool is_absolute () const; + t.notok (p0.is_absolute (), "'' !is_absolute"); + t.notok (p1.is_absolute (), "foo !is_absolute"); + t.ok (p2.is_absolute (), "~ is_absolute (after expansion)"); + t.ok (p3.is_absolute (), "/tmp is_absolute"); + t.ok (p4.is_absolute (), "/a/b/c/file.ext is_absolute"); + return 0; } diff --git a/src/tests/rc.t b/src/tests/rc.t index 9b05079e4..38faee75b 100755 --- a/src/tests/rc.t +++ b/src/tests/rc.t @@ -29,7 +29,7 @@ use strict; use warnings; use File::Path; -use Test::More tests => 8; +use Test::More tests => 12; # Create the rc file, using rc.name:value. unlink 'foo.rc'; @@ -51,6 +51,23 @@ qx{echo 'y'|../task rc:foo.rc rc.data.location:foo}; ok (-r 'foo.rc', 'Created default rc file'); ok (-d 'foo', 'Created default data directory'); +# Add a setting. +qx{echo 'y'|../task rc:foo.rc config must_be_unique old}; +my $output = qx{../task rc:foo.rc config}; +like ($output, qr/^must_be_unique\s+old$/ms, 'config setting a new value'); + +qx{echo 'y'|../task rc:foo.rc config must_be_unique new}; +$output = qx{../task rc:foo.rc config}; +like ($output, qr/^must_be_unique\s+new$/ms, 'config overwriting an existing value'); + +qx{echo 'y'|../task rc:foo.rc config must_be_unique ''}; +$output = qx{../task rc:foo.rc config}; +like ($output, qr/^must_be_unique$/ms, 'config setting a blank value'); + +qx{echo 'y'|../task rc:foo.rc config must_be_unique}; +$output = qx{../task rc:foo.rc config}; +unlike ($output, qr/^must_be_unique/ms, 'config removing a value'); + rmtree 'foo', 0, 0; ok (!-r 'foo', 'Removed foo'); diff --git a/src/tests/sequence.t b/src/tests/sequence.t index d7d8ca417..58b3540bb 100755 --- a/src/tests/sequence.t +++ b/src/tests/sequence.t @@ -33,7 +33,8 @@ use Test::More tests => 28; # Create the rc file. if (open my $fh, '>', 'seq.rc') { - print $fh "data.location=.\n"; + print $fh "data.location=.\n", + "confirmation=off\n"; close $fh; ok (-r 'seq.rc', 'Created seq.rc'); } diff --git a/src/tests/shadow.t b/src/tests/shadow.t index 09797075d..fcdacea8d 100755 --- a/src/tests/shadow.t +++ b/src/tests/shadow.t @@ -34,6 +34,7 @@ use Test::More tests => 22; if (open my $fh, '>', 'shadow.rc') { print $fh "data.location=.\n", + "confirmation=off\n", "shadow.file=./shadow.txt\n", "shadow.command=rc:shadow.rc stats\n", "shadow.notify=on\n"; diff --git a/src/tests/stringtable.t.cpp b/src/tests/stringtable.t.cpp index 625d283b7..b97c72811 100644 --- a/src/tests/stringtable.t.cpp +++ b/src/tests/stringtable.t.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -39,7 +40,7 @@ int main (int argc, char** argv) // Create a string file. std::string file = "./strings.xx-XX"; - spit (file, "# comment\n1 found"); + File::write (file, "# comment\n1 found"); t.is (access (file.c_str (), F_OK), 0, "strings.xx-XX created."); // Load the string file. diff --git a/src/tests/util.t.cpp b/src/tests/util.t.cpp index 5003e25e2..946eddbdc 100644 --- a/src/tests/util.t.cpp +++ b/src/tests/util.t.cpp @@ -34,7 +34,7 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (439); + UnitTest t (430); // TODO bool confirm (const std::string&); // TODO int confirm3 (const std::string&); @@ -514,23 +514,6 @@ int main (int argc, char** argv) // TODO const std::string uuid (); - // std::string expandPath (const std::string&); - t.ok (expandPath ("foo") == "foo", "expandPath nop"); - t.ok (expandPath ("~/") != "~/", "expandPath ~/"); - t.ok (expandPath ("~") != "~", "expandPath ~"); - - // bool isAbsolutePath (const std::string&); - t.notok (isAbsolutePath ("."), "isAbsolutePath ."); - t.notok (isAbsolutePath ("~"), "isAbsolutePath ~"); - t.ok (isAbsolutePath (expandPath ("~")), "isAbsolutePath (expandPath ~)"); - t.ok (isAbsolutePath (expandPath ("~/")), "isAbsolutePath (expandPath ~/)"); - t.ok (isAbsolutePath ("/"), "isAbsolutePath /"); - t.ok (isAbsolutePath ("/tmp"), "isAbsolutePath /tmp"); - - // TODO bool slurp (const std::string&, std::vector &, bool trimLines = false); - // TODO bool slurp (const std::string&, std::string&, bool trimLines = false); - // TODO void spit (const std::string&, const std::string&); - // std::string taskDiff (const Task&, const Task&); Task left; left.set ("zero", "0"); diff --git a/src/text.cpp b/src/text.cpp index 5388888a5..1da58de17 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -352,7 +352,7 @@ std::string ucFirst (const std::string& input) //////////////////////////////////////////////////////////////////////////////// const char* optionalBlankLine () { - if (context.config.get ("blanklines", true) == true) // no i18n + if (context.config.getBoolean ("blanklines") == true) // no i18n return newline; return noline; diff --git a/src/util.cpp b/src/util.cpp index f0e3ee543..a10e9289c 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -341,49 +341,6 @@ const std::string uuid () } #endif -//////////////////////////////////////////////////////////////////////////////// -// no i18n -std::string expandPath (const std::string& in) -{ - std::string copy = in; - std::string::size_type tilde; - - if ((tilde = copy.find ("~/")) != std::string::npos) - { - struct passwd* pw = getpwuid (getuid ()); - copy.replace (tilde, 1, pw->pw_dir); - } - else if ((tilde = copy.find ("~")) != std::string::npos) - { - struct passwd* pw = getpwuid (getuid ()); - std::string home = pw->pw_dir; - home += "/"; - copy.replace (tilde, 1, home); - } - else if ((tilde = copy.find ("~")) != std::string::npos) - { - std::string::size_type slash; - if ((slash = copy.find ("/", tilde)) != std::string::npos) - { - std::string name = copy.substr (tilde + 1, slash - tilde - 1); - struct passwd* pw = getpwnam (name.c_str ()); - if (pw) - copy.replace (tilde, slash - tilde, pw->pw_dir); - } - } - - return copy; -} - -//////////////////////////////////////////////////////////////////////////////// -bool isAbsolutePath (const std::string& in) -{ - if (in.length () && in[0] == '/') - return true; - - return false; -} - //////////////////////////////////////////////////////////////////////////////// // On Solaris no flock function exists. #ifdef SOLARIS @@ -418,92 +375,6 @@ int flock (int fd, int operation) } #endif -//////////////////////////////////////////////////////////////////////////////// -bool slurp ( - const std::string& file, - std::vector & contents, - bool trimLines /* = false */) -{ - contents.clear (); - - std::ifstream in (file.c_str ()); - if (in.good ()) - { - std::string line; - while (getline (in, line)) - { - if (trimLines) line = trim (line); - contents.push_back (line); - } - - in.close (); - return true; - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -bool slurp ( - const std::string& file, - std::string& contents, - bool trimLines /* = false */) -{ - contents = ""; - - std::ifstream in (file.c_str ()); - if (in.good ()) - { - std::string line; - while (getline (in, line)) - { - if (trimLines) line = trim (line); - contents += line + "\n"; - } - - in.close (); - return true; - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -void spit (const std::string& file, const std::string& contents) -{ - std::ofstream out (file.c_str ()); - if (out.good ()) - { - out << contents; - out.close (); - } - else - throw std::string ("Could not write file '") + file + "'"; // TODO i18n -} - -//////////////////////////////////////////////////////////////////////////////// -void spit ( - const std::string& file, - const std::vector & lines, - bool addNewlines /* = true */) -{ - std::ofstream out (file.c_str ()); - if (out.good ()) - { - foreach (line, lines) - { - out << *line; - - if (addNewlines) - out << "\n"; - } - - out.close (); - } - else - throw std::string ("Could not write file '") + file + "'"; // TODO i18n -} - //////////////////////////////////////////////////////////////////////////////// bool taskDiff (const Task& before, const Task& after) { @@ -589,7 +460,7 @@ std::string renderAttribute (const std::string& name, const std::string& value) if (a.type (name) == "date") { Date d ((time_t)::atoi (value.c_str ())); - return d.toString (context.config.get ("dateformat", "m/d/Y")); + return d.toString (context.config.get ("dateformat")); } return value; diff --git a/src/util.h b/src/util.h index b6ee6576f..50fef817e 100644 --- a/src/util.h +++ b/src/util.h @@ -60,8 +60,6 @@ std::string formatSecondsCompact (time_t); std::string formatBytes (size_t); int autoComplete (const std::string&, const std::vector&, std::vector&); const std::string uuid (); -std::string expandPath (const std::string&); -bool isAbsolutePath (const std::string&); #ifdef SOLARIS #define LOCK_SH 1 @@ -72,10 +70,6 @@ bool isAbsolutePath (const std::string&); int flock (int, int); #endif -bool slurp (const std::string&, std::vector &, bool trimLines = false); -bool slurp (const std::string&, std::string&, bool trimLines = false); -void spit (const std::string&, const std::string&); -void spit (const std::string&, const std::vector &, bool addNewlines = true); bool taskDiff (const Task&, const Task&); std::string taskDifferences (const Task&, const Task&); std::string renderAttribute (const std::string&, const std::string&);