Merge branch '1.7.0' into timesheet

This commit is contained in:
Paul Beckingham 2009-05-07 00:44:12 -04:00
commit c61a295df7
37 changed files with 1037 additions and 663 deletions

View file

@ -4,6 +4,14 @@
1.7.0 (?) ? 1.7.0 (?) ?
+ Improved the errors when parsing a corrupt or unrecognized pending.data + Improved the errors when parsing a corrupt or unrecognized pending.data
or completed.data file (thanks to T. Charles Yun). or completed.data file (thanks to T. Charles Yun).
+ Added details to the "info" report about recurring tasks (thanks to T.
Charles Yun).
+ Now writes a sample "defaultwidth" configuration variable to the default
.taskrc file (thanks to T. Charles Yun).
+ Task allows commands that require an ID to now be given a sequence, which
is a set of IDs. This allows commands like "task delete 1 2 5-10,12".
+ Fixed bug in the ghistory report, which caused it to only show a new
month if a task was added during that month.
------ old releases ------------------------------ ------ old releases ------------------------------

6
README
View file

@ -44,7 +44,11 @@ All feedback is welcome, in addition to any bug reports or patches to:
task@beckingham.net task@beckingham.net
Got an idea for an enhancement? Send a message! Or better yet, get involved in the discussion at
http://groups.google.com/group/taskprogram
Got an idea for an enhancement? Post a message!
I have found that task makes me more productive and organized. I have found that task makes me more productive and organized.
I hope task can do the same for you. I hope task can do the same for you.

26
checklist.txt Normal file
View file

@ -0,0 +1,26 @@
Release Checklist
-----------------
- Update "Upcoming Features" document on group
- Ensure all unit tests pass on OS X
- Ensure clean build on OS X
- Make a source package (1)
- Ensure clean build on latest Fedora Core from source package
- Git clone and rebuild, ensure all unit tests pass
- Ensure clean build on latest Ubuntu from source package
- Git clone and rebuild, ensure all unit tests pass
- Ensure clean build on Windows/Cygwin from source package
- Git clone and rebuild, ensure all unit tests pass
- Make a new source package (2)
- Add actual release date to ChangeLog
- Add actual release date to html/task.html
- Merge version branch to master
- Tag master
- Make a new source package (3)
- Send source package to package maintainer
- Make OS X .pkg package
- Wait for all packages
- Upload all packages to website
- Upload all docs to website
- Send announcement to group

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
@ -52,7 +51,7 @@
generated on a regular basis. Consider the example: generated on a regular basis. Consider the example:
</p> </p>
<pre><code>% task Pay rent due:7/1/2008 recur:monthly</code></pre> <pre><code>% task add Pay rent due:7/1/2008 recur:monthly</code></pre>
<p> <p>
If today's date is 7/10, for example, then that due date is in the past, and If today's date is 7/10, for example, then that due date is in the past, and
@ -81,7 +80,7 @@ ID Project Pri Due Active Age Description
Thursdays instead: Thursdays instead:
</p> </p>
<pre><code>% task TPS report due:thursday recur:weekly until:8/31/2008</code></pre> <pre><code>% task add TPS report due:thursday recur:weekly until:8/31/2008</code></pre>
<p> <p>
This create a weekly recurring task that expires on 8/31/2008. What this means This create a weekly recurring task that expires on 8/31/2008. What this means

154
html/sequence.html Normal file
View file

@ -0,0 +1,154 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Task Usage</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="task.css" type="text/css" />
</head>
<body>
<div id="container">
<table>
<tr>
<td>
<div id="toolbar">
<a href="task.html">Home</a>
<a href="setup.html">Setup</a>
<a href="30second.html">30-second Tutorial</a>
<a href="simple.html">Simple</a>
<a href="advanced.html">Advanced</a>
<a href="shell.html">Shell</a>
<a href="config.html">Configuration</a>
<a href="color.html">Colors</a>
<a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a>
<a href="versions.html">Old Versions</a>
<a href="links.html">Task on the Web</a>
</div>
<div id="content">
<br />
<br />
<br />
<h1 class="title">ID Sequences</h1>
<div class="content">
<p>
Some task commands require an ID to be specified. For example:
</p>
<pre><code>% task 3 done</code></pre>
<p>
This marks a single task as done. But if you wanted to mark
several tasks as done, you could use:
</p>
<pre><code>% task 3,4,5 done</code></pre>
<p>
Which would mark tasks 3, 4 and 5 as all done. In this example,
the three IDs are consecutive, which means you could also have
entered:
</p>
<pre><code>% task 3-5 done</code></pre>
<p>
Or in a more complex example:
</p>
<pre><code>% task 1,3-5,12 23-25 done</code></pre>
<p>
This would mark tasks 1, 3, 4, 5, 12, 23, 24 and 25 as done.
Note that this example uses two sequences, separated by a space.
</p>
<p>
You must be careful though. Task tries very carefully to do
the right thing when it interprets the command line, but must
still impose some rules so that it can unambiguously read the
command. If you use one or more sequences, then they must
appear on the command line adjacent to each other. If they
are separated by something else, then task assumes the second
and subsequent set is not a sequence. Here is an example
of this:
</p>
<pre><code>% task 3 Order part number 4-123</code></pre>
<p>
Clearly the 4-123 is a part number, and not a sequence.
Task is being asked to modify the description of task 3 to be
"Order part number 4-123". Note that the ID is separated
from the part number by something other than a sequence.
Here is a bad example that task will misinterpret:
</p>
<pre><code>% task 3 4-123 is back-ordered, try again next week</code></pre>
<p>
The intent here is that task 3 have its description modified to be
"40123 is back-ordered, try again next week", but will be
misinterpreted as tasks 3, 4, 5, 6 ... 123 will all be modified
to have the description "is back-ordered, try again next week".
The solution is to quote the whole description:
</p>
<pre><code>% task 3 "4-123 is back-ordered, try again next week"</code></pre>
</div>
<br />
<br />
<div class="content">
<p>
Copyright 2006-2009, P. Beckingham. All rights reserved.
</p>
</div>
</div>
</td>
<td align="right" valign="top" width="200px">
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<script type="text/javascript"><!--
google_ad_client = "pub-9709799404235424";
/* Task Main */
google_ad_slot = "8660617875";
google_ad_width = 120;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</td>
</tr>
</table>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-4737637-1");
pageTracker._initData();
pageTracker._trackPageview();
</script>
</body>
</html>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
@ -50,7 +49,6 @@
<li><a href="shell.html">Interacting with the Shell</a> <li><a href="shell.html">Interacting with the Shell</a>
<li><a href="config.html">Configuring Task</a> <li><a href="config.html">Configuring Task</a>
<li><a href="color.html">Color</a> <li><a href="color.html">Color</a>
<li><a href="usage.html">Task Command Usage</a>
<li><a href="recur.html">Recurring Tasks</a> <li><a href="recur.html">Recurring Tasks</a>
<li><a href="date.html">Date Handling</a> <li><a href="date.html">Date Handling</a>
<li><a href="versions.html">Old Versions</a> <li><a href="versions.html">Old Versions</a>
@ -59,6 +57,7 @@
<li><a href="custom.html">Custom Reports</a> <li><a href="custom.html">Custom Reports</a>
<li><a href="import.html">Data Import</a> <li><a href="import.html">Data Import</a>
<li><a href="faq.html">Frequently Asked Questions</a> <li><a href="faq.html">Frequently Asked Questions</a>
<li><a href="sequence.html">ID Sequences</a>
</ul> </ul>
<p> <p>
@ -71,6 +70,12 @@
which illustrates many of task's features. which illustrates many of task's features.
</p> </p>
<p>
For the latest news, discussion of proposed task features, and
somewhere to voice your opinions, join us at
<a href="http://groups.google.com/group/taskprogram">http://groups.google.com/group/taskprogram</a>.
</p>
<br /> <br />
<h2 class="title">Get the Latest Stable Release</h2> <h2 class="title">Get the Latest Stable Release</h2>
@ -110,6 +115,14 @@
<ul> <ul>
<li>Improved the errors when parsing a corrupt or unrecognized pending.data <li>Improved the errors when parsing a corrupt or unrecognized pending.data
or completed.data file (thanks to T. Charles Yun). or completed.data file (thanks to T. Charles Yun).
<li>Added details to the "info" report about recurring tasks (thanks to T.
Charles Yun).
<li>Now writes a sample "defaultwidth" configuration variable to the default
.taskrc file (thanks to T. Charles Yun).
<li>Task allows commands that require an ID to now be given a sequence, which
is a set of IDs. This allows commands like "task delete 1 2 5-10,12".
<li>Fixed bug in the ghistory report, which caused it to only show a new
month if a task was added during that month.
</ul> </ul>
<p> <p>

View file

@ -1,153 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Task Usage</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="task.css" type="text/css" />
</head>
<body>
<div id="container">
<table>
<tr>
<td>
<div id="toolbar">
<a href="task.html">Home</a>
<a href="setup.html">Setup</a>
<a href="30second.html">30-second Tutorial</a>
<a href="simple.html">Simple</a>
<a href="advanced.html">Advanced</a>
<a href="shell.html">Shell</a>
<a href="config.html">Configuration</a>
<a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a>
<a href="versions.html">Old Versions</a>
<a href="links.html">Task on the Web</a>
</div>
<div id="content">
<br />
<br />
<br />
<h2 class="title"><a name="usage">Command Usage<a></h2>
<div class="content">
<pre><code>Usage: task
task add [tags] [attrs] desc...
task append [tags] [attrs] desc...
task annotate ID desc...
task completed [tags] [attrs] desc...
task ID [tags] [attrs] [desc...]
task ID /from/to/
task delete ID
task undelete ID
task info ID
task start ID
task stop ID
task done ID
task undo ID
task projects
task tags
task summary
task history
task ghistory
task next
task calendar
task active
task overdue
task stats
task export
task color
task version
task help
task list [tags] [attrs] desc...
task long [tags] [attrs] desc...
task ls [tags] [attrs] desc...
task newest [tags] [attrs] desc...
task oldest [tags] [attrs] desc...
See http://www.beckingham.net/task.html for the latest releases and a full tutorial.
ID is the numeric identifier displayed by the 'task list' command
Tags are arbitrary words, any quantity:
+tag The + means add the tag
-tag The - means remove the tag
Attributes are:
project: Project name
priority: Priority
due: Due date
recur: Recurrence frequency
until: Recurrence end date
fg: Foreground color
bg: Background color
rc: Alternate .taskrc file
Any command or attribute name may be abbreviated if still unique:
task list project:Home
task li pro:Home
Some task descriptions need to be escaped because of the shell:
task add "quoted ' quote"
task add escaped \' quote
Many characters have special meaning to the shell, including:
$ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~</code></pre>
<div>
<br />
<br />
<div class="content">
<p>
Copyright 2006-2009, P. Beckingham. All rights reserved.
</p>
</div>
</div>
</td>
<td align="right" valign="top" width="200px">
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<script type="text/javascript"><!--
google_ad_client = "pub-9709799404235424";
/* Task Main */
google_ad_slot = "8660617875";
google_ad_width = 120;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</td>
</tr>
</table>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-4737637-1");
pageTracker._initData();
pageTracker._trackPageview();
</script>
</body>
</html>

View file

@ -20,7 +20,6 @@
<a href="shell.html">Shell</a> <a href="shell.html">Shell</a>
<a href="config.html">Configuration</a> <a href="config.html">Configuration</a>
<a href="color.html">Colors</a> <a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a> <a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a> <a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>

View file

@ -149,6 +149,7 @@ void Config::createDefault (const std::string& home)
fprintf (out, "next=2\n"); fprintf (out, "next=2\n");
fprintf (out, "dateformat=m/d/Y\n"); fprintf (out, "dateformat=m/d/Y\n");
fprintf (out, "#monthsperline=2\n"); fprintf (out, "#monthsperline=2\n");
fprintf (out, "#defaultwidth=80\n");
fprintf (out, "curses=on\n"); fprintf (out, "curses=on\n");
fprintf (out, "color=on\n"); fprintf (out, "color=on\n");
fprintf (out, "due=7\n"); fprintf (out, "due=7\n");

View file

@ -37,6 +37,7 @@ T::T ()
mUUID = uuid (); mUUID = uuid ();
mStatus = pending; mStatus = pending;
mId = 0; mId = 0;
mSequence.clear ();
mTags.clear (); mTags.clear ();
mAttributes.clear (); mAttributes.clear ();
mDescription = ""; mDescription = "";
@ -59,6 +60,7 @@ T::T (const T& other)
mStatus = other.mStatus; mStatus = other.mStatus;
mUUID = other.mUUID; mUUID = other.mUUID;
mId = other.mId; mId = other.mId;
mSequence = other.mSequence;
mDescription = other.mDescription; mDescription = other.mDescription;
mTags = other.mTags; mTags = other.mTags;
mRemoveTags = other.mRemoveTags; mRemoveTags = other.mRemoveTags;
@ -74,6 +76,7 @@ T& T::operator= (const T& other)
mStatus = other.mStatus; mStatus = other.mStatus;
mUUID = other.mUUID; mUUID = other.mUUID;
mId = other.mId; mId = other.mId;
mSequence = other.mSequence;
mDescription = other.mDescription; mDescription = other.mDescription;
mTags = other.mTags; mTags = other.mTags;
mRemoveTags = other.mRemoveTags; mRemoveTags = other.mRemoveTags;
@ -286,6 +289,16 @@ void T::addAnnotation (const std::string& description)
mAnnotations[time (NULL)] = sanitized; mAnnotations[time (NULL)] = sanitized;
} }
////////////////////////////////////////////////////////////////////////////////
bool T::sequenceContains (int id) const
{
foreach (seq, mSequence)
if (*seq == id)
return true;
return false;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// uuid status [tags] [attributes] [annotations] description // uuid status [tags] [attributes] [annotations] description
// //
@ -575,8 +588,6 @@ void T::parse (const std::string& line)
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1); openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> pairs; std::vector <std::string> pairs;
split (pairs, attributes, ' '); split (pairs, attributes, ' ');
if (pairs.size () == 0)
throw std::string ("Could not find any attributes.");
for (size_t i = 0; i < pairs.size (); ++i) for (size_t i = 0; i < pairs.size (); ++i)
{ {

View file

@ -49,7 +49,9 @@ public:
void setUUID (const std::string& uuid) { mUUID = uuid; } void setUUID (const std::string& uuid) { mUUID = uuid; }
int getId () const { return mId; } int getId () const { return mId; }
void setId (int id) { mId = id; } void setId (int id) { mId = id; mSequence.push_back (id); }
std::vector <int> getAllIds () const { return mSequence; }
void addId (int id) { if (mId == 0) mId = id; mSequence.push_back (id); }
status getStatus () const { return mStatus; } status getStatus () const { return mStatus; }
void setStatus (status s) { mStatus = s; } void setStatus (status s) { mStatus = s; }
@ -82,6 +84,7 @@ public:
void getAnnotations (std::map <time_t, std::string>&) const; void getAnnotations (std::map <time_t, std::string>&) const;
void setAnnotations (const std::map <time_t, std::string>&); void setAnnotations (const std::map <time_t, std::string>&);
void addAnnotation (const std::string&); void addAnnotation (const std::string&);
bool sequenceContains (int) const;
const std::string compose () const; const std::string compose () const;
const std::string composeCSV (); const std::string composeCSV ();
@ -95,6 +98,7 @@ private:
status mStatus; status mStatus;
std::string mUUID; std::string mUUID;
int mId; int mId;
std::vector <int> mSequence;
std::string mDescription; std::string mDescription;
std::vector<std::string> mTags; std::vector<std::string> mTags;
std::vector<std::string> mRemoveTags; std::vector<std::string> mRemoveTags;

View file

@ -193,42 +193,34 @@ std::string handleTags (TDB& tdb, T& task, Config& conf)
std::string handleUndelete (TDB& tdb, T& task, Config& conf) std::string handleUndelete (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
filterSequence (all, task);
int id = task.getId (); foreach (t, all)
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == id) if (t->getStatus () == T::deleted)
{ {
if (it->getStatus () == T::deleted) if (t->getAttribute ("recur") != "")
{ out << "Task does not support 'undo' for recurring tasks.\n";
if (it->getAttribute ("recur") != "")
{
out << "Task does not support 'undelete' for recurring tasks." << std::endl;
return out.str ();
}
T restored (*it); t->setStatus (T::pending);
restored.setStatus (T::pending); t->removeAttribute ("end");
restored.removeAttribute ("end"); tdb.modifyT (*t);
tdb.modifyT (restored);
out << "Task " << id << " successfully undeleted." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undeleted.\n";
return out.str ();
} }
else else
{ {
out << "Task " << id << " is not deleted - therefore cannot undelete." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' is not deleted - therefore cannot be undeleted.\n";
return out.str ();
}
} }
} }
out << "Task " << id out << "\n"
<< " not found - tasks can only be reliably undeleted if the undelete" << std::endl << "Please note that tasks can only be reliably undeleted if the undelete "
<< "command is run immediately after the errant delete command." << std::endl; << "command is run immediately after the errant delete command."
<< std::endl;
return out.str (); return out.str ();
} }
@ -242,37 +234,31 @@ std::string handleUndo (TDB& tdb, T& task, Config& conf)
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
filterSequence (all, task);
int id = task.getId (); foreach (t, all)
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == id) if (t->getStatus () == T::completed)
{ {
if (it->getStatus () == T::completed) if (t->getAttribute ("recur") != "")
{ out << "Task does not support 'undo' for recurring tasks.\n";
if (it->getAttribute ("recur") != "")
return std::string ("Task does not support 'undo' for recurring tasks.\n");
T restored (*it); t->setStatus (T::pending);
restored.setStatus (T::pending); t->removeAttribute ("end");
restored.removeAttribute ("end"); tdb.modifyT (*t);
tdb.modifyT (restored);
out << "Task " << id << " successfully undone." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undone." << std::endl;
return out.str ();
} }
else else
{ {
out << "Task " << id << " is not done - therefore cannot be undone." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' is not done - therefore cannot be undone." << std::endl;
return out.str ();
}
} }
} }
out << "Task " << id out << std::endl
<< " not found - tasks can only be reliably undone if the undo" << std::endl << "Please note that tasks can only be reliably undone if the undo "
<< "command is run immediately after the errant done command." << std::endl; << "command is run immediately after the errant done command."
<< std::endl;
return out.str (); return out.str ();
} }
@ -367,6 +353,7 @@ std::string handleVersion (Config& conf)
// Complain about configuration variables that are not recognized. // Complain about configuration variables that are not recognized.
// These are the regular configuration variables. // These are the regular configuration variables.
// Note that there is a leading and trailing space.
std::string recognized = std::string recognized =
" blanklines color color.active color.due color.overdue color.pri.H " " blanklines color color.active color.due color.overdue color.pri.H "
"color.pri.L color.pri.M color.pri.none color.recurring color.tagged " "color.pri.L color.pri.M color.pri.none color.recurring color.tagged "
@ -387,7 +374,10 @@ std::string handleVersion (Config& conf)
std::vector <std::string> unrecognized; std::vector <std::string> unrecognized;
foreach (i, all) foreach (i, all)
{ {
if (recognized.find (*i) == std::string::npos) // Disallow partial matches by tacking a leading an trailing space on each
// variable name.
std::string pattern = " " + *i + " ";
if (recognized.find (pattern) == std::string::npos)
{ {
// These are special configuration variables, because their name is // These are special configuration variables, because their name is
// dynamic. // dynamic.
@ -439,13 +429,20 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
if (!conf.get (std::string ("confirmation"), false) || confirm ("Permanently delete task?"))
{
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
filterSequence (all, task);
foreach (t, all) foreach (t, all)
{ {
if (t->getId () == task.getId ()) std::stringstream question;
question << "Permanently delete task "
<< t->getId ()
<< " '"
<< t->getDescription ()
<< "'?";
if (!conf.get (std::string ("confirmation"), false) || confirm (question.str ()))
{ {
// Check for the more complex case of a recurring task. If this is a // Check for the more complex case of a recurring task. If this is a
// recurring task, get confirmation to delete them all. // recurring task, get confirmation to delete them all.
@ -465,8 +462,9 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Deleting recurring task " out << "Deleting recurring task "
<< sibling->getId () << sibling->getId ()
<< " " << " '"
<< sibling->getDescription () << sibling->getDescription ()
<< "'"
<< std::endl; << std::endl;
} }
} }
@ -479,8 +477,9 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
tdb.deleteT (*t); tdb.deleteT (*t);
out << "Deleting recurring task " out << "Deleting recurring task "
<< t->getId () << t->getId ()
<< " " << " '"
<< t->getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
} }
} }
@ -490,17 +489,15 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Deleting task " out << "Deleting task "
<< t->getId () << t->getId ()
<< " " << " '"
<< t->getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
} }
break; // No point continuing the loop.
}
}
} }
else else
out << "Task not deleted." << std::endl; out << "Task not deleted." << std::endl;
}
return out.str (); return out.str ();
} }
@ -508,112 +505,97 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleStart (TDB& tdb, T& task, Config& conf) std::string handleStart (TDB& tdb, T& task, Config& conf)
{ {
std::vector <T> all;
tdb.pendingT (all);
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{
if (it->getId () == task.getId ())
{
T original (*it);
std::stringstream out; std::stringstream out;
if (original.getAttribute ("start") == "") std::vector <T> all;
tdb.pendingT (all);
filterSequence (all, task);
foreach (t, all)
{
if (t->getAttribute ("start") == "")
{ {
char startTime[16]; char startTime[16];
sprintf (startTime, "%u", (unsigned int) time (NULL)); sprintf (startTime, "%u", (unsigned int) time (NULL));
original.setAttribute ("start", startTime); t->setAttribute ("start", startTime);
original.setId (task.getId ()); tdb.modifyT (*t);
tdb.modifyT (original);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Started " out << "Started "
<< original.getId () << t->getId ()
<< " " << " '"
<< original.getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
nag (tdb, task, conf); nag (tdb, task, conf);
} }
else else
{ {
out << "Task " << task.getId () << " already started." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' already started." << std::endl;
}
} }
return out.str (); return out.str ();
} }
}
throw std::string ("Task not found.");
return std::string (""); // To satisfy gcc.
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleStop (TDB& tdb, T& task, Config& conf) std::string handleStop (TDB& tdb, T& task, Config& conf)
{ {
std::vector <T> all;
tdb.pendingT (all);
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{
if (it->getId () == task.getId ())
{
T original (*it);
std::stringstream out; std::stringstream out;
if (original.getAttribute ("start") != "") std::vector <T> all;
tdb.pendingT (all);
filterSequence (all, task);
foreach (t, all)
{ {
original.removeAttribute ("start"); if (t->getAttribute ("start") != "")
original.setId (task.getId ()); {
tdb.modifyT (original); t->removeAttribute ("start");
tdb.modifyT (*t);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Stopped " << original.getId () << " " << original.getDescription () << std::endl; out << "Stopped " << t->getId () << " '" << t->getDescription () << "'" << std::endl;
} }
else else
{ {
out << "Task " << task.getId () << " not started." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' not started." << std::endl;
}
} }
return out.str (); return out.str ();
} }
}
throw std::string ("Task not found.");
return std::string (""); // To satisfy gcc.
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleDone (TDB& tdb, T& task, Config& conf) std::string handleDone (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
if (!tdb.completeT (task)) std::vector <T> all;
tdb.allPendingT (all);
std::vector <T> filtered = all;
filterSequence (filtered, task);
foreach (t, filtered)
{
t->setStatus (T::completed);
if (!tdb.completeT (*t))
throw std::string ("Could not mark task as completed."); throw std::string ("Could not mark task as completed.");
// Now update mask in parent. // Now update mask in parent.
std::vector <T> all;
tdb.allPendingT (all);
foreach (t, all)
{
if (t->getId () == task.getId ())
{
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Completed " out << "Completed "
<< t->getId () << t->getId ()
<< " " << " '"
<< t->getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
t->setStatus (T::completed);
updateRecurrenceMask (tdb, all, *t); updateRecurrenceMask (tdb, all, *t);
break; nag (tdb, task, conf);
}
} }
nag (tdb, task, conf);
return out.str (); return out.str ();
} }
@ -677,163 +659,50 @@ std::string handleExport (TDB& tdb, T& task, Config& conf)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleModify (TDB& tdb, T& task, Config& conf) std::string handleModify (TDB& tdb, T& task, Config& conf)
{ {
int count = 0;
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
// Lookup the complete task. std::vector <T> filtered = all;
T complete = findT (task.getId (), all); filterSequence (filtered, task);
foreach (seq, filtered)
{
// Perform some logical consistency checks. // Perform some logical consistency checks.
if (task.getAttribute ("recur") != "" && if (task.getAttribute ("recur") != "" &&
task.getAttribute ("due") == "" && task.getAttribute ("due") == "" &&
complete.getAttribute ("due") == "") seq->getAttribute ("due") == "")
throw std::string ("You cannot specify a recurring task without a due date."); throw std::string ("You cannot specify a recurring task without a due date.");
if (task.getAttribute ("until") != "" && if (task.getAttribute ("until") != "" &&
task.getAttribute ("recur") == "" && task.getAttribute ("recur") == "" &&
complete.getAttribute ("recur") == "") seq->getAttribute ("recur") == "")
throw std::string ("You cannot specify an until date for a non-recurring task."); throw std::string ("You cannot specify an until date for a non-recurring task.");
int count = 0; // Make all changes.
std::vector <T>::iterator it; foreach (other, all)
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == complete.getId () || // Self if (other->getId () == seq->getId () || // Self
(complete.getAttribute ("parent") != "" && (seq->getAttribute ("parent") != "" &&
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
it->getUUID () == complete.getAttribute ("parent")) // Parent other->getUUID () == seq->getAttribute ("parent")) // Parent
{ {
T original (*it);
// A non-zero value forces a file write. // A non-zero value forces a file write.
int changes = 0; int changes = 0;
// Apply a new description, if any. // Apply other deltas.
if (task.getDescription () != "") changes += deltaDescription (*other, task);
{ changes += deltaTags (*other, task);
original.setDescription (task.getDescription ()); changes += deltaAttributes (*other, task);
++changes; changes += deltaSubstitutions (*other, task);
}
// Apply or remove tags, if any.
std::vector <std::string> tags;
task.getTags (tags);
for (unsigned int i = 0; i < tags.size (); ++i)
{
if (tags[i][0] == '+')
original.addTag (tags[i].substr (1, std::string::npos));
else
original.addTag (tags[i]);
++changes;
}
task.getRemoveTags (tags);
for (unsigned int i = 0; i < tags.size (); ++i)
{
if (tags[i][0] == '-')
original.removeTag (tags[i].substr (1, std::string::npos));
else
original.removeTag (tags[i]);
++changes;
}
// Apply or remove attributes, if any.
std::map <std::string, std::string> attributes;
task.getAttributes (attributes);
foreach (i, attributes)
{
if (i->second == "")
original.removeAttribute (i->first);
else
{
original.setAttribute (i->first, i->second);
// If a "recur" attribute is added, upgrade to a recurring task.
if (i->first == "recur")
original.setStatus (T::recurring);
}
++changes;
}
std::string from;
std::string to;
bool global;
task.getSubstitution (from, to, global);
if (from != "")
{
std::string description = original.getDescription ();
size_t pattern;
if (global)
{
// Perform all subs on description.
while ((pattern = description.find (from)) != std::string::npos)
{
description.replace (pattern, from.length (), to);
++changes;
}
original.setDescription (description);
// Perform all subs on annotations.
std::map <time_t, std::string> annotations;
original.getAnnotations (annotations);
std::map <time_t, std::string>::iterator it;
for (it = annotations.begin (); it != annotations.end (); ++it)
{
while ((pattern = it->second.find (from)) != std::string::npos)
{
it->second.replace (pattern, from.length (), to);
++changes;
}
}
original.setAnnotations (annotations);
}
else
{
// Perform first description substitution.
if ((pattern = description.find (from)) != std::string::npos)
{
description.replace (pattern, from.length (), to);
original.setDescription (description);
++changes;
}
// Failing that, perform the first annotation substitution.
else
{
std::map <time_t, std::string> annotations;
original.getAnnotations (annotations);
std::map <time_t, std::string>::iterator it;
for (it = annotations.begin (); it != annotations.end (); ++it)
{
if ((pattern = it->second.find (from)) != std::string::npos)
{
it->second.replace (pattern, from.length (), to);
++changes;
break;
}
}
original.setAnnotations (annotations);
}
}
}
if (changes) if (changes)
tdb.modifyT (original); tdb.modifyT (*other);
++count; ++count;
} }
} }
}
if (count == 0)
throw std::string ("Task not found.");
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl; out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
@ -844,91 +713,46 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleAppend (TDB& tdb, T& task, Config& conf) std::string handleAppend (TDB& tdb, T& task, Config& conf)
{ {
int count = 0;
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
// Lookup the complete task. std::vector <T> filtered = all;
T complete = findT (task.getId (), all); filterSequence (filtered, task);
foreach (seq, filtered)
int count = 0;
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == complete.getId () || // Self foreach (other, all)
(complete.getAttribute ("parent") != "" && {
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling if (other->getId () == seq->getId () || // Self
it->getUUID () == complete.getAttribute ("parent")) // Parent (seq->getAttribute ("parent") != "" &&
seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
other->getUUID () == seq->getAttribute ("parent")) // Parent
{ {
T original (*it);
// A non-zero value forces a file write. // A non-zero value forces a file write.
int changes = 0; int changes = 0;
// Apply a new description, if any. // Apply other deltas.
if (task.getDescription () != "") changes += deltaAppend (*other, task);
{ changes += deltaTags (*other, task);
original.setDescription (original.getDescription () + changes += deltaAttributes (*other, task);
" " +
task.getDescription ());
++changes;
}
// Apply or remove tags, if any.
std::vector <std::string> tags;
task.getTags (tags);
for (unsigned int i = 0; i < tags.size (); ++i)
{
if (tags[i][0] == '+')
original.addTag (tags[i].substr (1, std::string::npos));
else
original.addTag (tags[i]);
++changes;
}
task.getRemoveTags (tags);
for (unsigned int i = 0; i < tags.size (); ++i)
{
if (tags[i][0] == '-')
original.removeTag (tags[i].substr (1, std::string::npos));
else
original.removeTag (tags[i]);
++changes;
}
// Apply or remove attributes, if any.
std::map <std::string, std::string> attributes;
task.getAttributes (attributes);
foreach (i, attributes)
{
if (i->second == "")
original.removeAttribute (i->first);
else
original.setAttribute (i->first, i->second);
++changes;
}
if (changes) if (changes)
{ {
tdb.modifyT (original); tdb.modifyT (*other);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Appended '" out << "Appended '"
<< task.getDescription () << task.getDescription ()
<< "' to task " << "' to task "
<< original.getId () << other->getId ()
<< std::endl; << std::endl;
} }
++count; ++count;
} }
} }
}
if (count == 0)
throw std::string ("Task not found.");
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl; out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
@ -1030,28 +854,22 @@ std::string handleAnnotate (TDB& tdb, T& task, Config& conf)
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.pendingT (all); tdb.pendingT (all);
filterSequence (all, task);
std::vector <T>::iterator it; foreach (t, all)
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == task.getId ()) t->addAnnotation (task.getDescription ());
{ tdb.modifyT (*t);
it->addAnnotation (task.getDescription ());
tdb.modifyT (*it);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Annotated " out << "Annotated "
<< task.getId () << t->getId ()
<< " with '" << " with '"
<< task.getDescription () << t->getDescription ()
<< "'" << "'"
<< std::endl; << std::endl;
return out.str ();
}
} }
throw std::string ("Task not found.");
return out.str (); return out.str ();
} }
@ -1067,3 +885,156 @@ T findT (int id, const std::vector <T>& all)
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int deltaAppend (T& task, T& delta)
{
if (delta.getDescription () != "")
{
task.setDescription (
task.getDescription () +
" " +
delta.getDescription ());
return 1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
int deltaDescription (T& task, T& delta)
{
if (delta.getDescription () != "")
{
task.setDescription (delta.getDescription ());
return 1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
int deltaTags (T& task, T& delta)
{
int changes = 0;
// Apply or remove tags, if any.
std::vector <std::string> tags;
delta.getTags (tags);
for (unsigned int i = 0; i < tags.size (); ++i)
{
if (tags[i][0] == '+')
task.addTag (tags[i].substr (1, std::string::npos));
else
task.addTag (tags[i]);
++changes;
}
delta.getRemoveTags (tags);
for (unsigned int i = 0; i < tags.size (); ++i)
{
if (tags[i][0] == '-')
task.removeTag (tags[i].substr (1, std::string::npos));
else
task.removeTag (tags[i]);
++changes;
}
return changes;
}
////////////////////////////////////////////////////////////////////////////////
int deltaAttributes (T& task, T& delta)
{
int changes = 0;
std::map <std::string, std::string> attributes;
delta.getAttributes (attributes);
foreach (i, attributes)
{
if (i->second == "")
task.removeAttribute (i->first);
else
task.setAttribute (i->first, i->second);
++changes;
}
return changes;
}
////////////////////////////////////////////////////////////////////////////////
int deltaSubstitutions (T& task, T& delta)
{
int changes = 0;
std::string from;
std::string to;
bool global;
delta.getSubstitution (from, to, global);
if (from != "")
{
std::string description = task.getDescription ();
size_t pattern;
if (global)
{
// Perform all subs on description.
while ((pattern = description.find (from)) != std::string::npos)
{
description.replace (pattern, from.length (), to);
++changes;
}
task.setDescription (description);
// Perform all subs on annotations.
std::map <time_t, std::string> annotations;
task.getAnnotations (annotations);
std::map <time_t, std::string>::iterator it;
for (it = annotations.begin (); it != annotations.end (); ++it)
{
while ((pattern = it->second.find (from)) != std::string::npos)
{
it->second.replace (pattern, from.length (), to);
++changes;
}
}
task.setAnnotations (annotations);
}
else
{
// Perform first description substitution.
if ((pattern = description.find (from)) != std::string::npos)
{
description.replace (pattern, from.length (), to);
task.setDescription (description);
++changes;
}
// Failing that, perform the first annotation substitution.
else
{
std::map <time_t, std::string> annotations;
task.getAnnotations (annotations);
std::map <time_t, std::string>::iterator it;
for (it = annotations.begin (); it != annotations.end (); ++it)
{
if ((pattern = it->second.find (from)) != std::string::npos)
{
it->second.replace (pattern, from.length (), to);
++changes;
break;
}
}
task.setAnnotations (annotations);
}
}
}
return changes;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -301,6 +301,9 @@ static bool validAttribute (
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
static bool validId (const std::string& input) static bool validId (const std::string& input)
{ {
if (input.length () == 0)
return false;
for (size_t i = 0; i < input.length (); ++i) for (size_t i = 0; i < input.length (); ++i)
if (!::isdigit (input[i])) if (!::isdigit (input[i]))
return false; return false;
@ -308,6 +311,56 @@ static bool validId (const std::string& input)
return true; return true;
} }
////////////////////////////////////////////////////////////////////////////////
// 1,2-4,6
static bool validSequence (
const std::string& input,
std::vector <int>& ids)
{
std::vector <std::string> ranges;
split (ranges, input, ',');
std::vector <std::string>::iterator it;
for (it = ranges.begin (); it != ranges.end (); ++it)
{
std::vector <std::string> range;
split (range, *it, '-');
switch (range.size ())
{
case 1:
if (! validId (range[0]))
return false;
int id = ::atoi (range[0].c_str ());
ids.push_back (id);
break;
case 2:
{
if (! validId (range[0]) ||
! validId (range[1]))
return false;
int low = ::atoi (range[0].c_str ());
int high = ::atoi (range[1].c_str ());
if (low >= high)
return false;
for (int i = low; i <= high; ++i)
ids.push_back (i);
}
break;
default:
return false;
break;
}
}
return ids.size () ? true : false;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
static bool validTag (const std::string& input) static bool validTag (const std::string& input)
{ {
@ -392,15 +445,23 @@ bool validDuration (std::string& input)
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Token Distinguishing characteristic // Token EBNF
// ------- ----------------------------- // ------- ----------------------------------
// command first positional // command first non-id recognized argument
// id \d+
// description default, accumulate
// substitution /\w+/\w*/
// tags [-+]\w+
// attributes \w+:.+
// //
// substitution ::= "/" from "/" to "/g"
// | "/" from "/" to "/" ;
//
// tags ::= "+" word
// | "-" word ;
//
// attributes ::= word ":" value
// | word ":"
//
// sequence ::= \d+ "," sequence
// | \d+ "-" \d+ ;
//
// description (whatever isn't one of the above)
void parse ( void parse (
std::vector <std::string>& args, std::vector <std::string>& args,
std::string& command, std::string& command,
@ -409,6 +470,9 @@ void parse (
{ {
command = ""; command = "";
bool foundSequence = false;
bool foundSomethingAfterSequence = false;
std::string descCandidate = ""; std::string descCandidate = "";
for (size_t i = 0; i < args.size (); ++i) for (size_t i = 0; i < args.size (); ++i)
{ {
@ -422,16 +486,24 @@ void parse (
std::string from; std::string from;
std::string to; std::string to;
bool global; bool global;
std::vector <int> sequence;
// An id is the first argument found that contains all digits. // An id is the first argument found that contains all digits.
if (lowerCase (command) != "add" && // "add" doesn't require an ID if (lowerCase (command) != "add" && // "add" doesn't require an ID
task.getId () == 0 && validSequence (arg, sequence) &&
validId (arg)) ! foundSomethingAfterSequence)
task.setId (::atoi (arg.c_str ())); {
foundSequence = true;
foreach (id, sequence)
task.addId (*id);
}
// Tags begin with + or - and contain arbitrary text. // Tags begin with + or - and contain arbitrary text.
else if (validTag (arg)) else if (validTag (arg))
{ {
if (foundSequence)
foundSomethingAfterSequence = true;
if (arg[0] == '+') if (arg[0] == '+')
task.addTag (arg.substr (1, std::string::npos)); task.addTag (arg.substr (1, std::string::npos));
else if (arg[0] == '-') else if (arg[0] == '-')
@ -442,6 +514,9 @@ void parse (
// value. // value.
else if ((colon = arg.find (":")) != std::string::npos) else if ((colon = arg.find (":")) != std::string::npos)
{ {
if (foundSequence)
foundSomethingAfterSequence = true;
std::string name = lowerCase (arg.substr (0, colon)); std::string name = lowerCase (arg.substr (0, colon));
std::string value = arg.substr (colon + 1, std::string::npos); std::string value = arg.substr (colon + 1, std::string::npos);
@ -464,12 +539,18 @@ void parse (
// Substitution of description text. // Substitution of description text.
else if (validSubstitution (arg, from, to, global)) else if (validSubstitution (arg, from, to, global))
{ {
if (foundSequence)
foundSomethingAfterSequence = true;
task.setSubstitution (from, to, global); task.setSubstitution (from, to, global);
} }
// Command. // Command.
else if (command == "") else if (command == "")
{ {
if (foundSequence)
foundSomethingAfterSequence = true;
std::string l = lowerCase (arg); std::string l = lowerCase (arg);
if (isCommand (l) && validCommand (l)) if (isCommand (l) && validCommand (l))
command = l; command = l;
@ -484,6 +565,9 @@ void parse (
// Anything else is just considered description. // Anything else is just considered description.
else else
{ {
if (foundSequence)
foundSomethingAfterSequence = true;
if (descCandidate.length ()) if (descCandidate.length ())
descCandidate += " "; descCandidate += " ";
descCandidate += arg; descCandidate += arg;

View file

@ -46,6 +46,53 @@
#include <ncurses.h> #include <ncurses.h>
#endif #endif
////////////////////////////////////////////////////////////////////////////////
void filterSequence (std::vector<T>& all, T& task)
{
std::vector <int> sequence = task.getAllIds ();
std::vector <T> filtered;
std::vector <T>::iterator t;
for (t = all.begin (); t != all.end (); ++t)
{
std::vector <int>::iterator s;
for (s = sequence.begin (); s != sequence.end (); ++s)
if (t->getId () == *s)
filtered.push_back (*t);
}
if (sequence.size () != filtered.size ())
{
std::vector <int> filteredSequence;
std::vector <T>::iterator fs;
for (fs = filtered.begin (); fs != filtered.end (); ++fs)
filteredSequence.push_back (fs->getId ());
std::vector <int> left;
std::vector <int> right;
listDiff (filteredSequence, sequence, left, right);
if (left.size ())
throw std::string ("Sequence filtering error - please report this error");
if (right.size ())
{
std::stringstream out;
out << "Task";
if (right.size () > 1) out << "s";
std::vector <int>::iterator s;
for (s = right.begin (); s != right.end (); ++s)
out << " " << *s;
out << " not found";
throw out.str ();
}
}
all = filtered;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void filter (std::vector<T>& all, T& task) void filter (std::vector<T>& all, T& task)
{ {
@ -265,6 +312,16 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
std::vector <T> tasks; std::vector <T> tasks;
tdb.allPendingT (tasks); tdb.allPendingT (tasks);
// Find the task.
int count = 0;
for (unsigned int i = 0; i < tasks.size (); ++i)
{
T refTask (tasks[i]);
if (refTask.getId () == task.getId () || task.sequenceContains (refTask.getId ()))
{
++count;
Table table; Table table;
table.setTableWidth (width); table.setTableWidth (width);
table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
@ -285,27 +342,23 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
table.setColumnJustification (0, Table::left); table.setColumnJustification (0, Table::left);
table.setColumnJustification (1, Table::left); table.setColumnJustification (1, Table::left);
// Find the task.
for (unsigned int i = 0; i < tasks.size (); ++i)
{
T refTask (tasks[i]);
if (refTask.getId () == task.getId ())
{
Date now; Date now;
int row = table.addRow (); int row = table.addRow ();
table.addCell (row, 0, "ID"); table.addCell (row, 0, "ID");
table.addCell (row, 1, refTask.getId ()); table.addCell (row, 1, refTask.getId ());
row = table.addRow (); std::string status = refTask.getStatus () == T::pending ? "Pending"
table.addCell (row, 0, "Status");
table.addCell (row, 1, ( refTask.getStatus () == T::pending ? "Pending"
: refTask.getStatus () == T::completed ? "Completed" : refTask.getStatus () == T::completed ? "Completed"
: refTask.getStatus () == T::deleted ? "Deleted" : refTask.getStatus () == T::deleted ? "Deleted"
: refTask.getStatus () == T::recurring ? "Recurring" : refTask.getStatus () == T::recurring ? "Recurring"
: "")); : "";
if (refTask.getAttribute ("parent") != "")
status += " (Recurring)";
row = table.addRow ();
table.addCell (row, 0, "Status");
table.addCell (row, 1, status);
std::string description = refTask.getDescription (); std::string description = refTask.getDescription ();
std::string when; std::string when;
@ -336,16 +389,25 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
table.addCell (row, 1, refTask.getAttribute ("priority")); table.addCell (row, 1, refTask.getAttribute ("priority"));
} }
if (refTask.getStatus () == T::recurring) if (refTask.getStatus () == T::recurring ||
refTask.getAttribute ("parent") != "")
{
if (refTask.getAttribute ("recur") != "")
{ {
row = table.addRow (); row = table.addRow ();
table.addCell (row, 0, "Recurrence"); table.addCell (row, 0, "Recurrence");
table.addCell (row, 1, refTask.getAttribute ("recur")); table.addCell (row, 1, refTask.getAttribute ("recur"));
}
if (refTask.getAttribute ("until") != "")
{
row = table.addRow (); row = table.addRow ();
table.addCell (row, 0, "Recur until"); table.addCell (row, 0, "Recur until");
table.addCell (row, 1, refTask.getAttribute ("until")); table.addCell (row, 1, refTask.getAttribute ("until"));
}
if (refTask.getAttribute ("mask") != "")
{
row = table.addRow (); row = table.addRow ();
table.addCell (row, 0, "Mask"); table.addCell (row, 0, "Mask");
table.addCell (row, 1, refTask.getAttribute ("mask")); table.addCell (row, 1, refTask.getAttribute ("mask"));
@ -356,6 +418,7 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
row = table.addRow (); row = table.addRow ();
table.addCell (row, 0, "Parent task"); table.addCell (row, 0, "Parent task");
table.addCell (row, 1, refTask.getAttribute ("parent")); table.addCell (row, 1, refTask.getAttribute ("parent"));
}
row = table.addRow (); row = table.addRow ();
table.addCell (row, 0, "Mask Index"); table.addCell (row, 0, "Mask Index");
@ -440,14 +503,14 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
} }
table.addCell (row, 1, entry + " (" + age + ")"); table.addCell (row, 1, entry + " (" + age + ")");
}
}
if (table.rowCount ())
out << optionalBlankLine (conf) out << optionalBlankLine (conf)
<< table.render () << table.render ()
<< std::endl; << std::endl;
else }
}
if (! count)
out << "No matches." << std::endl; out << "No matches." << std::endl;
return out.str (); return out.str ();
@ -1038,6 +1101,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
if (task.getStatus () == T::deleted) if (task.getStatus () == T::deleted)
{ {
epoch = monthlyEpoch (task.getAttribute ("end")); epoch = monthlyEpoch (task.getAttribute ("end"));
groups[epoch] = 0;
if (deletedGroup.find (epoch) != deletedGroup.end ()) if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1; deletedGroup[epoch] = deletedGroup[epoch] + 1;
@ -1047,6 +1111,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
else if (task.getStatus () == T::completed) else if (task.getStatus () == T::completed)
{ {
epoch = monthlyEpoch (task.getAttribute ("end")); epoch = monthlyEpoch (task.getAttribute ("end"));
groups[epoch] = 0;
if (completedGroup.find (epoch) != completedGroup.end ()) if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1; completedGroup[epoch] = completedGroup[epoch] + 1;

View file

@ -230,7 +230,13 @@ static std::string longUsage (Config& conf)
{ {
std::stringstream out; std::stringstream out;
out << shortUsage (conf) out << shortUsage (conf)
<< "ID is the numeric identifier displayed by the 'task list' command." << "\n" << "ID is the numeric identifier displayed by the 'task list' command. "
<< "You can specify multiple IDs for task commands, and multiple tasks "
<< "will be affected. To specify multiple IDs make sure you use one "
<< "of these forms:" << "\n"
<< " task delete 1,2,3" << "\n"
<< " task info 1-3" << "\n"
<< " task pri:H 1,2-5,19" << "\n"
<< "\n" << "\n"
<< "Tags are arbitrary words, any quantity:" << "\n" << "Tags are arbitrary words, any quantity:" << "\n"
<< " +tag The + means add the tag" << "\n" << " +tag The + means add the tag" << "\n"
@ -339,7 +345,7 @@ int main (int argc, char** argv)
catch (std::string& error) catch (std::string& error)
{ {
std::cerr << error << std::endl; std::cout << error << std::endl;
return -1; return -1;
} }

View file

@ -90,8 +90,14 @@ std::string handleUndo (TDB&, T&, Config&);
std::string handleColor (Config&); std::string handleColor (Config&);
std::string handleAnnotate (TDB&, T&, Config&); std::string handleAnnotate (TDB&, T&, Config&);
T findT (int, const std::vector <T>&); T findT (int, const std::vector <T>&);
int deltaAppend (T&, T&);
int deltaDescription (T&, T&);
int deltaTags (T&, T&);
int deltaAttributes (T&, T&);
int deltaSubstitutions (T&, T&);
// report.cpp // report.cpp
void filterSequence (std::vector<T>&, T&);
void filter (std::vector<T>&, T&); void filter (std::vector<T>&, T&);
std::string handleInfo (TDB&, T&, Config&); std::string handleInfo (TDB&, T&, Config&);
std::string handleCompleted (TDB&, T&, Config&); std::string handleCompleted (TDB&, T&, Config&);
@ -152,4 +158,45 @@ void autoColorize (T&, Text::color&, Text::color&, Config&);
// import.cpp // import.cpp
std::string handleImport (TDB&, T&, Config&); std::string handleImport (TDB&, T&, Config&);
// list template
///////////////////////////////////////////////////////////////////////////////
template <class T> void listDiff (
const T& left, const T& right, T& leftOnly, T& rightOnly)
{
leftOnly.clear ();
rightOnly.clear ();
for (unsigned int l = 0; l < left.size (); ++l)
{
bool found = false;
for (unsigned int r = 0; r < right.size (); ++r)
{
if (left[l] == right[r])
{
found = true;
break;
}
}
if (!found)
leftOnly.push_back (left[l]);
}
for (unsigned int r = 0; r < right.size (); ++r)
{
bool found = false;
for (unsigned int l = 0; l < left.size (); ++l)
{
if (left[l] == right[r])
{
found = true;
break;
}
}
if (!found)
rightOnly.push_back (right[r]);
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -5,3 +5,4 @@ date.t
duration.t duration.t
text.t text.t
autocomplete.t autocomplete.t
parse.t

View file

@ -1,4 +1,5 @@
PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \
parse.t
CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti
LFLAGS = -L/usr/local/lib LFLAGS = -L/usr/local/lib
OBJECTS = ../TDB.o ../T.o ../parse.o ../text.o ../Date.o ../util.o ../Config.o OBJECTS = ../TDB.o ../T.o ../parse.o ../text.o ../Date.o ../util.o ../Config.o
@ -38,3 +39,6 @@ text.t: text.t.o $(OBJECTS) test.o
autocomplete.t: autocomplete.t.o $(OBJECTS) test.o autocomplete.t: autocomplete.t.o $(OBJECTS) test.o
g++ autocomplete.t.o $(OBJECTS) test.o $(LFLAGS) -o autocomplete.t g++ autocomplete.t.o $(OBJECTS) test.o $(LFLAGS) -o autocomplete.t
parse.t: parse.t.o $(OBJECTS) test.o
g++ parse.t.o $(OBJECTS) test.o $(LFLAGS) -o parse.t

View file

@ -51,49 +51,49 @@ qx{../task rc:confirm.rc add foo} for 1 .. 10;
# Test the various forms of "yes". # Test the various forms of "yes".
my $output = qx{echo "yes" | ../task rc:confirm.rc del 1}; my $output = qx{echo "yes" | ../task rc:confirm.rc del 1};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - yes works'); like ($output, qr/Permanently delete task 1 'foo'\? \(y\/n\)/, 'confirmation - yes works');
unlike ($output, qr/Task not deleted\./, 'confirmation - yes works'); unlike ($output, qr/Task not deleted\./, 'confirmation - yes works');
$output = qx{echo "ye" | ../task rc:confirm.rc del 2}; $output = qx{echo "ye" | ../task rc:confirm.rc del 2};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - ye works'); like ($output, qr/Permanently delete task 2 'foo'\? \(y\/n\)/, 'confirmation - ye works');
unlike ($output, qr/Task not deleted\./, 'confirmation - ye works'); unlike ($output, qr/Task not deleted\./, 'confirmation - ye works');
$output = qx{echo "y" | ../task rc:confirm.rc del 3}; $output = qx{echo "y" | ../task rc:confirm.rc del 3};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - y works'); like ($output, qr/Permanently delete task 3 'foo'\? \(y\/n\)/, 'confirmation - y works');
unlike ($output, qr/Task not deleted\./, 'confirmation - y works'); unlike ($output, qr/Task not deleted\./, 'confirmation - y works');
$output = qx{echo "YES" | ../task rc:confirm.rc del 4}; $output = qx{echo "YES" | ../task rc:confirm.rc del 4};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - YES works'); like ($output, qr/Permanently delete task 4 'foo'\? \(y\/n\)/, 'confirmation - YES works');
unlike ($output, qr/Task not deleted\./, 'confirmation - YES works'); unlike ($output, qr/Task not deleted\./, 'confirmation - YES works');
$output = qx{echo "YE" | ../task rc:confirm.rc del 5}; $output = qx{echo "YE" | ../task rc:confirm.rc del 5};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - YE works'); like ($output, qr/Permanently delete task 5 'foo'\? \(y\/n\)/, 'confirmation - YE works');
unlike ($output, qr/Task not deleted\./, 'confirmation - YE works'); unlike ($output, qr/Task not deleted\./, 'confirmation - YE works');
$output = qx{echo "Y" | ../task rc:confirm.rc del 6}; $output = qx{echo "Y" | ../task rc:confirm.rc del 6};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - Y works'); like ($output, qr/Permanently delete task 6 'foo'\? \(y\/n\)/, 'confirmation - Y works');
unlike ($output, qr/Task not deleted\./, 'confirmation - Y works'); unlike ($output, qr/Task not deleted\./, 'confirmation - Y works');
# Test the various forms of "no". # Test the various forms of "no".
$output = qx{echo "no" | ../task rc:confirm.rc del 7}; $output = qx{echo "no" | ../task rc:confirm.rc del 7};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - no works'); like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - no works');
like ($output, qr/Task not deleted\./, 'confirmation - no works'); like ($output, qr/Task not deleted\./, 'confirmation - no works');
$output = qx{echo "n" | ../task rc:confirm.rc del 7}; $output = qx{echo "n" | ../task rc:confirm.rc del 7};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - n works'); like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - n works');
like ($output, qr/Task not deleted\./, 'confirmation - n works'); like ($output, qr/Task not deleted\./, 'confirmation - n works');
$output = qx{echo "NO" | ../task rc:confirm.rc del 7}; $output = qx{echo "NO" | ../task rc:confirm.rc del 7};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - NO works'); like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - NO works');
like ($output, qr/Task not deleted\./, 'confirmation - NO works'); like ($output, qr/Task not deleted\./, 'confirmation - NO works');
$output = qx{echo "N" | ../task rc:confirm.rc del 7}; $output = qx{echo "N" | ../task rc:confirm.rc del 7};
like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - N works'); like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - N works');
like ($output, qr/Task not deleted\./, 'confirmation - N works'); like ($output, qr/Task not deleted\./, 'confirmation - N works');
# Test newlines. # Test newlines.
$output = qx{cat response.txt | ../task rc:confirm.rc del 7}; $output = qx{cat response.txt | ../task rc:confirm.rc del 7};
like ($output, qr/(Permanently delete task\? \(y\/n\)) \1 \1/, 'confirmation - \n re-prompt works'); like ($output, qr/(Permanently delete task 7 'foo'\? \(y\/n\)) \1 \1/, 'confirmation - \n re-prompt works');
# Cleanup. # Cleanup.
unlink 'pending.data'; unlink 'pending.data';

View file

@ -57,7 +57,7 @@ like ($output, qr/^No matches/, 'No matches');
ok (-r 'completed.data', 'completed.data created'); ok (-r 'completed.data', 'completed.data created');
$output = qx{../task rc:undelete.rc undelete 1}; $output = qx{../task rc:undelete.rc undelete 1};
like ($output, qr/reliably undeleted/, 'can only be reliable undeleted...'); like ($output, qr/Task 1 not found/, 'Task 1 not found');
$output = qx{../task rc:undelete.rc info 1}; $output = qx{../task rc:undelete.rc info 1};
like ($output, qr/No matches./, 'no matches'); like ($output, qr/No matches./, 'no matches');

134
src/tests/parse.t.cpp Normal file
View file

@ -0,0 +1,134 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
// Copyright 2006 - 2009, 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
//
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include "task.h"
#include "test.h"
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (18);
std::vector <std::string> args;
std::string command;
Config conf;
conf.set ("dateformat", "m/d/Y");
{
T task;
split (args, "add foo", ' ');
parse (args, command, task, conf);
t.is (command, "add", "(1) command found");
t.is (task.getId (), 0, "(1) zero id on add");
t.is (task.getDescription (), "foo", "(1) correct description");
}
{
T task;
split (args, "delete 1,3-5,7", ' ');
parse (args, command, task, conf);
std::vector <int> sequence = task.getAllIds ();
t.is (sequence.size (), (size_t)5, "(2) sequence length");
if (sequence.size () == 5)
{
t.is (sequence[0], 1, "(2) sequence[0] == 1");
t.is (sequence[1], 3, "(2) sequence[1] == 3");
t.is (sequence[2], 4, "(2) sequence[2] == 4");
t.is (sequence[3], 5, "(2) sequence[3] == 5");
t.is (sequence[4], 7, "(2) sequence[4] == 7");
}
else
{
t.fail ("(2) sequence[0] == 1");
t.fail ("(2) sequence[1] == 3");
t.fail ("(2) sequence[2] == 4");
t.fail ("(2) sequence[3] == 5");
t.fail ("(2) sequence[4] == 7");
}
}
{
T task;
split (args, "delete 1,2 3,4", ' ');
parse (args, command, task, conf);
std::vector <int> sequence = task.getAllIds ();
t.is (sequence.size (), (size_t)4, "(3) sequence length");
if (sequence.size () == 4)
{
t.is (sequence[0], 1, "(3) sequence[0] == 1");
t.is (sequence[1], 2, "(3) sequence[1] == 2");
t.is (sequence[2], 3, "(3) sequence[2] == 3");
t.is (sequence[3], 4, "(3) sequence[3] == 4");
}
else
{
t.fail ("(3) sequence[0] == 1");
t.fail ("(3) sequence[1] == 2");
t.fail ("(3) sequence[2] == 3");
t.fail ("(3) sequence[3] == 4");
}
}
{
T task;
split (args, "1 There are 7 days in a week", ' ');
parse (args, command, task, conf);
std::vector <int> sequence = task.getAllIds ();
t.is (sequence.size (), (size_t)1, "(4) sequence length");
if (sequence.size () == 1)
{
t.is (sequence[0], 1, "(4) sequence[0] == 1");
}
else
{
t.fail ("(4) sequence[0] == 1");
}
}
{
T task;
args.clear ();
args.push_back ("1");
args.push_back ("4-123 is back-ordered");
parse (args, command, task, conf);
std::vector <int> sequence = task.getAllIds ();
t.is (sequence.size (), (size_t)1, "(5) sequence length");
if (sequence.size () == 1)
{
t.is (sequence[0], 1, "(5) sequence[0] == 1");
}
else
{
t.fail ("(5) sequence[0] == 1");
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -28,7 +28,7 @@
use strict; use strict;
use warnings; use warnings;
use Test::More tests => 7; use Test::More tests => 9;
# Create the rc file. # Create the rc file.
if (open my $fh, '>', 'subst.rc') if (open my $fh, '>', 'subst.rc')
@ -58,6 +58,16 @@ qx{../task rc:subst.rc 1 /bar/BAR/g};
$output = qx{../task rc:subst.rc info 1}; $output = qx{../task rc:subst.rc info 1};
like ($output, qr/BAR BAR BAR/, 'global substitution in annotation'); like ($output, qr/BAR BAR BAR/, 'global substitution in annotation');
qx{../task rc:subst.rc 1 /FOO/aaa/};
qx{../task rc:subst.rc 1 /FOO/bbb/};
qx{../task rc:subst.rc 1 /FOO/ccc/};
$output = qx{../task rc:subst.rc info 1};
like ($output, qr/aaa bbb ccc/, 'individual successive substitution in description');
qx{../task rc:subst.rc 1 /bbb//};
$output = qx{../task rc:subst.rc info 1};
like ($output, qr/aaa ccc/, 'word deletion in description');
# Cleanup. # Cleanup.
unlink 'pending.data'; unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data'); ok (!-r 'pending.data', 'Removed pending.data');

View file

@ -56,8 +56,8 @@ $output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc list};
like ($output, qr/^No matches/, 'No matches'); like ($output, qr/^No matches/, 'No matches');
$output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1}; $output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1};
like ($output, qr/Task 1 not found/, 'task not found'); like ($output, qr/Task 1 not found/, 'Task 1 not found');
like ($output, qr/reliably undone/, 'can only be reliable undone...'); like ($output, qr/No matches/, 'No matches');
# Cleanup. # Cleanup.
ok (-r 'pending.data', 'Need to remove pending.data'); ok (-r 'pending.data', 'Need to remove pending.data');