Merge branch '1.9.0' into hooks

This commit is contained in:
Paul Beckingham 2010-01-17 00:35:08 -05:00
commit 31055360dc
56 changed files with 1271 additions and 1316 deletions

View file

@ -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

View file

@ -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 <name> <value>', or
'task config <name>' 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

View file

@ -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

1
NEWS
View file

@ -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

View file

@ -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 <support@taskwarrior.org>.
.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 <paul@beckingham.net>.

View file

@ -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.

View file

@ -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.

View file

@ -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

34
doc/misc/script-color.txt Normal file
View file

@ -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

1
doc/rc/dark-16.theme Normal file
View file

@ -0,0 +1 @@
# Sample task 1.9 (or later) color theme

1
doc/rc/dark-256.theme Normal file
View file

@ -0,0 +1 @@
# Sample task 1.9 (or later) color theme

1
doc/rc/light-16.theme Normal file
View file

@ -0,0 +1 @@
# Sample task 1.9 (or later) color theme

1
doc/rc/light-256.theme Normal file
View file

@ -0,0 +1 @@
# Sample task 1.9 (or later) color theme

View file

@ -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

View file

@ -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.
---

View file

@ -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;

View file

@ -27,30 +27,231 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#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 <std::string> lines;
split (lines, input, "\n");
// Parse each line.
std::vector <std::string>::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 <std::string, std::string>::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<std::string>& items)
items.push_back (i->first);
}
////////////////////////////////////////////////////////////////////////////////
void Config::getSequence (std::vector<std::string>& items)
{
items = sequence;
}
////////////////////////////////////////////////////////////////////////////////
std::string Config::checkForDuplicates ()
{
std::vector <std::string> duplicates;
std::map <std::string, int> 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 ()
{

View file

@ -30,6 +30,7 @@
#include <map>
#include <vector>
#include <string>
#include "File.h"
class Config : public std::map <std::string, std::string>
{
@ -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 <std::string>&);
void getSequence (std::vector<std::string>&);
std::string checkForDuplicates ();
std::string checkForDeprecatedColor ();
public:
File original_file;
private:
std::vector <std::string> sequence;
static std::string defaults;
};
#endif

View file

@ -32,6 +32,8 @@
#include <string.h>
#include <unistd.h>
#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.<variant>), 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 <std::string> 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 <std::string> filtered;

View file

@ -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;
}

View file

@ -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;

View file

@ -29,6 +29,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include "Directory.h"
////////////////////////////////////////////////////////////////////////////////

View file

@ -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 <std::string> 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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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 <std::string> 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 <std::string> 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 <std::string> 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),

View file

@ -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 <int>& 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") ||

View file

@ -41,11 +41,13 @@ public:
ascendingCharacter,
ascendingPriority,
ascendingDate,
ascendingDueDate,
ascendingPeriod,
descendingNumeric,
descendingCharacter,
descendingPriority,
descendingDate,
descendingDueDate,
descendingPeriod};
enum sizing {minimum = -1, flexible = 0};

View file

@ -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 <time.h>
#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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <std::string> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 '"

View file

@ -82,7 +82,7 @@ int handleCustomReport (const std::string& report, std::string &outs)
// Get all the tasks.
std::vector <Task> 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).

View file

@ -33,6 +33,8 @@
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#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 <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
Filter filter;
context.tdb.loadPending (tasks, filter);

View file

@ -28,6 +28,7 @@
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#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 <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.lock (context.config.getBoolean ("locking"));
std::vector <std::string>::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 <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.lock (context.config.getBoolean ("locking"));
std::vector <std::string>::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 <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.lock (context.config.getBoolean ("locking"));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
@ -715,7 +716,7 @@ static std::string importTodoSh_2_0 (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.lock (context.config.getBoolean ("locking"));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
@ -840,7 +841,7 @@ static std::string importText (const std::vector <std::string>& lines)
std::vector <std::string> failed;
int count = 0;
context.tdb.lock (context.config.get ("locking", true));
context.tdb.lock (context.config.getBoolean ("locking"));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
@ -904,7 +905,7 @@ static std::string importCSV (const std::vector <std::string>& lines)
{
std::vector <std::string> 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 <std::string, int> mapping;
@ -1155,7 +1156,7 @@ int handleImport (std::string &outs)
{
// Load the file.
std::vector <std::string> all;
slurp (file, all, true);
File::read (file, all);
std::vector <std::string> lines;
std::vector <std::string>::iterator it;

View file

@ -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 ();

View file

@ -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 &);

View file

@ -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.

View file

@ -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 <sstream>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
@ -37,6 +36,8 @@
#include <time.h>
#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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <Task> 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 <std::string> undo;
slurp (file, undo, false);
std::vector <std::string> 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 <Task> 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 <Task>& 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 <Att> 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;

View file

@ -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');
}

View file

@ -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';

View file

@ -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 ("");

View file

@ -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');
}

View file

@ -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;

View file

@ -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::string>&);
std::vector <std::string> all;

View file

@ -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");

View file

@ -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;

View file

@ -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');

View file

@ -25,6 +25,7 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <Context.h>
#include <Directory.h>
#include <test.h>
@ -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 <std::string> list ();
std::vector <std::string> 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 <std::string> 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");

View file

@ -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");

View file

@ -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;
}

View file

@ -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');

View file

@ -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');
}

View file

@ -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";

View file

@ -27,6 +27,7 @@
#include <unistd.h>
#include <Context.h>
#include <StringTable.h>
#include <File.h>
#include <util.h>
#include <test.h>
@ -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.

View file

@ -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 <std::string>&, 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");

View file

@ -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;

View file

@ -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 <std::string>& 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 <std::string>& 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;

View file

@ -60,8 +60,6 @@ std::string formatSecondsCompact (time_t);
std::string formatBytes (size_t);
int autoComplete (const std::string&, const std::vector<std::string>&, std::vector<std::string>&);
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 <std::string>&, 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 <std::string>&, 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&);