mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Merge branch '1.7.0' into timesheet
This commit is contained in:
commit
c61a295df7
37 changed files with 1037 additions and 663 deletions
|
@ -4,6 +4,14 @@
|
|||
1.7.0 (?) ?
|
||||
+ Improved the errors when parsing a corrupt or unrecognized pending.data
|
||||
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 ------------------------------
|
||||
|
||||
|
|
6
README
6
README
|
@ -44,7 +44,11 @@ All feedback is welcome, in addition to any bug reports or patches to:
|
|||
|
||||
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 hope task can do the same for you.
|
||||
|
|
26
checklist.txt
Normal file
26
checklist.txt
Normal 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
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
@ -52,7 +51,7 @@
|
|||
generated on a regular basis. Consider the example:
|
||||
</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>
|
||||
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:
|
||||
</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>
|
||||
This create a weekly recurring task that expires on 8/31/2008. What this means
|
||||
|
|
154
html/sequence.html
Normal file
154
html/sequence.html
Normal 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>
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
@ -50,7 +49,6 @@
|
|||
<li><a href="shell.html">Interacting with the Shell</a>
|
||||
<li><a href="config.html">Configuring Task</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="date.html">Date Handling</a>
|
||||
<li><a href="versions.html">Old Versions</a>
|
||||
|
@ -59,6 +57,7 @@
|
|||
<li><a href="custom.html">Custom Reports</a>
|
||||
<li><a href="import.html">Data Import</a>
|
||||
<li><a href="faq.html">Frequently Asked Questions</a>
|
||||
<li><a href="sequence.html">ID Sequences</a>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
@ -71,6 +70,12 @@
|
|||
which illustrates many of task's features.
|
||||
</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 />
|
||||
<h2 class="title">Get the Latest Stable Release</h2>
|
||||
|
||||
|
@ -110,6 +115,14 @@
|
|||
<ul>
|
||||
<li>Improved the errors when parsing a corrupt or unrecognized pending.data
|
||||
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>
|
||||
|
||||
<p>
|
||||
|
|
153
html/usage.html
153
html/usage.html
|
@ -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>
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
<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>
|
||||
|
|
|
@ -149,6 +149,7 @@ void Config::createDefault (const std::string& home)
|
|||
fprintf (out, "next=2\n");
|
||||
fprintf (out, "dateformat=m/d/Y\n");
|
||||
fprintf (out, "#monthsperline=2\n");
|
||||
fprintf (out, "#defaultwidth=80\n");
|
||||
fprintf (out, "curses=on\n");
|
||||
fprintf (out, "color=on\n");
|
||||
fprintf (out, "due=7\n");
|
||||
|
|
15
src/T.cpp
15
src/T.cpp
|
@ -37,6 +37,7 @@ T::T ()
|
|||
mUUID = uuid ();
|
||||
mStatus = pending;
|
||||
mId = 0;
|
||||
mSequence.clear ();
|
||||
mTags.clear ();
|
||||
mAttributes.clear ();
|
||||
mDescription = "";
|
||||
|
@ -59,6 +60,7 @@ T::T (const T& other)
|
|||
mStatus = other.mStatus;
|
||||
mUUID = other.mUUID;
|
||||
mId = other.mId;
|
||||
mSequence = other.mSequence;
|
||||
mDescription = other.mDescription;
|
||||
mTags = other.mTags;
|
||||
mRemoveTags = other.mRemoveTags;
|
||||
|
@ -74,6 +76,7 @@ T& T::operator= (const T& other)
|
|||
mStatus = other.mStatus;
|
||||
mUUID = other.mUUID;
|
||||
mId = other.mId;
|
||||
mSequence = other.mSequence;
|
||||
mDescription = other.mDescription;
|
||||
mTags = other.mTags;
|
||||
mRemoveTags = other.mRemoveTags;
|
||||
|
@ -286,6 +289,16 @@ void T::addAnnotation (const std::string& description)
|
|||
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
|
||||
//
|
||||
|
@ -575,8 +588,6 @@ void T::parse (const std::string& line)
|
|||
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
|
||||
std::vector <std::string> pairs;
|
||||
split (pairs, attributes, ' ');
|
||||
if (pairs.size () == 0)
|
||||
throw std::string ("Could not find any attributes.");
|
||||
|
||||
for (size_t i = 0; i < pairs.size (); ++i)
|
||||
{
|
||||
|
|
6
src/T.h
6
src/T.h
|
@ -49,7 +49,9 @@ public:
|
|||
void setUUID (const std::string& uuid) { mUUID = uuid; }
|
||||
|
||||
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; }
|
||||
void setStatus (status s) { mStatus = s; }
|
||||
|
@ -82,6 +84,7 @@ public:
|
|||
void getAnnotations (std::map <time_t, std::string>&) const;
|
||||
void setAnnotations (const std::map <time_t, std::string>&);
|
||||
void addAnnotation (const std::string&);
|
||||
bool sequenceContains (int) const;
|
||||
|
||||
const std::string compose () const;
|
||||
const std::string composeCSV ();
|
||||
|
@ -95,6 +98,7 @@ private:
|
|||
status mStatus;
|
||||
std::string mUUID;
|
||||
int mId;
|
||||
std::vector <int> mSequence;
|
||||
std::string mDescription;
|
||||
std::vector<std::string> mTags;
|
||||
std::vector<std::string> mRemoveTags;
|
||||
|
|
785
src/command.cpp
785
src/command.cpp
|
@ -193,42 +193,34 @@ std::string handleTags (TDB& tdb, T& task, Config& conf)
|
|||
std::string handleUndelete (TDB& tdb, T& task, Config& conf)
|
||||
{
|
||||
std::stringstream out;
|
||||
|
||||
std::vector <T> all;
|
||||
tdb.allPendingT (all);
|
||||
filterSequence (all, task);
|
||||
|
||||
int id = task.getId ();
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
foreach (t, all)
|
||||
{
|
||||
if (it->getId () == id)
|
||||
if (t->getStatus () == T::deleted)
|
||||
{
|
||||
if (it->getStatus () == T::deleted)
|
||||
{
|
||||
if (it->getAttribute ("recur") != "")
|
||||
{
|
||||
out << "Task does not support 'undelete' for recurring tasks." << std::endl;
|
||||
return out.str ();
|
||||
}
|
||||
if (t->getAttribute ("recur") != "")
|
||||
out << "Task does not support 'undo' for recurring tasks.\n";
|
||||
|
||||
T restored (*it);
|
||||
restored.setStatus (T::pending);
|
||||
restored.removeAttribute ("end");
|
||||
tdb.modifyT (restored);
|
||||
t->setStatus (T::pending);
|
||||
t->removeAttribute ("end");
|
||||
tdb.modifyT (*t);
|
||||
|
||||
out << "Task " << id << " successfully undeleted." << std::endl;
|
||||
return out.str ();
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << id << " is not deleted - therefore cannot undelete." << std::endl;
|
||||
return out.str ();
|
||||
}
|
||||
out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undeleted.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << t->getId () << " '" << t->getDescription () << "' is not deleted - therefore cannot be undeleted.\n";
|
||||
}
|
||||
}
|
||||
|
||||
out << "Task " << id
|
||||
<< " not found - tasks can only be reliably undeleted if the undelete" << std::endl
|
||||
<< "command is run immediately after the errant delete command." << std::endl;
|
||||
out << "\n"
|
||||
<< "Please note that tasks can only be reliably undeleted if the undelete "
|
||||
<< "command is run immediately after the errant delete command."
|
||||
<< std::endl;
|
||||
|
||||
return out.str ();
|
||||
}
|
||||
|
@ -242,37 +234,31 @@ std::string handleUndo (TDB& tdb, T& task, Config& conf)
|
|||
|
||||
std::vector <T> all;
|
||||
tdb.allPendingT (all);
|
||||
filterSequence (all, task);
|
||||
|
||||
int id = task.getId ();
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
foreach (t, all)
|
||||
{
|
||||
if (it->getId () == id)
|
||||
if (t->getStatus () == T::completed)
|
||||
{
|
||||
if (it->getStatus () == T::completed)
|
||||
{
|
||||
if (it->getAttribute ("recur") != "")
|
||||
return std::string ("Task does not support 'undo' for recurring tasks.\n");
|
||||
if (t->getAttribute ("recur") != "")
|
||||
out << "Task does not support 'undo' for recurring tasks.\n";
|
||||
|
||||
T restored (*it);
|
||||
restored.setStatus (T::pending);
|
||||
restored.removeAttribute ("end");
|
||||
tdb.modifyT (restored);
|
||||
t->setStatus (T::pending);
|
||||
t->removeAttribute ("end");
|
||||
tdb.modifyT (*t);
|
||||
|
||||
out << "Task " << id << " successfully undone." << std::endl;
|
||||
return out.str ();
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << id << " is not done - therefore cannot be undone." << std::endl;
|
||||
return out.str ();
|
||||
}
|
||||
out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undone." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << t->getId () << " '" << t->getDescription () << "' is not done - therefore cannot be undone." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
out << "Task " << id
|
||||
<< " not found - tasks can only be reliably undone if the undo" << std::endl
|
||||
<< "command is run immediately after the errant done command." << std::endl;
|
||||
out << 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;
|
||||
|
||||
return out.str ();
|
||||
}
|
||||
|
@ -367,8 +353,9 @@ std::string handleVersion (Config& conf)
|
|||
|
||||
// Complain about configuration variables that are not recognized.
|
||||
// These are the regular configuration variables.
|
||||
// Note that there is a leading and trailing space.
|
||||
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 "
|
||||
"confirmation curses data.location dateformat default.command "
|
||||
"default.priority defaultwidth due echo.command locking monthsperline nag "
|
||||
|
@ -377,7 +364,7 @@ std::string handleVersion (Config& conf)
|
|||
"import.synonym.tags import.synonym.entry import.synonym.start "
|
||||
"import.synonym.due import.synonym.recur import.synonym.end "
|
||||
"import.synonym.project import.synonym.priority import.synonym.fg "
|
||||
"import.synonym.bg import.synonym.description";
|
||||
"import.synonym.bg import.synonym.description ";
|
||||
|
||||
// This configuration variable is supported, but not documented. It exists
|
||||
// so that unit tests can force color to be on even when the output from task
|
||||
|
@ -387,7 +374,10 @@ std::string handleVersion (Config& conf)
|
|||
std::vector <std::string> unrecognized;
|
||||
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
|
||||
// dynamic.
|
||||
|
@ -439,68 +429,75 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
|
|||
{
|
||||
std::stringstream out;
|
||||
|
||||
if (!conf.get (std::string ("confirmation"), false) || confirm ("Permanently delete task?"))
|
||||
std::vector <T> all;
|
||||
tdb.allPendingT (all);
|
||||
filterSequence (all, task);
|
||||
|
||||
foreach (t, all)
|
||||
{
|
||||
std::vector <T> all;
|
||||
tdb.allPendingT (all);
|
||||
foreach (t, all)
|
||||
std::stringstream question;
|
||||
question << "Permanently delete task "
|
||||
<< t->getId ()
|
||||
<< " '"
|
||||
<< t->getDescription ()
|
||||
<< "'?";
|
||||
|
||||
if (!conf.get (std::string ("confirmation"), false) || confirm (question.str ()))
|
||||
{
|
||||
if (t->getId () == task.getId ())
|
||||
// Check for the more complex case of a recurring task. If this is a
|
||||
// recurring task, get confirmation to delete them all.
|
||||
std::string parent = t->getAttribute ("parent");
|
||||
if (parent != "")
|
||||
{
|
||||
// Check for the more complex case of a recurring task. If this is a
|
||||
// recurring task, get confirmation to delete them all.
|
||||
std::string parent = t->getAttribute ("parent");
|
||||
if (parent != "")
|
||||
if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?"))
|
||||
{
|
||||
if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?"))
|
||||
// Scan all pending tasks for siblings of this task, and the parent
|
||||
// itself, and delete them.
|
||||
foreach (sibling, all)
|
||||
{
|
||||
// Scan all pending tasks for siblings of this task, and the parent
|
||||
// itself, and delete them.
|
||||
foreach (sibling, all)
|
||||
if (sibling->getAttribute ("parent") == parent ||
|
||||
sibling->getUUID () == parent)
|
||||
{
|
||||
if (sibling->getAttribute ("parent") == parent ||
|
||||
sibling->getUUID () == parent)
|
||||
{
|
||||
tdb.deleteT (*sibling);
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Deleting recurring task "
|
||||
<< sibling->getId ()
|
||||
<< " "
|
||||
<< sibling->getDescription ()
|
||||
<< std::endl;
|
||||
}
|
||||
tdb.deleteT (*sibling);
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Deleting recurring task "
|
||||
<< sibling->getId ()
|
||||
<< " '"
|
||||
<< sibling->getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update mask in parent.
|
||||
t->setStatus (T::deleted);
|
||||
updateRecurrenceMask (tdb, all, *t);
|
||||
tdb.deleteT (*t);
|
||||
out << "Deleting recurring task "
|
||||
<< t->getId ()
|
||||
<< " "
|
||||
<< t->getDescription ()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update mask in parent.
|
||||
t->setStatus (T::deleted);
|
||||
updateRecurrenceMask (tdb, all, *t);
|
||||
tdb.deleteT (*t);
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Deleting task "
|
||||
<< t->getId ()
|
||||
<< " "
|
||||
<< t->getDescription ()
|
||||
<< std::endl;
|
||||
out << "Deleting recurring task "
|
||||
<< t->getId ()
|
||||
<< " '"
|
||||
<< t->getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
break; // No point continuing the loop.
|
||||
}
|
||||
else
|
||||
{
|
||||
tdb.deleteT (*t);
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Deleting task "
|
||||
<< t->getId ()
|
||||
<< " '"
|
||||
<< t->getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
out << "Task not deleted." << std::endl;
|
||||
}
|
||||
else
|
||||
out << "Task not deleted." << std::endl;
|
||||
|
||||
return out.str ();
|
||||
}
|
||||
|
@ -508,81 +505,66 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
std::string handleStart (TDB& tdb, T& task, Config& conf)
|
||||
{
|
||||
std::stringstream out;
|
||||
|
||||
std::vector <T> all;
|
||||
tdb.pendingT (all);
|
||||
filterSequence (all, task);
|
||||
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
foreach (t, all)
|
||||
{
|
||||
if (it->getId () == task.getId ())
|
||||
if (t->getAttribute ("start") == "")
|
||||
{
|
||||
T original (*it);
|
||||
std::stringstream out;
|
||||
char startTime[16];
|
||||
sprintf (startTime, "%u", (unsigned int) time (NULL));
|
||||
t->setAttribute ("start", startTime);
|
||||
|
||||
if (original.getAttribute ("start") == "")
|
||||
{
|
||||
char startTime[16];
|
||||
sprintf (startTime, "%u", (unsigned int) time (NULL));
|
||||
original.setAttribute ("start", startTime);
|
||||
tdb.modifyT (*t);
|
||||
|
||||
original.setId (task.getId ());
|
||||
tdb.modifyT (original);
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Started "
|
||||
<< original.getId ()
|
||||
<< " "
|
||||
<< original.getDescription ()
|
||||
<< std::endl;
|
||||
nag (tdb, task, conf);
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << task.getId () << " already started." << std::endl;
|
||||
}
|
||||
|
||||
return out.str ();
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Started "
|
||||
<< t->getId ()
|
||||
<< " '"
|
||||
<< t->getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
nag (tdb, task, conf);
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << t->getId () << " '" << t->getDescription () << "' already started." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::string ("Task not found.");
|
||||
return std::string (""); // To satisfy gcc.
|
||||
return out.str ();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
std::string handleStop (TDB& tdb, T& task, Config& conf)
|
||||
{
|
||||
std::stringstream out;
|
||||
|
||||
std::vector <T> all;
|
||||
tdb.pendingT (all);
|
||||
filterSequence (all, task);
|
||||
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
foreach (t, all)
|
||||
{
|
||||
if (it->getId () == task.getId ())
|
||||
if (t->getAttribute ("start") != "")
|
||||
{
|
||||
T original (*it);
|
||||
std::stringstream out;
|
||||
t->removeAttribute ("start");
|
||||
tdb.modifyT (*t);
|
||||
|
||||
if (original.getAttribute ("start") != "")
|
||||
{
|
||||
original.removeAttribute ("start");
|
||||
original.setId (task.getId ());
|
||||
tdb.modifyT (original);
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Stopped " << original.getId () << " " << original.getDescription () << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << task.getId () << " not started." << std::endl;
|
||||
}
|
||||
|
||||
return out.str ();
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Stopped " << t->getId () << " '" << t->getDescription () << "'" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Task " << t->getId () << " '" << t->getDescription () << "' not started." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::string ("Task not found.");
|
||||
return std::string (""); // To satisfy gcc.
|
||||
return out.str ();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -590,30 +572,30 @@ std::string handleDone (TDB& tdb, T& task, Config& conf)
|
|||
{
|
||||
std::stringstream out;
|
||||
|
||||
if (!tdb.completeT (task))
|
||||
throw std::string ("Could not mark task as completed.");
|
||||
|
||||
// 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))
|
||||
out << "Completed "
|
||||
<< t->getId ()
|
||||
<< " "
|
||||
<< t->getDescription ()
|
||||
<< std::endl;
|
||||
std::vector <T> filtered = all;
|
||||
filterSequence (filtered, task);
|
||||
|
||||
t->setStatus (T::completed);
|
||||
updateRecurrenceMask (tdb, all, *t);
|
||||
break;
|
||||
}
|
||||
foreach (t, filtered)
|
||||
{
|
||||
t->setStatus (T::completed);
|
||||
if (!tdb.completeT (*t))
|
||||
throw std::string ("Could not mark task as completed.");
|
||||
|
||||
// Now update mask in parent.
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Completed "
|
||||
<< t->getId ()
|
||||
<< " '"
|
||||
<< t->getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
|
||||
updateRecurrenceMask (tdb, all, *t);
|
||||
nag (tdb, task, conf);
|
||||
}
|
||||
|
||||
nag (tdb, task, conf);
|
||||
return out.str ();
|
||||
}
|
||||
|
||||
|
@ -677,164 +659,51 @@ std::string handleExport (TDB& tdb, T& task, Config& conf)
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
std::string handleModify (TDB& tdb, T& task, Config& conf)
|
||||
{
|
||||
int count = 0;
|
||||
std::stringstream out;
|
||||
std::vector <T> all;
|
||||
tdb.allPendingT (all);
|
||||
|
||||
// Lookup the complete task.
|
||||
T complete = findT (task.getId (), all);
|
||||
|
||||
// Perform some logical consistency checks.
|
||||
if (task.getAttribute ("recur") != "" &&
|
||||
task.getAttribute ("due") == "" &&
|
||||
complete.getAttribute ("due") == "")
|
||||
throw std::string ("You cannot specify a recurring task without a due date.");
|
||||
|
||||
if (task.getAttribute ("until") != "" &&
|
||||
task.getAttribute ("recur") == "" &&
|
||||
complete.getAttribute ("recur") == "")
|
||||
throw std::string ("You cannot specify an until date for a non-recurring task.");
|
||||
|
||||
int count = 0;
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
std::vector <T> filtered = all;
|
||||
filterSequence (filtered, task);
|
||||
foreach (seq, filtered)
|
||||
{
|
||||
if (it->getId () == complete.getId () || // Self
|
||||
(complete.getAttribute ("parent") != "" &&
|
||||
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling
|
||||
it->getUUID () == complete.getAttribute ("parent")) // Parent
|
||||
// Perform some logical consistency checks.
|
||||
if (task.getAttribute ("recur") != "" &&
|
||||
task.getAttribute ("due") == "" &&
|
||||
seq->getAttribute ("due") == "")
|
||||
throw std::string ("You cannot specify a recurring task without a due date.");
|
||||
|
||||
if (task.getAttribute ("until") != "" &&
|
||||
task.getAttribute ("recur") == "" &&
|
||||
seq->getAttribute ("recur") == "")
|
||||
throw std::string ("You cannot specify an until date for a non-recurring task.");
|
||||
|
||||
// Make all changes.
|
||||
foreach (other, all)
|
||||
{
|
||||
T original (*it);
|
||||
|
||||
// A non-zero value forces a file write.
|
||||
int changes = 0;
|
||||
|
||||
// Apply a new description, if any.
|
||||
if (task.getDescription () != "")
|
||||
if (other->getId () == seq->getId () || // Self
|
||||
(seq->getAttribute ("parent") != "" &&
|
||||
seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
|
||||
other->getUUID () == seq->getAttribute ("parent")) // Parent
|
||||
{
|
||||
original.setDescription (task.getDescription ());
|
||||
++changes;
|
||||
// A non-zero value forces a file write.
|
||||
int changes = 0;
|
||||
|
||||
// Apply other deltas.
|
||||
changes += deltaDescription (*other, task);
|
||||
changes += deltaTags (*other, task);
|
||||
changes += deltaAttributes (*other, task);
|
||||
changes += deltaSubstitutions (*other, task);
|
||||
|
||||
if (changes)
|
||||
tdb.modifyT (*other);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
// 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)
|
||||
tdb.modifyT (original);
|
||||
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
throw std::string ("Task not found.");
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
|
||||
|
||||
|
@ -844,92 +713,47 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
std::string handleAppend (TDB& tdb, T& task, Config& conf)
|
||||
{
|
||||
int count = 0;
|
||||
std::stringstream out;
|
||||
std::vector <T> all;
|
||||
tdb.allPendingT (all);
|
||||
|
||||
// Lookup the complete task.
|
||||
T complete = findT (task.getId (), all);
|
||||
|
||||
int count = 0;
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
std::vector <T> filtered = all;
|
||||
filterSequence (filtered, task);
|
||||
foreach (seq, filtered)
|
||||
{
|
||||
if (it->getId () == complete.getId () || // Self
|
||||
(complete.getAttribute ("parent") != "" &&
|
||||
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling
|
||||
it->getUUID () == complete.getAttribute ("parent")) // Parent
|
||||
foreach (other, all)
|
||||
{
|
||||
T original (*it);
|
||||
|
||||
// A non-zero value forces a file write.
|
||||
int changes = 0;
|
||||
|
||||
// Apply a new description, if any.
|
||||
if (task.getDescription () != "")
|
||||
if (other->getId () == seq->getId () || // Self
|
||||
(seq->getAttribute ("parent") != "" &&
|
||||
seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
|
||||
other->getUUID () == seq->getAttribute ("parent")) // Parent
|
||||
{
|
||||
original.setDescription (original.getDescription () +
|
||||
" " +
|
||||
task.getDescription ());
|
||||
++changes;
|
||||
// A non-zero value forces a file write.
|
||||
int changes = 0;
|
||||
|
||||
// Apply other deltas.
|
||||
changes += deltaAppend (*other, task);
|
||||
changes += deltaTags (*other, task);
|
||||
changes += deltaAttributes (*other, task);
|
||||
|
||||
if (changes)
|
||||
{
|
||||
tdb.modifyT (*other);
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Appended '"
|
||||
<< task.getDescription ()
|
||||
<< "' to task "
|
||||
<< other->getId ()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
tdb.modifyT (original);
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Appended '"
|
||||
<< task.getDescription ()
|
||||
<< "' to task "
|
||||
<< original.getId ()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
throw std::string ("Task not found.");
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
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::vector <T> all;
|
||||
tdb.pendingT (all);
|
||||
filterSequence (all, task);
|
||||
|
||||
std::vector <T>::iterator it;
|
||||
for (it = all.begin (); it != all.end (); ++it)
|
||||
foreach (t, all)
|
||||
{
|
||||
if (it->getId () == task.getId ())
|
||||
{
|
||||
it->addAnnotation (task.getDescription ());
|
||||
tdb.modifyT (*it);
|
||||
t->addAnnotation (task.getDescription ());
|
||||
tdb.modifyT (*t);
|
||||
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Annotated "
|
||||
<< task.getId ()
|
||||
<< " with '"
|
||||
<< task.getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
|
||||
return out.str ();
|
||||
}
|
||||
if (conf.get ("echo.command", true))
|
||||
out << "Annotated "
|
||||
<< t->getId ()
|
||||
<< " with '"
|
||||
<< t->getDescription ()
|
||||
<< "'"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
throw std::string ("Task not found.");
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
108
src/parse.cpp
108
src/parse.cpp
|
@ -301,6 +301,9 @@ static bool validAttribute (
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
static bool validId (const std::string& input)
|
||||
{
|
||||
if (input.length () == 0)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < input.length (); ++i)
|
||||
if (!::isdigit (input[i]))
|
||||
return false;
|
||||
|
@ -308,6 +311,56 @@ static bool validId (const std::string& input)
|
|||
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)
|
||||
{
|
||||
|
@ -392,15 +445,23 @@ bool validDuration (std::string& input)
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Token Distinguishing characteristic
|
||||
// ------- -----------------------------
|
||||
// command first positional
|
||||
// id \d+
|
||||
// description default, accumulate
|
||||
// substitution /\w+/\w*/
|
||||
// tags [-+]\w+
|
||||
// attributes \w+:.+
|
||||
// Token EBNF
|
||||
// ------- ----------------------------------
|
||||
// command first non-id recognized argument
|
||||
//
|
||||
// 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 (
|
||||
std::vector <std::string>& args,
|
||||
std::string& command,
|
||||
|
@ -409,6 +470,9 @@ void parse (
|
|||
{
|
||||
command = "";
|
||||
|
||||
bool foundSequence = false;
|
||||
bool foundSomethingAfterSequence = false;
|
||||
|
||||
std::string descCandidate = "";
|
||||
for (size_t i = 0; i < args.size (); ++i)
|
||||
{
|
||||
|
@ -422,16 +486,24 @@ void parse (
|
|||
std::string from;
|
||||
std::string to;
|
||||
bool global;
|
||||
std::vector <int> sequence;
|
||||
|
||||
// An id is the first argument found that contains all digits.
|
||||
if (lowerCase (command) != "add" && // "add" doesn't require an ID
|
||||
task.getId () == 0 &&
|
||||
validId (arg))
|
||||
task.setId (::atoi (arg.c_str ()));
|
||||
if (lowerCase (command) != "add" && // "add" doesn't require an ID
|
||||
validSequence (arg, sequence) &&
|
||||
! foundSomethingAfterSequence)
|
||||
{
|
||||
foundSequence = true;
|
||||
foreach (id, sequence)
|
||||
task.addId (*id);
|
||||
}
|
||||
|
||||
// Tags begin with + or - and contain arbitrary text.
|
||||
else if (validTag (arg))
|
||||
{
|
||||
if (foundSequence)
|
||||
foundSomethingAfterSequence = true;
|
||||
|
||||
if (arg[0] == '+')
|
||||
task.addTag (arg.substr (1, std::string::npos));
|
||||
else if (arg[0] == '-')
|
||||
|
@ -442,6 +514,9 @@ void parse (
|
|||
// value.
|
||||
else if ((colon = arg.find (":")) != std::string::npos)
|
||||
{
|
||||
if (foundSequence)
|
||||
foundSomethingAfterSequence = true;
|
||||
|
||||
std::string name = lowerCase (arg.substr (0, colon));
|
||||
std::string value = arg.substr (colon + 1, std::string::npos);
|
||||
|
||||
|
@ -464,12 +539,18 @@ void parse (
|
|||
// Substitution of description text.
|
||||
else if (validSubstitution (arg, from, to, global))
|
||||
{
|
||||
if (foundSequence)
|
||||
foundSomethingAfterSequence = true;
|
||||
|
||||
task.setSubstitution (from, to, global);
|
||||
}
|
||||
|
||||
// Command.
|
||||
else if (command == "")
|
||||
{
|
||||
if (foundSequence)
|
||||
foundSomethingAfterSequence = true;
|
||||
|
||||
std::string l = lowerCase (arg);
|
||||
if (isCommand (l) && validCommand (l))
|
||||
command = l;
|
||||
|
@ -484,6 +565,9 @@ void parse (
|
|||
// Anything else is just considered description.
|
||||
else
|
||||
{
|
||||
if (foundSequence)
|
||||
foundSomethingAfterSequence = true;
|
||||
|
||||
if (descCandidate.length ())
|
||||
descCandidate += " ";
|
||||
descCandidate += arg;
|
||||
|
|
163
src/report.cpp
163
src/report.cpp
|
@ -46,6 +46,53 @@
|
|||
#include <ncurses.h>
|
||||
#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)
|
||||
{
|
||||
|
@ -265,47 +312,53 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
|
|||
std::vector <T> tasks;
|
||||
tdb.allPendingT (tasks);
|
||||
|
||||
Table table;
|
||||
table.setTableWidth (width);
|
||||
table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
|
||||
|
||||
table.addColumn ("Name");
|
||||
table.addColumn ("Value");
|
||||
|
||||
if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
|
||||
{
|
||||
table.setColumnUnderline (0);
|
||||
table.setColumnUnderline (1);
|
||||
}
|
||||
else
|
||||
table.setTableDashedUnderline ();
|
||||
|
||||
table.setColumnWidth (0, Table::minimum);
|
||||
table.setColumnWidth (1, Table::flexible);
|
||||
|
||||
table.setColumnJustification (0, Table::left);
|
||||
table.setColumnJustification (1, Table::left);
|
||||
|
||||
// Find the task.
|
||||
int count = 0;
|
||||
for (unsigned int i = 0; i < tasks.size (); ++i)
|
||||
{
|
||||
T refTask (tasks[i]);
|
||||
|
||||
if (refTask.getId () == task.getId ())
|
||||
if (refTask.getId () == task.getId () || task.sequenceContains (refTask.getId ()))
|
||||
{
|
||||
Date now;
|
||||
++count;
|
||||
|
||||
Table table;
|
||||
table.setTableWidth (width);
|
||||
table.setDateFormat (conf.get ("dateformat", "m/d/Y"));
|
||||
|
||||
table.addColumn ("Name");
|
||||
table.addColumn ("Value");
|
||||
|
||||
if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
|
||||
{
|
||||
table.setColumnUnderline (0);
|
||||
table.setColumnUnderline (1);
|
||||
}
|
||||
else
|
||||
table.setTableDashedUnderline ();
|
||||
|
||||
table.setColumnWidth (0, Table::minimum);
|
||||
table.setColumnWidth (1, Table::flexible);
|
||||
|
||||
table.setColumnJustification (0, Table::left);
|
||||
table.setColumnJustification (1, Table::left);
|
||||
Date now;
|
||||
|
||||
int row = table.addRow ();
|
||||
table.addCell (row, 0, "ID");
|
||||
table.addCell (row, 1, refTask.getId ());
|
||||
|
||||
std::string status = refTask.getStatus () == T::pending ? "Pending"
|
||||
: refTask.getStatus () == T::completed ? "Completed"
|
||||
: refTask.getStatus () == T::deleted ? "Deleted"
|
||||
: refTask.getStatus () == T::recurring ? "Recurring"
|
||||
: "";
|
||||
if (refTask.getAttribute ("parent") != "")
|
||||
status += " (Recurring)";
|
||||
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Status");
|
||||
table.addCell (row, 1, ( refTask.getStatus () == T::pending ? "Pending"
|
||||
: refTask.getStatus () == T::completed ? "Completed"
|
||||
: refTask.getStatus () == T::deleted ? "Deleted"
|
||||
: refTask.getStatus () == T::recurring ? "Recurring"
|
||||
: ""));
|
||||
table.addCell (row, 1, status);
|
||||
|
||||
std::string description = refTask.getDescription ();
|
||||
std::string when;
|
||||
|
@ -336,26 +389,36 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
|
|||
table.addCell (row, 1, refTask.getAttribute ("priority"));
|
||||
}
|
||||
|
||||
if (refTask.getStatus () == T::recurring)
|
||||
if (refTask.getStatus () == T::recurring ||
|
||||
refTask.getAttribute ("parent") != "")
|
||||
{
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Recurrence");
|
||||
table.addCell (row, 1, refTask.getAttribute ("recur"));
|
||||
if (refTask.getAttribute ("recur") != "")
|
||||
{
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Recurrence");
|
||||
table.addCell (row, 1, refTask.getAttribute ("recur"));
|
||||
}
|
||||
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Recur until");
|
||||
table.addCell (row, 1, refTask.getAttribute ("until"));
|
||||
if (refTask.getAttribute ("until") != "")
|
||||
{
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Recur until");
|
||||
table.addCell (row, 1, refTask.getAttribute ("until"));
|
||||
}
|
||||
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Mask");
|
||||
table.addCell (row, 1, refTask.getAttribute ("mask"));
|
||||
}
|
||||
if (refTask.getAttribute ("mask") != "")
|
||||
{
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Mask");
|
||||
table.addCell (row, 1, refTask.getAttribute ("mask"));
|
||||
}
|
||||
|
||||
if (refTask.getAttribute ("parent") != "")
|
||||
{
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Parent task");
|
||||
table.addCell (row, 1, refTask.getAttribute ("parent"));
|
||||
if (refTask.getAttribute ("parent") != "")
|
||||
{
|
||||
row = table.addRow ();
|
||||
table.addCell (row, 0, "Parent task");
|
||||
table.addCell (row, 1, refTask.getAttribute ("parent"));
|
||||
}
|
||||
|
||||
row = table.addRow ();
|
||||
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 + ")");
|
||||
|
||||
out << optionalBlankLine (conf)
|
||||
<< table.render ()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (table.rowCount ())
|
||||
out << optionalBlankLine (conf)
|
||||
<< table.render ()
|
||||
<< std::endl;
|
||||
else
|
||||
if (! count)
|
||||
out << "No matches." << std::endl;
|
||||
|
||||
return out.str ();
|
||||
|
@ -1038,6 +1101,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
|
|||
if (task.getStatus () == T::deleted)
|
||||
{
|
||||
epoch = monthlyEpoch (task.getAttribute ("end"));
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (deletedGroup.find (epoch) != deletedGroup.end ())
|
||||
deletedGroup[epoch] = deletedGroup[epoch] + 1;
|
||||
|
@ -1047,6 +1111,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
|
|||
else if (task.getStatus () == T::completed)
|
||||
{
|
||||
epoch = monthlyEpoch (task.getAttribute ("end"));
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (completedGroup.find (epoch) != completedGroup.end ())
|
||||
completedGroup[epoch] = completedGroup[epoch] + 1;
|
||||
|
|
10
src/task.cpp
10
src/task.cpp
|
@ -230,7 +230,13 @@ static std::string longUsage (Config& conf)
|
|||
{
|
||||
std::stringstream out;
|
||||
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"
|
||||
<< "Tags are arbitrary words, any quantity:" << "\n"
|
||||
<< " +tag The + means add the tag" << "\n"
|
||||
|
@ -339,7 +345,7 @@ int main (int argc, char** argv)
|
|||
|
||||
catch (std::string& error)
|
||||
{
|
||||
std::cerr << error << std::endl;
|
||||
std::cout << error << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
47
src/task.h
47
src/task.h
|
@ -90,8 +90,14 @@ std::string handleUndo (TDB&, T&, Config&);
|
|||
std::string handleColor (Config&);
|
||||
std::string handleAnnotate (TDB&, T&, Config&);
|
||||
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
|
||||
void filterSequence (std::vector<T>&, T&);
|
||||
void filter (std::vector<T>&, T&);
|
||||
std::string handleInfo (TDB&, T&, Config&);
|
||||
std::string handleCompleted (TDB&, T&, Config&);
|
||||
|
@ -152,4 +158,45 @@ void autoColorize (T&, Text::color&, Text::color&, Config&);
|
|||
// import.cpp
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
1
src/tests/.gitignore
vendored
1
src/tests/.gitignore
vendored
|
@ -5,3 +5,4 @@ date.t
|
|||
duration.t
|
||||
text.t
|
||||
autocomplete.t
|
||||
parse.t
|
||||
|
|
|
@ -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
|
||||
LFLAGS = -L/usr/local/lib
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
@ -51,49 +51,49 @@ qx{../task rc:confirm.rc add foo} for 1 .. 10;
|
|||
|
||||
# Test the various forms of "yes".
|
||||
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');
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
# Test the various forms of "no".
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
# Test newlines.
|
||||
$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.
|
||||
unlink 'pending.data';
|
||||
|
|
|
@ -57,7 +57,7 @@ like ($output, qr/^No matches/, 'No matches');
|
|||
ok (-r 'completed.data', 'completed.data created');
|
||||
|
||||
$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};
|
||||
like ($output, qr/No matches./, 'no matches');
|
||||
|
|
134
src/tests/parse.t.cpp
Normal file
134
src/tests/parse.t.cpp
Normal 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More tests => 7;
|
||||
use Test::More tests => 9;
|
||||
|
||||
# Create the rc file.
|
||||
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};
|
||||
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.
|
||||
unlink 'pending.data';
|
||||
ok (!-r 'pending.data', 'Removed pending.data');
|
||||
|
|
|
@ -56,8 +56,8 @@ $output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc list};
|
|||
like ($output, qr/^No matches/, 'No matches');
|
||||
|
||||
$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/reliably undone/, 'can only be reliable undone...');
|
||||
like ($output, qr/Task 1 not found/, 'Task 1 not found');
|
||||
like ($output, qr/No matches/, 'No matches');
|
||||
|
||||
# Cleanup.
|
||||
ok (-r 'pending.data', 'Need to remove pending.data');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue