Compare commits

...

67 Commits

Author SHA1 Message Date
Ingo Karkat
3b960a2e3c Revert to safer POSIX AWK regexp.
AWK from Ubuntu 8.04 (mawk) doesn't support [[:space:]]; so for backwards compatibility use a plain ASCII space instead.
2012-02-21 09:18:39 +01:00
Gina Trapani
ad1ca6c2c9 Merge pull request #75 from inkarkat/rid-tmp-file
Split up _list() and get rid of $TMP_FILE and cleanup().
2012-02-14 21:59:10 -08:00
Gina Trapani
36e018fd86 Merge pull request #74 from inkarkat/listpri-pri-range
ENH: Allow listpri filtering with priority ranges.
2012-02-14 21:58:26 -08:00
Gina Trapani
74858365f6 Merge pull request #73 from inkarkat/testlib-enhancements
Testlib enhancements

As the vast majority of tests uses the todo.sh output to verify its correctness, output differences should always be shown, not just in verbose mode. 

This change removes the output redirection and check for exit code from the command-under-test, so as another benefit, these "housekeeping commands" are now kept off the test log.

Also, by using a separate function for exit code assertion, this can now report both differences in output and exit code (and not suppress the former any more.)

(Note: To see any of this, you need to temporarily introduce some test failures.)

Finally, I've added color highlighting for the aggregate test report (as in test-lib), because it didn't stood out against the colored test results, so that I often missed the crucial summary.
2012-02-14 20:55:28 -08:00
Ingo Karkat
be0a0265d1 Also get rid of TMP_FILE in todo.cfg.
There's a slight chance that some add-on has used this (undocumented, unofficial) configuration value for its own purposes (and maybe also relied on the unexposed cleanup() infrastructure), but detecting and fixing that problem (by moving the cleanup into the add-on itself) is pretty straightforward.
2012-01-26 16:21:46 +01:00
Ingo Karkat
cf7f7531be Break up _list(), get rid of TMP_FILE.
Extract a new function _format() (and getPadding(), both also exported for add-ons) from _list(), which includes the main formatting and filtering pipeline, without the file handling and verbose summary. This can receive the todo file via stdin, so the listall action is able to format the concatenated files without going through a temporary file.

Eventually, after further refactorings, _format() could be used for actual formatted verbose messages in all commands; currently, the raw, unformatted task is printed.
2012-01-26 16:18:30 +01:00
Ingo Karkat
28ec5a06f2 Get rid of cleanup, only use TMP_FILE in listall.
After the recent refactorings, the temporary file is only needed for the listall action. Therefore, the creation-checks and eventual cleanup can be restricted to the listall action, which should slightly speed up the overall script execution.
2012-01-26 14:48:29 +01:00
Ingo Karkat
dfec12e2a4 ENH: Allow listpri filtering with priority ranges.
So far, the listpri action only supports a single priority. Allowing priority ranges (e.g. todo.sh listpri A-C @work) is a simple but useful enhancement.

Note: The syntax extension only clashes with the [TERM] filtering in a few corner cases, and this can be worked around (e.g. "todo.sh listpri A-Z A-Z" lists all prioritized tasks containing the text A-Z).
2012-01-26 13:17:31 +01:00
Ingo Karkat
c31716af47 test-report: Use color highlighting as in test-lib.
The test aggregate results are easy to miss when running the entire test suite via "make test", as the status of the last test case is highlighted, but the aggregate results appear in an unformatted, uncolored block of text.
Copy the say_color() function from test-lib.sh. (Sorry for the duplication, I found no simple way to import or share just this piece of functionality without adding much complexity.)
Successes, errors and broken summaries will now be highlighted in the appropriate colors (unless --no-color is given or output is not to a terminal), but only if the number is more than zero.
2012-01-26 12:15:26 +01:00
Ingo Karkat
ebe9fb868b test-lib: Show full todo.sh output in verbose mode.
Now that differences in the output (and exit code) are already printed by default, we can make the verbose mode actually "verbose" by including all todo.sh output generated during the test run. This may help in reviewing the tests and for troubleshooting.
By moving the redirection to the output file to test_run_(), all testing-related embellishments have been removed from the command under test itself, resulting in much cleaner test messages.
Additionally, also capture stderr in output. todo.sh itself currently prints everything to stdout (but the die() output probably belongs to stderr), so as of now, that has no consequences, but seems to be more consistent and future-proof.
2012-01-26 09:54:30 +01:00
Ingo Karkat
189779c6de test-lib: Separate function for exit code assertion.
Remove the check for the todo.sh exit code (scripted via "=== N") from the command under test into a separate assertion test_expect_code_and_output. This allows for reporting of expected vs. actual exit code (also in the default non-verbose mode), and unexpected output from the same test is now reported, too.
2012-01-26 09:54:25 +01:00
Ingo Karkat
516f806d58 test-lib: Always print output differences, not just in verbose mode.
Differences in the expected and actual todo.sh output are critical to analyzing the error, so they should always be printed, not just when the -verbose argument is given.

This refactoring moves the test_cmp call from the command under test (as seen from test-lib's perspective) into a new assertion test_expect_output, derived from test_expect_success.
2012-01-26 09:35:38 +01:00
Gina Trapani
9e38fa11ee Merge pull request #72 from inkarkat/list-optimization2
Optimization: Put grep -v empty task filter inside sed.
2012-01-24 11:28:53 -08:00
Gina Trapani
309b0f81b0 Merge pull request #71 from inkarkat/deduplicate-and-report
Deduplicate and report
2012-01-24 11:26:43 -08:00
Ingo Karkat
f3fc18af6b Optimization: Put grep -v empty task filter inside sed.
No need to spawn off another process for this; we can do this inside the sed command that joins the task numbers with the task text.

Note: The sed on OS X does not understand the \+ bound, only in the form of + when used with -E. Instead, I chose to fall back to the \{1,\} basic regexp, in the hope that it is very portable, and to avoid introducing extended regexps to the script.
2012-01-23 11:55:15 +01:00
Ingo Karkat
25e6d7ae24 FIX: Adapt deduplicate sed command for OS X.
The sed command of BSD / OS X doesn't like { command } blocks in a single expression. So move the (dynamic) deduplicate sed command(s) to the end, and use a label to access them. This also makes the entire sed script somewhat easier to understand.
2012-01-23 11:05:12 +01:00
Ingo Karkat
880d829e8e Create dedicated test file for report tests. 2012-01-21 14:35:04 +01:00
Ingo Karkat
7e525ee743 ENH: Only add new data to report.
When the last reported values are identical to the current values, do not append the same information (just with a new timestamp) to the report. Instead, just print the last report line.

With this, the report action can be scheduled periodically (e.g. via cron) without artificially inflating the report file.
2012-01-21 14:30:21 +01:00
Ingo Karkat
d46adadb1d Align report order with other actions.
Like "add" or "do", first the object, then (in verbose mode) the status message is printed.
2012-01-21 14:29:11 +01:00
Ingo Karkat
d0205b48a6 Use ISO 8601 timestamp format.
IMO 2012-01-14T14:49 looks better than the messy -*-*-*- of 2012-01-14-14:49, and is more aligned with standards.
2012-01-21 14:27:19 +01:00
Ingo Karkat
7a4d11812d Simplify writing of report line. 2012-01-21 14:27:19 +01:00
Ingo Karkat
ba66f66e86 Only print the added report line, not entire report.
I think this makes much more sense, especially once the report gets very long. (One can always use "cat" to view the entire report.)
2012-01-21 14:27:18 +01:00
Ingo Karkat
88ac3d87e6 Remove inactive broken report header.
This was meant to write a report header on the initial report run, but as it mistakenly used TODO_FILE instead of REPORT_FILE, it was inactive, and also missing in the tests. Let's just remove it; the format is simple, anyway.
2012-01-21 14:16:17 +01:00
Ingo Karkat
1a2af45b4d Pull archive() in-line and delegate via recursive call, also for report.
The report action should delegate to archive; it previously (half) did this via duplicated code (and forgot to defragment empty lines, so the tally could be off, and kept silent about the archiving).

The do action directly invoked archive(); if the user had extended / modified the archive action via an eponymous custom action, it would not run. Therefore, always invoke archive through another call of todo.sh, so that a possible custom action is considered.
2012-01-21 14:12:25 +01:00
Ingo Karkat
6424c4c1a0 Support "preserve line numbers" in deduplicate. 2012-01-21 14:12:25 +01:00
Ingo Karkat
fe5cdcb13a Fix deduplicate for non-printable (and non-ASCII) characters. 2012-01-21 14:12:25 +01:00
Ingo Karkat
bf2ca0ed6a Rework fixed archive deduplication into new deduplicate action.
As per discussion on the mailing list (http://tech.groups.yahoo.com/group/todotxt/message/3775), the automatic deduplication during archiving is unexpected and difficult to enforce in other implementations. Rather, make this a separate (optional) action.
2012-01-21 14:12:25 +01:00
Paul Roub
62d78a0034 explained the sed duplicate-removal pattern 2012-01-21 14:12:25 +01:00
Paul Roub
e33603939b Fix typo in duplicate removal on archive, per discussion at http://tech.groups.yahoo.com/group/todotxt/message/3775 2012-01-21 14:12:24 +01:00
Jean Jordaan
c99543506a Various helptext fixes
* 'Quotes optional.' doesn't make sense for 'addm', because without quotes, 'addm' is 'add'.
* There are no projects that don't start with a +.
* Don't tell people to type uppercase when the software takes care of it.
* Mention actions, mention filtering *out* terms.
* Fix link to wiki in the README
Closes #60
2012-01-20 11:46:30 -08:00
Ingo Karkat
b4aaba8387 Strip trailing whitespace when completing tasks. 2012-01-20 11:23:56 -08:00
Ingo Karkat
493e975199 Remove task date(s) when completing prioritized and done tasks.
Replace the primitive substitutions inside the completion function with a call to sed (instead of calling head), and supply more powerful substitutions there.
2012-01-20 11:23:56 -08:00
Ingo Karkat
fda31ea260 Avoid external TR when replacing , with space.
No need for an external tool, Bash can do a global literal substitution itself.
2012-01-20 11:17:37 -08:00
Ingo Karkat
8744167827 Add HIDE_CUSTOM_SUBSTITUTION for customization and add-ons.
Add-ons or users may want to hide parts of the task text from the output.
Though this can already be solved through TODOTXT_FINAL_FILTER, augmenting the configured value is not trivial, and it introduces another SED command into the already long _list() pipeline. Putting an additional HIDE_CUSTOM_SUBSTITUTION into the existing pipeline has hardly any performance implications, and makes the realization of this use case trivial.
2012-01-20 11:10:30 -08:00
Ingo Karkat
73e28b7225 BUG: SED error when HIDE_..._SUBSTITUTION contains whitespace.
This error only occurs when add-ons override either HIDE_PROJECTS_SUBSTITUTION or HIDE_CONTEXTS_SUBSTITUTION with a pattern that contains whitespace, not with the values used within todo.sh. But correcting the sloppy quoting doesn't hurt, neither.
2012-01-20 11:10:30 -08:00
Ingo Karkat
43bd1b645b Add hint for OR'ing TERMs to help text for ls/list.
The regexp syntax and quoting rules aren't known to many who are not well versed in the Bash shell, and difficult to get right even for people in the know. This question came up just recently on the mailing list, too.
2012-01-20 11:09:10 -08:00
Ingo Karkat
4db4494f03 Cosmetics: Correct double-spacing in help text. 2012-01-20 11:09:10 -08:00
Ingo Karkat
799840b664 Cosmetics: Consistently use "TERM(s)" in help text.
Two places only used "TERM" although multiple are supported. This can be misleading: do one or all have to match?
2012-01-20 11:09:10 -08:00
Ingo Karkat
54f15a7854 ENH: listall doesn't simply use concatenated task lists.
The simplistic "listall" action implementation just uses _list() on the concatenation of active and done task lists. This has the following shortcomings:

- Task numbers shown for archived tasks from done.txt are invalid.
- As the number of done tasks likely greatly outnumbers the number of active tasks, the task number padding is often larger than expected (e.g. 0005 instead of 05).
- Verbose output lists all tasks as originating from TODO, whereas it should differentiate between TODO: and DONE: sources.

The main challenge is to keep processing all tasks through a single pass of _list(), so that there is a single, unified sorting applied to all tasks. A custom AWK script sets all (originally invalid) task numbers from done.txt to "0", meaning "archived task".
The verbose message from _list() is replaced with a custom message that shows the tasks from todo.txt, done.txt, and totals.

Oh, and added tests for the previously untested "listall" action.
2012-01-20 11:07:28 -08:00
Ingo Karkat
17658c852d Reformatting: Lay out _list() file numbering in multi-line.
...like the following complex formatting and filtering steps.
2011-12-23 22:51:13 +01:00
Gina Trapani
f8a6e5f8d6 Merge pull request #59 from inkarkat/bug-pri-no-existence-check
BUG: pri doesn't issue an error when the task does not exist.
2011-12-18 21:50:18 -08:00
Ingo Karkat
388ae745af Refactoring: Extract getPrefix() for more consistent move error.
I think that the error on the "move dest src" action should be given like "SRC: No task 42" instead of "TODO: No task 42 in /path/to/src.txt", to be consistent with the addto and listfile actions. Extracted and exposed getPrefix(), again to remove a bit of duplication, and because this can be useful in custom add-ons, too.
2011-12-18 21:44:47 +01:00
Ingo Karkat
cb908bd454 Refactoring: Extract getTodo() and getNewtodo() functions.
The retrieval of a task text for $item and associated error handling so far was scattered around the individual actions. This is now consolidated in two new utility functions, which directly set $todo or $newtodo, respectively. (Inconsistent variable names like $NEWTODO have been adapted.) This ensures that all actions perform the same error checking, reduces a bit of duplication, and allows custom add-ons to benefit from these exported functions. Ah, and the error messages for the "move" action is now more in line with the other errors; unfortunately, this isn't yet covered by a test.

Note that the check whether $item is numeric must not use the +([0-9]) extglob any more, as such functions cannot be exported; a new Bash doesn't have the "shopt -s extglob" and complains with a syntax error. Fortunately, it is possible to perform the same check via standard Bash mechanisms.
2011-12-17 23:26:25 +01:00
Ingo Karkat
55679d136f BUG: pri doesn't issue error when task does not exist. 2011-12-17 21:29:18 +01:00
Gina Trapani
c0847b0b25 Merge pull request #58 from inkarkat/bug-quoting-negative-term
FIX: Correct quoting for negative -TERM filtering.
2011-12-07 09:31:28 -08:00
Gina Trapani
76fb1cb3ee Merge pull request #57 from inkarkat/filename-completion
Add file completion for addto, listfile, and move.
2011-12-07 09:21:17 -08:00
Gina Trapani
ac090fa30b Merge pull request #56 from inkarkat/todo_completion
Incorporate Bash completion from the Wiki page into the distribution.
2011-12-07 09:20:07 -08:00
Ingo Karkat
ea0e7c7b25 FIX: Correct quoting for negative -TERM filtering.
This oversight was recently introduced with the new filtercommand() in a0f39480bf.
I've enhanced the test to cover -TERM filtering, too.
2011-12-03 16:55:13 +01:00
Ingo Karkat
cd7d2f2fda ENH: Add file completion for move. 2011-11-20 17:07:59 +01:00
Ingo Karkat
395465b5f2 ENH: Add file completion for addto and listfile.
This enhancement to todo_completion requires a small enhancement to the listfile action: When no SRC is specified, the list of text files in the todo.txt directory is printed. This is probably also useful on its own, and better than the original behavior of printing "TODO: File  does not exist."

Note: I intentionally omitted bullet-proof error handling ($TODO_DIR non-existing or no text files contained), to avoid over-complicating this.
2011-11-20 16:30:31 +01:00
Ingo Karkat
d2e0f6e38d FIX: Support $HOME and $TODOTXT_ACTIONS_DIR containing spaces.
And document a minor discrepancy in action completion to todo.sh.
2011-11-20 15:17:50 +01:00
Ingo Karkat
b1d894f65d Add recently added "shorthelp" built-in command. 2011-11-20 15:17:50 +01:00
Ingo Karkat
f5270be463 ENH: Append task text as shell comment when completing task number.
This is useful for the paranoid before a destructive todo.txt operation. Appending the text as a shell comment doesn't affect the todo.txt command itself, but shows that the task number corresponds to the task you had in mind.
2011-11-20 15:17:45 +01:00
Ingo Karkat
8cba7533b9 Avoid leaking internal variables into the shell + robustness against add-ons.
Use "local" to avoid that the internal completion variables are accessible from the user's shell.
Use "todo.sh command" for the context/project lookups to avoid interference with custom add-ons of the same name, and reset TODOTXT_VERBOSE to avoid adding any message output (currently there is none).
2011-11-20 14:53:18 +01:00
Ingo Karkat
285d5039f2 Add shebang line and check for Bash.
The shebang is ignored when sourcing the script (but still helps many text editors auto-detect the file type), and will cause an error when the script is mistakenly executed.
The Bash check allows to have this called from a generic place (e.g. .profile), and do no harm when under a different shell.
2011-11-20 14:26:52 +01:00
Ingo Karkat
9b67a76833 Do not automatically complete for guessed aliases.
Don't infringe against the principle of least astonishment (they user may have completely unrelated aliases). Rather, if the user sets up his own alias, make him apply the same to todo_completion.
2011-11-20 14:22:48 +01:00
Ingo Karkat
2b0921f4ec Add todo_completion to todo.txt distribution. 2011-11-20 14:20:25 +01:00
Ingo Karkat
202f892e85 Include todo Bash completion from the todo.txt Wiki.
This is f32aba2 of https://github.com/ginatrapani/todo.txt-cli/wiki/Tips-and-Tricks, last edited 30-Oct-2011.
2011-11-20 14:17:22 +01:00
Ingo Karkat
417e86ae7a FIX: Handle special shell characters when filtering TERM.
The double quotes used in the filter_command erroneously expand $VARIABLE, and due to missing quoting in the eval() of filter_command, multiple spaces are condensed into a single space.
Introduce a new function shellquote() to correctly quote each filter TERM.
2011-11-10 14:21:47 -08:00
Ingo Karkat
cb982391de Implement listproj with factored out filtercommand() function.
_list() is way too large and monolithic for many (re-)use cases. As a first step, factor out the building of the filter_command and reuse that for the listproj filtering.
Enhance the listproj test with special cases that show the problems with the previous implementation directly using _list: Option -+, custom final filters, and non-ANSI colors cause it to break.
2011-11-10 14:21:47 -08:00
jmoore
b8244792cc Accept filters for lsprj 2011-11-10 14:21:47 -08:00
Ingo Karkat
a433c5828e shorthelp consistency
- Restore alphabetical order for "addto".
- Keep "depri" shorthand after the long form as all other.
Closes #50

Implement shorthelp listing add-on action one-line usage.
-h and an additional new "shorthelp" action list not just the usage of the built-in actions, but also from add-ons. This assumes that add-ons use the same usage indentation structure as todo.sh. (They should, anyway, for consistency of the full help message.)
Closes #12
2011-11-10 14:16:05 -08:00
Ingo Karkat
7742d3c115 Restructure help text to put built-in and add-on actions together.
I find it annoying that the built-in actions are printed first, and the add-on actions last, although for the user, the distinction is hardly important. Therefore, moving the "options" block first (as it is short and contains the stuff most difficult to memorize), then built-in, then add-on actions.
As environment variables are hardly used in day-to-day operations, only once for customizing the config, they are now omitted by default and only included when -vv is given.
2011-11-10 14:11:31 -08:00
Gina Trapani
7ca2b4c687 Merge pull request #49 from inkarkat/bug-disable-filter-not-propagating
BUG: disable filter not propagating to add-ons
2011-11-10 13:33:46 -08:00
Gina Trapani
239f7234ab Merge pull request #48 from inkarkat/bug-unrestrained-pri-matching
BUG: unrestrained pri "(X)" matching
2011-11-10 13:33:28 -08:00
Ingo Karkat
65d39319f1 TODOTXT_DISABLE_FILTER missing from help text.
Should be in there for consistency; corresponding option is -x.
2011-09-30 09:15:56 +02:00
Ingo Karkat
62d3ff2977 BUG: Option -x isn't propagated to custom actions.
Somehow, no default is set for TODOTXT_DISABLE_FILTER, so that it isn't exported, and therefore does not apply to the sourced actions, so you cannot disable filtering for "myaction" via todo.sh -x myaction.
2011-09-30 09:11:54 +02:00
18 changed files with 1158 additions and 249 deletions

View File

@@ -13,7 +13,7 @@ VERSION-FILE: .FORCE-VERSION-FILE
todo.sh: VERSION-FILE
# For packaging
DISTFILES := todo.cfg
DISTFILES := todo.cfg todo_completion
DISTNAME=todo.txt_cli-$(VERSION)
dist: $(DISTFILES) todo.sh

View File

@@ -21,6 +21,7 @@ h2. Quick Links
* Original anemic release by "Gina Trapani":http://ginatrapani.org on 5/11/2006.
* Raised to great heights by "brainy and dedicated volunteers":http://github.com/ginatrapani/todo.txt-cli/network.
* Licensed under the "GPL":http://www.gnu.org/copyleft/gpl.html
* "Add-on Directory":http://wiki.github.com/ginatrapani/todo.txt-cli/todosh-add-on-directory
* "Add-on Directory":https://github.com/ginatrapani/todo.txt-cli/wiki/Todo.sh-Add-on-Directory
https://github.com/ginatrapani/todo.txt-cli/wiki/Creating-and-Installing-Add-ons
* "Changelog":http://wiki.github.com/ginatrapani/todo.txt-cli/todosh-changelog
* "Known Bugs":http://github.com/ginatrapani/todo.txt-cli/issues

View File

@@ -1,4 +1,57 @@
#!/bin/sh
#!/bin/bash
[ "x$TERM" != "xdumb" ] && (
export TERM &&
[ -t 1 ] &&
tput bold >/dev/null 2>&1 &&
tput setaf 1 >/dev/null 2>&1 &&
tput sgr0 >/dev/null 2>&1
) &&
color=t
case "$1" in
--no-color)
color=; shift ;;
esac
if test -n "$color"; then
say_color () {
(
export TERM
case "$1" in
error) tput bold; tput setaf 1;; # bold red
skip) tput bold; tput setaf 2;; # bold green
pass) tput setaf 2;; # green
info) tput setaf 3;; # brown
*) test -n "$quiet" && return;;
esac
shift
printf "* %s" "$*"
tput sgr0
echo
)
}
else
say_color() {
test -z "$1" && test -n "$quiet" && return
shift
echo "* $*"
}
fi
get_color()
{
# Only use the supplied color if there are actually instances of that
# type, so that a clean test run does not distract the user by the
# appearance of the error highlighting.
if [ ${1:?} -eq 0 ]
then
echo 'info'
else
echo "${2:-info}"
fi
}
fixed=0
success=0
@@ -27,8 +80,8 @@ do
done <"$file"
done
printf "%-8s%d\n" fixed $fixed
printf "%-8s%d\n" success $success
printf "%-8s%d\n" failed $failed
printf "%-8s%d\n" broken $broken
printf "%-8s%d\n" total $total
say_color 'info' "$(printf "%-8s%d\n" fixed $fixed)"
say_color "$(get_color "$success" 'pass')" "$(printf "%-8s%d\n" success $success)"
say_color "$(get_color "$failed" 'error')" "$(printf "%-8s%d\n" failed $failed)"
say_color "$(get_color "$broken" 'error')" "$(printf "%-8s%d\n" broken $broken)"
say_color 'info' "$(printf "%-8s%d\n" total $total)"

View File

@@ -45,6 +45,8 @@ test_expect_success 'null listpri a' '
cat > expect <<EOF
--
TODO: 0 of 0 tasks shown
DONE: 0 of 0 tasks shown
total 0 of 0 tasks shown
EOF
test_expect_success 'null lsa' '

View File

@@ -39,6 +39,24 @@ GARDEN: 2 added.
GARDEN: 2 of 2 tasks shown
EOF
#
# List available files
#
test_todo_session 'list available files' <<EOF
>>> todo.sh listfile
Files in the todo.txt directory:
done.txt
garden.txt
report.txt
todo.txt
>>> TODOTXT_VERBOSE=0 todo.sh listfile
done.txt
garden.txt
report.txt
todo.txt
EOF
#
# Filter
#

View File

@@ -66,6 +66,12 @@ TODO: 4 added.
TODO: 4 of 4 tasks shown
EOF
test_todo_session 'priority error' <<EOF
>>> todo.sh pri 10 B
=== 1
TODO: No task 10.
EOF
cat > todo.txt <<EOF
(B) smell the uppercase Roses +flowers @outside
(C) notice the sunflowers

View File

@@ -61,6 +61,42 @@ TODO: 0 of 5 tasks shown
TODO: 0 of 5 tasks shown
EOF
cat > todo.txt <<EOF
(B) smell the uppercase Roses +flowers @outside
(X) clean the house from A-Z
(C) notice the sunflowers
(X) listen to music
buy more records from artists A-Z
EOF
test_todo_session 'listpri filtering priority ranges' <<EOF
>>> todo.sh -p listpri a-c
1 (B) smell the uppercase Roses +flowers @outside
3 (C) notice the sunflowers
--
TODO: 2 of 5 tasks shown
>>> todo.sh -p listpri c-Z
3 (C) notice the sunflowers
2 (X) clean the house from A-Z
4 (X) listen to music
--
TODO: 3 of 5 tasks shown
>>> todo.sh -p listpri A-
2 (X) clean the house from A-Z
--
TODO: 1 of 5 tasks shown
>>> todo.sh -p listpri A-C A-Z
--
TODO: 0 of 5 tasks shown
>>> todo.sh -p listpri X A-Z
2 (X) clean the house from A-Z
--
TODO: 1 of 5 tasks shown
EOF
cat > todo.txt <<EOF
(B) ccc xxx this line should be third.
ccc xxx this line should be third.

42
tests/t1260-listprj.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/sh
test_description='list project functionality
'
. ./test-lib.sh
cat > todo.txt <<EOF
(B) smell the uppercase Roses +roses @outside +shared
(C) notice the sunflowers +sunflowers @garden +shared +landscape
stop
EOF
test_todo_session 'basic listproj' <<EOF
>>> todo.sh listproj
+landscape
+roses
+shared
+sunflowers
EOF
test_todo_session 'listproj with context' <<EOF
>>> todo.sh listproj @garden
+landscape
+shared
+sunflowers
EOF
TEST_TODO_CUSTOM=todo-custom.cfg
cat todo.cfg > "$TEST_TODO_CUSTOM"
cat >> "$TEST_TODO_CUSTOM" <<'EOF'
export DEFAULT='</color>'
export PRI_B='<color type=green>'
export PRI_C='<color type=blue>'
export TODOTXT_FINAL_FILTER='grep -i roses'
EOF
test_todo_session 'listproj with context special cases' <<EOF
>>> todo.sh -+ -d "$TEST_TODO_CUSTOM" listproj @garden
+landscape
+shared
+sunflowers
EOF
test_done

View File

@@ -53,6 +53,18 @@ test_todo_session 'checking TODOTXT_FINAL_FILTER' <<EOF
TODO: 3 of 3 tasks shown
EOF
#
# check the custom hiding
#
test_todo_session 'checking HIDE_CUSTOM_SUBSTITUTION' <<EOF
>>> HIDE_CUSTOM_SUBSTITUTION='[tT]h' todo.sh ls
2 aaa zzz is line should be first.
3 bbb yyy is line should be second.
1 ccc xxx is line should be ird.
--
TODO: 3 of 3 tasks shown
EOF
#
# check the filtering of TERM
#
@@ -73,6 +85,29 @@ TODO: 1 of 3 tasks shown
TODO: 1 of 3 tasks shown
EOF
#
# check negative filtering via -TERM
#
test_todo_session 'checking negative filtering via -TERM' <<EOF
>>> todo.sh ls -second
2 aaa zzz this line should be first.
1 ccc xxx this line should be third.
--
TODO: 2 of 3 tasks shown
>>> todo.sh ls "-should be f"
3 bbb yyy this line should be second.
1 ccc xxx this line should be third.
--
TODO: 2 of 3 tasks shown
>>> todo.sh ls "- zzz"
3 bbb yyy this line should be second.
1 ccc xxx this line should be third.
--
TODO: 2 of 3 tasks shown
EOF
#
# check the filtering of TERM with regexp
#
@@ -94,6 +129,40 @@ TODO: 1 of 3 tasks shown
TODO: 1 of 3 tasks shown
EOF
#
# check the filtering of TERM containing characters that are special to the
# shell, like variables, quotes, and multiple subsequent spaces.
#
cat > todo.txt <<'EOF'
earn some pennies
earn some $$
earn some "money"
get money from O'Brian
just get money!
EOF
test_todo_session 'checking filtering of special characters' <<'EOF'
>>> todo.sh ls '$$'
2 earn some $$
--
TODO: 1 of 5 tasks shown
>>> todo.sh ls '"money"'
3 earn some "money"
--
TODO: 1 of 5 tasks shown
>>> todo.sh ls "O'Brian"
4 get money from O'Brian
--
TODO: 1 of 5 tasks shown
>>> todo.sh ls "get money"
5 just get money!
--
TODO: 1 of 5 tasks shown
EOF
#
# check the x command line option
#

150
tests/t1350-listall.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/sh
test_description='listall functionality
'
. ./test-lib.sh
cat > todo.txt <<EOF
smell the uppercase Roses +flowers @outside
x 2011-08-08 tend the garden @outside
notice the sunflowers
x 2011-12-26 go outside +wakeup
(A) stop
EOF
cat > done.txt <<EOF
x 2011-12-01 eat breakfast
x 2011-12-05 smell the coffee +wakeup
EOF
test_todo_session 'basic listall' <<EOF
>>> todo.sh -p listall
5 (A) stop
3 notice the sunflowers
1 smell the uppercase Roses +flowers @outside
2 x 2011-08-08 tend the garden @outside
0 x 2011-12-01 eat breakfast
0 x 2011-12-05 smell the coffee +wakeup
4 x 2011-12-26 go outside +wakeup
--
TODO: 5 of 5 tasks shown
DONE: 2 of 2 tasks shown
total 7 of 7 tasks shown
EOF
test_todo_session 'listall highlighting' <<EOF
>>> todo.sh listall
5 (A) stop
3 notice the sunflowers
1 smell the uppercase Roses +flowers @outside
2 x 2011-08-08 tend the garden @outside
0 x 2011-12-01 eat breakfast
0 x 2011-12-05 smell the coffee +wakeup
4 x 2011-12-26 go outside +wakeup
--
TODO: 5 of 5 tasks shown
DONE: 2 of 2 tasks shown
total 7 of 7 tasks shown
EOF
test_todo_session 'listall nonverbose' <<EOF
>>> TODOTXT_VERBOSE=0 todo.sh -p listall
5 (A) stop
3 notice the sunflowers
1 smell the uppercase Roses +flowers @outside
2 x 2011-08-08 tend the garden @outside
0 x 2011-12-01 eat breakfast
0 x 2011-12-05 smell the coffee +wakeup
4 x 2011-12-26 go outside +wakeup
EOF
test_todo_session 'listall filtering' <<EOF
>>> todo.sh -p listall @outside
1 smell the uppercase Roses +flowers @outside
2 x 2011-08-08 tend the garden @outside
--
TODO: 2 of 5 tasks shown
DONE: 0 of 2 tasks shown
total 2 of 7 tasks shown
>>> todo.sh -p listall the
3 notice the sunflowers
1 smell the uppercase Roses +flowers @outside
2 x 2011-08-08 tend the garden @outside
0 x 2011-12-05 smell the coffee +wakeup
--
TODO: 3 of 5 tasks shown
DONE: 1 of 2 tasks shown
total 4 of 7 tasks shown
>>> todo.sh -p listall breakfast
0 x 2011-12-01 eat breakfast
--
TODO: 0 of 5 tasks shown
DONE: 1 of 2 tasks shown
total 1 of 7 tasks shown
>>> todo.sh -p listall doesnotmatch
--
TODO: 0 of 5 tasks shown
DONE: 0 of 2 tasks shown
total 0 of 7 tasks shown
EOF
cat >> done.txt <<EOF
x 2010-01-01 old task 1
x 2010-01-01 old task 2
x 2010-01-01 old task 3
x 2010-01-01 old task 4
EOF
test_todo_session 'listall number width' <<EOF
>>> todo.sh -p listall
5 (A) stop
3 notice the sunflowers
1 smell the uppercase Roses +flowers @outside
0 x 2010-01-01 old task 1
0 x 2010-01-01 old task 2
0 x 2010-01-01 old task 3
0 x 2010-01-01 old task 4
2 x 2011-08-08 tend the garden @outside
0 x 2011-12-01 eat breakfast
0 x 2011-12-05 smell the coffee +wakeup
4 x 2011-12-26 go outside +wakeup
--
TODO: 5 of 5 tasks shown
DONE: 6 of 6 tasks shown
total 11 of 11 tasks shown
>>> TODOTXT_VERBOSE=0 todo.sh add new task 1
>>> TODOTXT_VERBOSE=0 todo.sh add new task 2
>>> TODOTXT_VERBOSE=0 todo.sh add new task 3
>>> TODOTXT_VERBOSE=0 todo.sh add new task 4
>>> TODOTXT_VERBOSE=0 todo.sh add new task 5
>>> todo.sh -p listall
05 (A) stop
06 new task 1
07 new task 2
08 new task 3
09 new task 4
10 new task 5
03 notice the sunflowers
01 smell the uppercase Roses +flowers @outside
00 x 2010-01-01 old task 1
00 x 2010-01-01 old task 2
00 x 2010-01-01 old task 3
00 x 2010-01-01 old task 4
02 x 2011-08-08 tend the garden @outside
00 x 2011-12-01 eat breakfast
00 x 2011-12-05 smell the coffee +wakeup
04 x 2011-12-26 go outside +wakeup
--
TODO: 10 of 10 tasks shown
DONE: 6 of 6 tasks shown
total 16 of 16 tasks shown
EOF
test_done

35
tests/t1900-archive.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/sh
test_description='archive functionality
Ensure we can archive items successfully.
'
. ./test-lib.sh
cat > todo.txt <<EOF
one
two
three
one
x done
four
EOF
test_todo_session 'archive with duplicates' <<EOF
>>> todo.sh archive
x done
TODO: $HOME/todo.txt archived.
EOF
test_todo_session 'list after archive' <<EOF
>>> todo.sh ls
5 four
1 one
4 one
3 three
2 two
--
TODO: 5 of 5 tasks shown
EOF
test_done

103
tests/t1910-deduplicate.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/sh
test_description='deduplicate functionality
Ensure we can deduplicate items successfully.
'
. ./test-lib.sh
cat > todo.txt <<EOF
duplicated
two
x done
duplicated
double task
double task
three
EOF
test_todo_session 'deduplicate and preserve line numbers' <<EOF
>>> todo.sh deduplicate
TODO: 2 duplicate task(s) removed
>>> todo.sh -p ls
5 double task
1 duplicated
7 three
2 two
3 x done
--
TODO: 5 of 5 tasks shown
EOF
test_todo_session 'deduplicate without duplicates' <<EOF
>>> todo.sh deduplicate
TODO: No duplicate tasks found
EOF
cat > todo.txt <<EOF
duplicated
two
x done
duplicated
double task
double task
three
EOF
test_todo_session 'deduplicate and delete lines' <<EOF
>>> todo.sh -n deduplicate
TODO: 2 duplicate task(s) removed
>>> todo.sh -p ls
4 double task
1 duplicated
5 three
2 two
3 x done
--
TODO: 5 of 5 tasks shown
EOF
cat > todo.txt <<EOF
one
duplicated
three
duplicated
duplicated
six
duplicated
EOF
test_todo_session 'deduplicate more than two occurrences' <<EOF
>>> todo.sh deduplicate
TODO: 3 duplicate task(s) removed
>>> todo.sh -p ls
2 duplicated
1 one
6 six
3 three
--
TODO: 4 of 4 tasks shown
EOF
cat > todo.txt <<EOF
normal task
a bold task
something else
a bold task
something more
EOF
test_todo_session 'deduplicate with non-printable duplicates' <<EOF
>>> todo.sh deduplicate
TODO: 1 duplicate task(s) removed
>>> todo.sh -p ls
2 a bold task
1 normal task
3 something else
5 something more
--
TODO: 4 of 4 tasks shown
EOF
test_done

96
tests/t1950-report.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/bin/sh
test_description='report functionality
This test checks the reporting and the format of the report file.
'
. ./test-lib.sh
cat > todo.txt <<EOF
(B) smell the uppercase Roses +flowers @outside
stop and think
smell the coffee +wakeup
make the coffee +wakeup
visit http://example.com
EOF
test_todo_session 'create new report' <<EOF
>>> todo.sh report
TODO: $HOME/todo.txt archived.
2009-02-13T04:40:00 5 0
TODO: Report file updated.
>>> todo.sh -p list
1 (B) smell the uppercase Roses +flowers @outside
4 make the coffee +wakeup
3 smell the coffee +wakeup
2 stop and think
5 visit http://example.com
--
TODO: 5 of 5 tasks shown
EOF
test_todo_session 'report of done tasks' <<EOF
>>> todo.sh -A do 3
3 x 2009-02-13 smell the coffee +wakeup
TODO: 3 marked as done.
x 2009-02-13 smell the coffee +wakeup
TODO: $HOME/todo.txt archived.
>>> todo.sh report
TODO: $HOME/todo.txt archived.
2009-02-13T04:40:00 4 1
TODO: Report file updated.
>>> todo.sh -p list
1 (B) smell the uppercase Roses +flowers @outside
3 make the coffee +wakeup
2 stop and think
4 visit http://example.com
--
TODO: 4 of 4 tasks shown
EOF
test_todo_session 'report performs archiving' <<EOF
>>> todo.sh -a do 3
3 x 2009-02-13 make the coffee +wakeup
TODO: 3 marked as done.
>>> todo.sh report
x 2009-02-13 make the coffee +wakeup
TODO: $HOME/todo.txt archived.
2009-02-13T04:40:00 3 2
TODO: Report file updated.
>>> todo.sh -p list
1 (B) smell the uppercase Roses +flowers @outside
2 stop and think
3 visit http://example.com
--
TODO: 3 of 3 tasks shown
>>> todo.sh -p listfile done.txt
2 x 2009-02-13 make the coffee +wakeup
1 x 2009-02-13 smell the coffee +wakeup
--
DONE: 2 of 2 tasks shown
EOF
test_todo_session 'report is unchanged when no changes' <<EOF
>>> cat report.txt
2009-02-13T04:40:00 5 0
2009-02-13T04:40:00 4 1
2009-02-13T04:40:00 3 2
>>> todo.sh report
TODO: $HOME/todo.txt archived.
2009-02-13T04:40:00 3 2
TODO: Report file is up-to-date.
>>> cat report.txt
2009-02-13T04:40:00 5 0
2009-02-13T04:40:00 4 1
2009-02-13T04:40:00 3 2
EOF
test_done

View File

@@ -85,13 +85,9 @@ TODO: $HOME/todo.txt archived.
TODO: 5 of 5 tasks shown
>>> todo.sh report
TODO: $HOME/todo.txt archived.
2009-02-13T04:40:00 5 1
TODO: Report file updated.
2009-02-13-04:40:00 5 1
>>> todo.sh report
TODO: Report file updated.
2009-02-13-04:40:00 5 1
2009-02-13-04:40:00 5 1
>>> todo.sh append g a
usage: todo.sh append ITEM# "TEXT TO APPEND"

View File

@@ -180,7 +180,7 @@ test_failure_ () {
test_failure=$(($test_failure + 1))
say_color error "FAIL $test_count: $1"
shift
echo "$@" | sed -e 's/^/ /'
echo "$@"
test "$immediate" = "" || { trap - EXIT; exit 1; }
}
@@ -199,8 +199,9 @@ test_debug () {
}
test_run_ () {
eval >&3 2>&4 "$1"
eval > output 2>&1 "$1"
eval_ret="$?"
cat >&3 output
return 0
}
@@ -260,6 +261,57 @@ test_expect_success () {
echo >&3 ""
}
test_expect_output () {
test "$#" = 2 ||
error "bug in the test script: not 2 parameters to test-expect-output"
if ! test_skip "$@"
then
say >&3 "expecting success and output: $2"
test_run_ "$2"
if [ "$?" = 0 -a "$eval_ret" = 0 ]
then
cmp_output=$(test_cmp expect output)
if [ "$?" = 0 ]
then
test_ok_ "$1"
else
test_failure_ "$@" "
$cmp_output"
fi
else
test_failure_ "$@"
fi
fi
echo >&3 ""
}
test_expect_code_and_output () {
test "$#" = 3 ||
error "bug in the test script: not 3 parameters to test-expect-code-and-output"
if ! test_skip "$@"
then
say >&3 "expecting exit code $1 and output: $3"
test_run_ "$3"
if [ "$?" = 0 -a "$eval_ret" = "$1" ]
then
cmp_output=$(test_cmp expect output)
if [ "$?" = 0 ]
then
test_ok_ "$2"
else
test_failure_ "$2" "$3" "
$cmp_output"
fi
else
cmp_output=$(test_cmp expect output)
test_failure_ "$2" "$3" "
* expected exit code $1, actual ${eval_ret}${cmp_output:+
}${cmp_output}"
fi
fi
echo >&3 ""
}
test_expect_code () {
test "$#" = 3 ||
error "bug in the test script: not 3 parameters to test-expect-code"
@@ -271,7 +323,8 @@ test_expect_code () {
then
test_ok_ "$2"
else
test_failure_ "$@"
test_failure_ "$2" "$3" "
* expected exit code $1, actual ${eval_ret}"
fi
fi
echo >&3 ""
@@ -542,9 +595,9 @@ test_todo_session () {
"")
if [ ! -z "$cmd" ]; then
if [ $status = 0 ]; then
test_expect_success "$1 $subnum" "$cmd > output && test_cmp expect output"
test_expect_output "$1 $subnum" "$cmd"
else
test_expect_success "$1 $subnum" "$cmd > output ; test \$? = $status && test_cmp expect output"
test_expect_code_and_output "$status" "$1 $subnum" "$cmd"
fi
subnum=$(($subnum + 1))
@@ -560,9 +613,9 @@ test_todo_session () {
done
if [ ! -z "$cmd" ]; then
if [ $status = 0 ]; then
test_expect_success "$1 $subnum" "$cmd > output && test_cmp expect output"
test_expect_output "$1 $subnum" "$cmd"
else
test_expect_success "$1 $subnum" "$cmd > output ; test \$? = $status && test_cmp expect output"
test_expect_code_and_output "$status" "$1 $subnum" "$cmd"
fi
fi
}

View File

@@ -8,7 +8,6 @@ export TODO_DIR=`dirname "$0"`
export TODO_FILE="$TODO_DIR/todo.txt"
export DONE_FILE="$TODO_DIR/done.txt"
export REPORT_FILE="$TODO_DIR/report.txt"
export TMP_FILE="$TODO_DIR/todo.tmp"
# You can customize your actions directory location
#export TODO_ACTIONS_DIR="$HOME/.todo.actions.d"

679
todo.sh
View File

@@ -44,141 +44,50 @@ shorthelp()
Actions:
add|a "THING I NEED TO DO +project @context"
addto DEST "TEXT TO ADD"
addm "THINGS I NEED TO DO
MORE THINGS I NEED TO DO"
addto DEST "TEXT TO ADD"
append|app ITEM# "TEXT TO APPEND"
archive
command [ACTIONS]
deduplicate
del|rm ITEM# [TERM]
dp|depri ITEM#[, ITEM#, ITEM#, ...]
depri|dp ITEM#[, ITEM#, ITEM#, ...]
do ITEM#[, ITEM#, ITEM#, ...]
help
list|ls [TERM...]
listall|lsa [TERM...]
listcon|lsc
listfile|lf SRC [TERM...]
listpri|lsp [PRIORITY] [TERM...]
listproj|lsprj
listfile|lf [SRC [TERM...]]
listpri|lsp [PRIORITIES] [TERM...]
listproj|lsprj [TERM...]
move|mv ITEM# DEST [SRC]
prepend|prep ITEM# "TEXT TO PREPEND"
pri|p ITEM# PRIORITY
replace ITEM# "UPDATED TODO"
report
shorthelp
Actions can be added and overridden using scripts in the actions
directory.
EndHelp
# Only list the one-line usage from the add-on actions. This assumes that
# add-ons use the same usage indentation structure as todo.sh.
addonHelp | grep -e '^ Add-on Actions:' -e '^ [[:alpha:]]'
cat <<-EndHelpFooter
See "help" for more details.
EndHelp
EndHelpFooter
exit 0
}
help()
{
cat <<-EndHelp
cat <<-EndOptionsHelp
Usage: $oneline_usage
Actions:
add "THING I NEED TO DO +project @context"
a "THING I NEED TO DO +project @context"
Adds THING I NEED TO DO to your todo.txt file on its own line.
Project and context notation optional.
Quotes optional.
addm "FIRST THING I NEED TO DO +project1 @context
SECOND THING I NEED TO DO +project2 @context"
Adds FIRST THING I NEED TO DO to your todo.txt on its own line and
Adds SECOND THING I NEED TO DO to you todo.txt on its own line.
Project and context notation optional.
Quotes optional.
addto DEST "TEXT TO ADD"
Adds a line of text to any file located in the todo.txt directory.
For example, addto inbox.txt "decide about vacation"
append ITEM# "TEXT TO APPEND"
app ITEM# "TEXT TO APPEND"
Adds TEXT TO APPEND to the end of the task on line ITEM#.
Quotes optional.
archive
Moves all done tasks from todo.txt to done.txt and removes blank lines.
command [ACTIONS]
Runs the remaining arguments using only todo.sh builtins.
Will not call any .todo.actions.d scripts.
del ITEM# [TERM]
rm ITEM# [TERM]
Deletes the task on line ITEM# in todo.txt.
If TERM specified, deletes only TERM from the task.
depri ITEM#[, ITEM#, ITEM#, ...]
dp ITEM#[, ITEM#, ITEM#, ...]
Deprioritizes (removes the priority) from the task(s)
on line ITEM# in todo.txt.
do ITEM#[, ITEM#, ITEM#, ...]
Marks task(s) on line ITEM# as done in todo.txt.
help
Display this help message.
list [TERM...]
ls [TERM...]
Displays all tasks that contain TERM(s) sorted by priority with line
numbers. If no TERM specified, lists entire todo.txt.
listall [TERM...]
lsa [TERM...]
Displays all the lines in todo.txt AND done.txt that contain TERM(s)
sorted by priority with line numbers. If no TERM specified, lists
entire todo.txt AND done.txt concatenated and sorted.
listcon
lsc
Lists all the task contexts that start with the @ sign in todo.txt.
listfile SRC [TERM...]
lf SRC [TERM...]
Displays all the lines in SRC file located in the todo.txt directory,
sorted by priority with line numbers. If TERM specified, lists
all lines that contain TERM in SRC file.
listpri [PRIORITY] [TERM...]
lsp [PRIORITY] [TERM...]
Displays all tasks prioritized PRIORITY.
If no PRIORITY specified, lists all prioritized tasks.
If TERM specified, lists only prioritized tasks that contain TERM.
listproj
lsprj
Lists all the projects that start with the + sign in todo.txt.
move ITEM# DEST [SRC]
mv ITEM# DEST [SRC]
Moves a line from source text file (SRC) to destination text file (DEST).
Both source and destination file must be located in the directory defined
in the configuration directory. When SRC is not defined
it's by default todo.txt.
prepend ITEM# "TEXT TO PREPEND"
prep ITEM# "TEXT TO PREPEND"
Adds TEXT TO PREPEND to the beginning of the task on line ITEM#.
Quotes optional.
pri ITEM# PRIORITY
p ITEM# PRIORITY
Adds PRIORITY to task on line ITEM#. If the task is already
prioritized, replaces current priority with new PRIORITY.
PRIORITY must be an uppercase letter between A and Z.
replace ITEM# "UPDATED TODO"
Replaces task on line ITEM# with UPDATED TODO.
report
Adds the number of open tasks and done tasks to report.txt.
Options:
-@
Hide context names in list output. Use twice to show context
@@ -193,7 +102,7 @@ help()
-f
Forces actions without confirmation or interactive input
-h
Display a short help message
Display a short help message; same as action "shorthelp"
-p
Plain mode turns off colors
-P
@@ -217,13 +126,17 @@ help()
-v
Verbose mode turns on confirmation messages
-vv
Extra verbose mode prints some debugging information
Extra verbose mode prints some debugging information and
additional help text
-V
Displays version, license and credits
-x
Disables TODOTXT_FINAL_FILTER
EndOptionsHelp
[ $TODOTXT_VERBOSE -gt 1 ] && cat <<-EndVerboseHelp
Environment variables:
TODOTXT_AUTO_ARCHIVE is same as option -a (0)/-A (1)
TODOTXT_CFG_FILE=CONFIG_FILE is same as option -d CONFIG_FILE
@@ -232,26 +145,156 @@ help()
TODOTXT_PLAIN is same as option -p (1)/-c (0)
TODOTXT_DATE_ON_ADD is same as option -t (1)/-T (0)
TODOTXT_VERBOSE=1 is same as option -v
TODOTXT_DISABLE_FILTER=1 is same as option -x
TODOTXT_DEFAULT_ACTION="" run this when called with no arguments
TODOTXT_SORT_COMMAND="sort ..." customize list output
TODOTXT_FINAL_FILTER="sed ..." customize list after color, P@+ hiding
EndHelp
if [ -d "$TODO_ACTIONS_DIR" ]
then
echo ""
EndVerboseHelp
cat <<-EndActionsHelp
Built-in Actions:
add "THING I NEED TO DO +project @context"
a "THING I NEED TO DO +project @context"
Adds THING I NEED TO DO to your todo.txt file on its own line.
Project and context notation optional.
Quotes optional.
addm "FIRST THING I NEED TO DO +project1 @context
SECOND THING I NEED TO DO +project2 @context"
Adds FIRST THING I NEED TO DO to your todo.txt on its own line and
Adds SECOND THING I NEED TO DO to you todo.txt on its own line.
Project and context notation optional.
addto DEST "TEXT TO ADD"
Adds a line of text to any file located in the todo.txt directory.
For example, addto inbox.txt "decide about vacation"
append ITEM# "TEXT TO APPEND"
app ITEM# "TEXT TO APPEND"
Adds TEXT TO APPEND to the end of the task on line ITEM#.
Quotes optional.
archive
Moves all done tasks from todo.txt to done.txt and removes blank lines.
command [ACTIONS]
Runs the remaining arguments using only todo.sh builtins.
Will not call any .todo.actions.d scripts.
deduplicate
Removes duplicate lines from todo.txt.
del ITEM# [TERM]
rm ITEM# [TERM]
Deletes the task on line ITEM# in todo.txt.
If TERM specified, deletes only TERM from the task.
depri ITEM#[, ITEM#, ITEM#, ...]
dp ITEM#[, ITEM#, ITEM#, ...]
Deprioritizes (removes the priority) from the task(s)
on line ITEM# in todo.txt.
do ITEM#[, ITEM#, ITEM#, ...]
Marks task(s) on line ITEM# as done in todo.txt.
help
Display this help message.
list [TERM...]
ls [TERM...]
Displays all tasks that contain TERM(s) sorted by priority with line
numbers. Each task must match all TERM(s) (logical AND); to display
tasks that contain any TERM (logical OR), use
"TERM1\|TERM2\|..." (with quotes), or TERM1\\\|TERM2 (unquoted).
Hides all tasks that contain TERM(s) preceded by a
minus sign (i.e. -TERM). If no TERM specified, lists entire todo.txt.
listall [TERM...]
lsa [TERM...]
Displays all the lines in todo.txt AND done.txt that contain TERM(s)
sorted by priority with line numbers. Hides all tasks that
contain TERM(s) preceded by a minus sign (i.e. -TERM). If no
TERM specified, lists entire todo.txt AND done.txt
concatenated and sorted.
listcon
lsc
Lists all the task contexts that start with the @ sign in todo.txt.
listfile [SRC [TERM...]]
lf [SRC [TERM...]]
Displays all the lines in SRC file located in the todo.txt directory,
sorted by priority with line numbers. If TERM specified, lists
all lines that contain TERM(s) in SRC file. Hides all tasks that
contain TERM(s) preceded by a minus sign (i.e. -TERM).
Without any arguments, the names of all text files in the todo.txt
directory are listed.
listpri [PRIORITIES] [TERM...]
lsp [PRIORITIES] [TERM...]
Displays all tasks prioritized PRIORITIES.
PRIORITIES can be a single one (A) or a range (A-C).
If no PRIORITIES specified, lists all prioritized tasks.
If TERM specified, lists only prioritized tasks that contain TERM(s).
Hides all tasks that contain TERM(s) preceded by a minus sign
(i.e. -TERM).
listproj
lsprj
Lists all the projects (terms that start with a + sign) in
todo.txt.
move ITEM# DEST [SRC]
mv ITEM# DEST [SRC]
Moves a line from source text file (SRC) to destination text file (DEST).
Both source and destination file must be located in the directory defined
in the configuration directory. When SRC is not defined
it's by default todo.txt.
prepend ITEM# "TEXT TO PREPEND"
prep ITEM# "TEXT TO PREPEND"
Adds TEXT TO PREPEND to the beginning of the task on line ITEM#.
Quotes optional.
pri ITEM# PRIORITY
p ITEM# PRIORITY
Adds PRIORITY to task on line ITEM#. If the task is already
prioritized, replaces current priority with new PRIORITY.
PRIORITY must be a letter between A and Z.
replace ITEM# "UPDATED TODO"
Replaces task on line ITEM# with UPDATED TODO.
report
Adds the number of open tasks and done tasks to report.txt.
shorthelp
List the one-line usage of all built-in and add-on actions.
EndActionsHelp
addonHelp
exit 1
}
addonHelp()
{
if [ -d "$TODO_ACTIONS_DIR" ]; then
didPrintAddonActionsHeader=
for action in "$TODO_ACTIONS_DIR"/*
do
if [ -f "$action" -a -x "$action" ]
then
if [ -f "$action" -a -x "$action" ]; then
if [ ! "$didPrintAddonActionsHeader" ]; then
cat <<-EndAddonActionsHeader
Add-on Actions:
EndAddonActionsHeader
didPrintAddonActionsHeader=1
fi
"$action" usage
fi
done
echo ""
fi
exit 1
}
die()
@@ -260,14 +303,13 @@ die()
exit 1
}
cleanup()
{
[ -f "$TMP_FILE" ] && rm "$TMP_FILE"
return 0
}
cleaninput()
{
# Parameters: When $1 = "for sed", performs additional escaping for use
# in sed substitution with "|" separators.
# Precondition: $input contains text to be cleaned.
# Postcondition: Modifies $input.
# Replace CR and LF with space; tasks always comprise a single line.
input=${input//$'\r'/ }
input=${input//$'\n'/ }
@@ -282,18 +324,42 @@ cleaninput()
fi
}
archive()
getPrefix()
{
#defragment blank lines
sed -i.bak -e '/./!d' "$TODO_FILE"
[ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"
grep "^x " "$TODO_FILE" >> "$DONE_FILE"
sed -i.bak '/^x /d' "$TODO_FILE"
cp "$TODO_FILE" "$TMP_FILE"
sed -n 'G; s/\n/&&/; /^\([ ~-]*\n\).*\n\1/d; s/\n//; h; P' "$TMP_FILE" > "$TODO_FILE"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
echo "TODO: $TODO_FILE archived."
fi
# Parameters: $1: todo file; empty means $TODO_FILE.
# Returns: Uppercase FILE prefix to be used in place of "TODO:" where
# a different todo file can be specified.
local base=$(basename "${1:-$TODO_FILE}")
echo "${base%%.[^.]*}" | tr 'a-z' 'A-Z'
}
getTodo()
{
# Parameters: $1: task number
# $2: Optional todo file
# Precondition: $errmsg contains usage message.
# Postcondition: $todo contains task text.
local item=$1
[ -z "$item" ] && die "$errmsg"
[ "${item//[0-9]/}" ] && die "$errmsg"
todo=$(sed "$item!d" "${2:-$TODO_FILE}")
[ -z "$todo" ] && die "$(getPrefix "$2"): No task $item."
}
getNewtodo()
{
# Parameters: $1: task number
# $2: Optional todo file
# Precondition: None.
# Postcondition: $newtodo contains task text.
local item=$1
[ -z "$item" ] && die 'Programming error: $item should exist.'
[ "${item//[0-9]/}" ] && die 'Programming error: $item should be numeric.'
newtodo=$(sed "$item!d" "${2:-$TODO_FILE}")
[ -z "$newtodo" ] && die "$(getPrefix "$2"): No updated task $item."
}
replaceOrPrepend()
@@ -310,12 +376,7 @@ replaceOrPrepend()
;;
esac
shift; item=$1; shift
[ -z "$item" ] && die "$errmsg"
[[ "$item" = +([0-9]) ]] || die "$errmsg"
todo=$(sed "$item!d" "$TODO_FILE")
[ -z "$todo" ] && die "TODO: No task $item."
getTodo "$item"
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
echo -n "$querytext"
@@ -340,7 +401,7 @@ replaceOrPrepend()
# date again.
sed -i.bak -e "$item s/^${priority}${prepdate}//" -e "$item s|^.*|${priority}${prepdate}${input}${backref}|" "$TODO_FILE"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
newtodo=$(sed "$item!d" "$TODO_FILE")
getNewtodo "$item"
case "$action" in
replace)
echo "$item $todo"
@@ -418,7 +479,10 @@ do
OVR_TODOTXT_FORCE=1
;;
h )
shorthelp
# Short-circuit option parsing and forward to the action.
# Cannot just invoke shorthelp() because we need the configuration
# processed to locate the add-on actions directory.
set -- '-h' 'shorthelp'
;;
n )
OVR_TODOTXT_PRESERVE_LINE_NUMBERS=0
@@ -474,6 +538,7 @@ TODOTXT_AUTO_ARCHIVE=${TODOTXT_AUTO_ARCHIVE:-1}
TODOTXT_DATE_ON_ADD=${TODOTXT_DATE_ON_ADD:-0}
TODOTXT_DEFAULT_ACTION=${TODOTXT_DEFAULT_ACTION:-}
TODOTXT_SORT_COMMAND=${TODOTXT_SORT_COMMAND:-env LC_COLLATE=C sort -f -k2}
TODOTXT_DISABLE_FILTER=${TODOTXT_DISABLE_FILTER:-}
TODOTXT_FINAL_FILTER=${TODOTXT_FINAL_FILTER:-cat}
# Export all TODOTXT_* variables
@@ -600,7 +665,6 @@ ACTION=${1:-$TODOTXT_DEFAULT_ACTION}
[ -d "$TODO_DIR" ] || die "Fatal Error: $TODO_DIR is not a directory"
( cd "$TODO_DIR" ) || die "Fatal Error: Unable to cd to $TODO_DIR"
[ -w "$TMP_FILE" ] || echo -n > "$TMP_FILE" || die "Fatal Error: Unable to write to $TMP_FILE"
[ -f "$TODO_FILE" ] || cp /dev/null "$TODO_FILE"
[ -f "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
[ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
@@ -626,13 +690,47 @@ _addto() {
echo "$input" >> "$file"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
TASKNUM=$(sed -n '$ =' "$file")
BASE=$(basename "$file")
PREFIX=$(echo ${BASE%%.[^.]*} | tr 'a-z' 'A-Z')
echo "$TASKNUM $input"
echo "${PREFIX}: $TASKNUM added."
echo "$(getPrefix "$file"): $TASKNUM added."
fi
}
shellquote()
{
typeset -r qq=\'; printf %s\\n "'${1//\'/${qq}\\${qq}${qq}}'";
}
filtercommand()
{
filter=${1:-}
shift
post_filter=${1:-}
shift
for search_term
do
## See if the first character of $search_term is a dash
if [ "${search_term:0:1}" != '-' ]
then
## First character isn't a dash: hide lines that don't match
## this $search_term
filter="${filter:-}${filter:+ | }grep -i $(shellquote "$search_term")"
else
## First character is a dash: hide lines that match this
## $search_term
#
## Remove the first character (-) before adding to our filter command
filter="${filter:-}${filter:+ | }grep -v -i $(shellquote "${search_term:1}")"
fi
done
[ -n "$post_filter" ] && {
filter="${filter:-}${filter:+ | }${post_filter:-}"
}
printf %s "$filter"
}
_list() {
local FILE="$1"
## If the file starts with a "/" use absolute path. Otherwise,
@@ -656,37 +754,32 @@ _list() {
## Get our search arguments, if any
shift ## was file name, new $1 is first search term
## Prefix the filter_command with the pre_filter_command
filter_command="${pre_filter_command:-}"
_format "$src" '' "$@"
for search_term
do
## See if the first character of $search_term is a dash
if [ "${search_term:0:1}" != '-' ]
then
## First character isn't a dash: hide lines that don't match
## this $search_term
filter_command="${filter_command:-} ${filter_command:+|} \
grep -i \"$search_term\" "
else
## First character is a dash: hide lines that match this
## $search_term
#
## Remove the first character (-) before adding to our filter command
filter_command="${filter_command:-} ${filter_command:+|} \
grep -v -i \"${search_term:1}\" "
if [ $TODOTXT_VERBOSE -gt 0 ]; then
echo "--"
echo "$(getPrefix "$src"): ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown"
fi
done
}
getPadding()
{
## We need one level of padding for each power of 10 $LINES uses.
LINES=$(sed -n '$ =' "${1:-$TODO_FILE}")
printf %s ${#LINES}
}
_format()
{
# Parameters: $1: todo input file; when empty formats stdin
# $2: ITEM# number width; if empty auto-detects from $1 / $TODO_FILE.
# Precondition: None
# Postcondition: $NUMTASKS and $TOTALTASKS contain statistics (unless $TODOTXT_VERBOSE=0).
## If post_filter_command is set, append it to the filter_command
[ -n "$post_filter_command" ] && {
filter_command="${filter_command:-}${filter_command:+ | }${post_filter_command:-}"
}
FILE=$1
shift
## Figure out how much padding we need to use
## We need one level of padding for each power of 10 $LINES uses
LINES=$( sed -n '$ =' "$src" )
PADDING=${#LINES}
## Figure out how much padding we need to use, unless this was passed to us.
PADDING=${1:-$(getPadding "$FILE")}
shift
## Number the file, then run the filter command,
## then sort and mangle output some more
@@ -694,12 +787,23 @@ _list() {
TODOTXT_FINAL_FILTER="cat"
fi
items=$(
sed = "$src" \
| sed "N; s/^/ /; s/ *\(.\{$PADDING,\}\)\n/\1 /" \
| grep -v "^[ 0-9]\+ *$"
if [ "$FILE" ]; then
sed = "$FILE"
else
sed =
fi \
| sed -e '''
N
s/^/ /
s/ *\([ 0-9]\{'"$PADDING"',\}\)\n/\1 /
/^[ 0-9]\{1,\} *$/d
'''
)
## Build and apply the filter.
filter_command=$(filtercommand "${pre_filter_command:-}" "${post_filter_command:-}" "$@")
if [ "${filter_command}" ]; then
filtered_items=$(echo -n "$items" | eval ${filter_command})
filtered_items=$(echo -n "$items" | eval "${filter_command}")
else
filtered_items=$items
fi
@@ -732,28 +836,24 @@ _list() {
}
''' \
| sed '''
s/'${HIDE_PROJECTS_SUBSTITUTION:-^}'//g
s/'${HIDE_CONTEXTS_SUBSTITUTION:-^}'//g
s/'"${HIDE_PROJECTS_SUBSTITUTION:-^}"'//g
s/'"${HIDE_CONTEXTS_SUBSTITUTION:-^}"'//g
s/'"${HIDE_CUSTOM_SUBSTITUTION:-^}"'//g
''' \
| eval ${TODOTXT_FINAL_FILTER} \
)
[ "$filtered_items" ] && echo "$filtered_items"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
BASE=$(basename "$FILE")
PREFIX=$(echo ${BASE%%.[^.]*} | tr 'a-z' 'A-Z')
NUMTASKS=$( echo -n "$filtered_items" | sed -n '$ =' )
TOTALTASKS=$( echo -n "$items" | sed -n '$ =' )
echo "--"
echo "${PREFIX}: ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown"
fi
if [ $TODOTXT_VERBOSE -gt 1 ]; then
echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
fi
}
export -f cleaninput _list die
export -f cleaninput getPrefix getTodo getNewtodo shellquote filtercommand _list getPadding _format die
# == HANDLE ACTION ==
action=$( printf "%s\n" "$ACTION" | tr 'A-Z' 'a-z' )
@@ -771,9 +871,7 @@ then
elif [ -d "$TODO_ACTIONS_DIR" -a -x "$TODO_ACTIONS_DIR/$action" ]
then
"$TODO_ACTIONS_DIR/$action" "$@"
status=$?
cleanup
exit $status
exit $?
fi
## Only run if $action isn't found in .todo.actions.d
@@ -830,11 +928,8 @@ case $action in
"append" | "app" )
errmsg="usage: $TODO_SH append ITEM# \"TEXT TO APPEND\""
shift; item=$1; shift
getTodo "$item"
[ -z "$item" ] && die "$errmsg"
[[ "$item" = +([0-9]) ]] || die "$errmsg"
todo=$(sed "$item!d" "$TODO_FILE")
[ -z "$todo" ] && die "TODO: No task $item."
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
echo -n "Append: "
read input
@@ -849,7 +944,7 @@ case $action in
if sed -i.bak $item" s|^.*|&${appendspace}${input}|" "$TODO_FILE"; then
if [ $TODOTXT_VERBOSE -gt 0 ]; then
newtodo=$(sed "$item!d" "$TODO_FILE")
getNewtodo "$item"
echo "$item $newtodo"
fi
else
@@ -858,20 +953,25 @@ case $action in
;;
"archive" )
archive;;
# defragment blank lines
sed -i.bak -e '/./!d' "$TODO_FILE"
[ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"
grep "^x " "$TODO_FILE" >> "$DONE_FILE"
sed -i.bak '/^x /d' "$TODO_FILE"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
echo "TODO: $TODO_FILE archived."
fi
;;
"del" | "rm" )
# replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
errmsg="usage: $TODO_SH del ITEM# [TERM]"
item=$2
[ -z "$item" ] && die "$errmsg"
[[ "$item" = +([0-9]) ]] || die "$errmsg"
DELETEME=$(sed "$item!d" "$TODO_FILE")
[ -z "$DELETEME" ] && die "TODO: No task $item."
getTodo "$item"
if [ -z "$3" ]; then
if [ $TODOTXT_FORCE = 0 ]; then
echo "Delete '$DELETEME'? (y/n)"
echo "Delete '$todo'? (y/n)"
read ANSWER
else
ANSWER="y"
@@ -885,7 +985,7 @@ case $action in
sed -i.bak -e $item"s/^.*//" "$TODO_FILE"
fi
if [ $TODOTXT_VERBOSE -gt 0 ]; then
echo "$item $DELETEME"
echo "$item $todo"
echo "TODO: $item deleted."
fi
else
@@ -899,13 +999,13 @@ case $action in
-e $item"s/ *$3 */ /g" \
-e $item"s/$3//g" \
"$TODO_FILE"
newtodo=$(sed "$item!d" "$TODO_FILE")
if [ "$DELETEME" = "$newtodo" ]; then
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item $DELETEME"
getNewtodo "$item"
if [ "$todo" = "$newtodo" ]; then
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item $todo"
die "TODO: '$3' not found; no removal done."
fi
if [ $TODOTXT_VERBOSE -gt 0 ]; then
echo "$item $DELETEME"
echo "$item $todo"
echo "TODO: Removed '$3' from task."
echo "$item $newtodo"
fi
@@ -919,16 +1019,14 @@ case $action in
# Split multiple depri's, if comma separated change to whitespace separated
# Loop the 'depri' function for each item
for item in $(echo $* | tr ',' ' '); do
[[ "$item" = +([0-9]) ]] || die "$errmsg"
todo=$(sed "$item!d" "$TODO_FILE")
[ -z "$todo" ] && die "TODO: No task $item."
for item in ${*//,/ }; do
getTodo "$item"
if [[ "$todo" = \(?\)\ * ]]; then
sed -i.bak -e $item"s/^(.) //" "$TODO_FILE"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
NEWTODO=$(sed "$item!d" "$TODO_FILE")
echo "$item $NEWTODO"
getNewtodo "$item"
echo "$item $newtodo"
echo "TODO: $item deprioritized."
fi
else
@@ -945,12 +1043,8 @@ case $action in
# Split multiple do's, if comma separated change to whitespace separated
# Loop the 'do' function for each item
for item in $(echo $* | tr ',' ' '); do
[ -z "$item" ] && die "$errmsg"
[[ "$item" = +([0-9]) ]] || die "$errmsg"
todo=$(sed "$item!d" "$TODO_FILE")
[ -z "$todo" ] && die "TODO: No task $item."
for item in ${*//,/ }; do
getTodo "$item"
# Check if this item has already been done
if [ "${todo:0:2}" != "x " ]; then
@@ -959,7 +1053,7 @@ case $action in
sed -i.bak $item"s/^(.) //" "$TODO_FILE"
sed -i.bak $item"s|^|x $now |" "$TODO_FILE"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
newtodo=$(sed "$item!d" "$TODO_FILE")
getNewtodo "$item"
echo "$item $newtodo"
echo "TODO: $item marked as done."
fi
@@ -969,7 +1063,9 @@ case $action in
done
if [ $TODOTXT_AUTO_ARCHIVE = 1 ]; then
archive
# Recursively invoke the script to allow overriding of the archive
# action.
"$TODO_FULL_SH" archive
fi
;;
@@ -983,6 +1079,16 @@ case $action in
help # just in case something failed above, we go ahead and just spew to STDOUT
;;
"shorthelp" )
if [ -t 1 ] ; then # STDOUT is a TTY
if which "${PAGER:-less}" >/dev/null 2>&1; then
# we have a working PAGER (or less as a default)
shorthelp | "${PAGER:-less}" && exit 0
fi
fi
shorthelp # just in case something failed above, we go ahead and just spew to STDOUT
;;
"list" | "ls" )
shift ## Was ls; new $1 is first search term
_list "$TODO_FILE" "$@"
@@ -991,16 +1097,34 @@ case $action in
"listall" | "lsa" )
shift ## Was lsa; new $1 is first search term
cat "$TODO_FILE" "$DONE_FILE" > "$TMP_FILE"
_list "$TMP_FILE" "$@"
TOTAL=$( sed -n '$ =' "$TODO_FILE" )
PADDING=${#TOTAL}
post_filter_command="awk -v TOTAL=$TOTAL -v PADDING=$PADDING '{ \$1 = sprintf(\"%\" PADDING \"d\", (\$1 > TOTAL ? 0 : \$1)); print }' "
cat "$TODO_FILE" "$DONE_FILE" | TODOTXT_VERBOSE=0 _format '' "$PADDING" "$@"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
TDONE=$( sed -n '$ =' "$DONE_FILE" )
TASKNUM=$(TODOTXT_PLAIN=1 TODOTXT_VERBOSE=0 _format "$TODO_FILE" 1 "$@" | sed -n '$ =')
DONENUM=$(TODOTXT_PLAIN=1 TODOTXT_VERBOSE=0 _format "$DONE_FILE" 1 "$@" | sed -n '$ =')
echo "--"
echo "$(getPrefix "$TODO_FILE"): ${TASKNUM:-0} of ${TOTAL:-0} tasks shown"
echo "$(getPrefix "$DONE_FILE"): ${DONENUM:-0} of ${TDONE:-0} tasks shown"
echo "total $((TASKNUM + DONENUM)) of $((TOTAL + TDONE)) tasks shown"
fi
;;
"listfile" | "lf" )
shift ## Was listfile, next $1 is file name
if [ $# -eq 0 ]; then
[ $TODOTXT_VERBOSE -gt 0 ] && echo "Files in the todo.txt directory:"
cd "$TODO_DIR" && ls -1 *.txt
else
FILE="$1"
shift ## Was filename; next $1 is first search term
_list "$FILE" "$@"
fi
;;
"listcon" | "lsc" )
@@ -1008,14 +1132,15 @@ case $action in
;;
"listproj" | "lsprj" )
grep -o '[^ ]*+[^ ]\+' "$TODO_FILE" | grep '^+' | sort -u
shift
eval "$(filtercommand 'cat "$TODO_FILE"' '' "$@")" | grep -o '[^ ]*+[^ ]\+' | grep '^+' | sort -u
;;
"listpri" | "lsp" )
shift ## was "listpri", new $1 is priority to list or first TERM
pri=$(printf "%s\n" "$1" | tr 'a-z' 'A-Z' | grep '^[A-Z]$') && shift || pri="[A-Z]"
post_filter_command="grep '^ *[0-9]\+ (${pri}) '"
pri=$(printf "%s\n" "$1" | tr 'a-z' 'A-Z' | grep -e '^[A-Z]$' -e '^[A-Z]-[A-Z]$') && shift || pri="A-Z"
post_filter_command="grep '^ *[0-9]\+ ([${pri}]) '"
_list "$TODO_FILE" "$@"
;;
@@ -1026,19 +1151,16 @@ case $action in
dest="$TODO_DIR/$3"
src="$TODO_DIR/$4"
[ -z "$item" ] && die "$errmsg"
[ -z "$4" ] && src="$TODO_FILE"
[ -z "$dest" ] && die "$errmsg"
[[ "$item" = +([0-9]) ]] || die "$errmsg"
[ -f "$src" ] || die "TODO: Source file $src does not exist."
[ -f "$dest" ] || die "TODO: Destination file $dest does not exist."
MOVEME=$(sed "$item!d" "$src")
[ -z "$MOVEME" ] && die "$item: No such item in $src."
getTodo "$item" "$src"
[ -z "$todo" ] && die "$item: No such item in $src."
if [ $TODOTXT_FORCE = 0 ]; then
echo "Move '$MOVEME' from $src to $dest? (y/n)"
echo "Move '$todo' from $src to $dest? (y/n)"
read ANSWER
else
ANSWER="y"
@@ -1051,10 +1173,10 @@ case $action in
# leave blank line behind (preserves line numbers)
sed -i.bak -e $item"s/^.*//" "$src"
fi
echo "$MOVEME" >> "$dest"
echo "$todo" >> "$dest"
if [ $TODOTXT_VERBOSE -gt 0 ]; then
echo "$item $MOVEME"
echo "$item $todo"
echo "TODO: $item moved from '$src' to '$dest'."
fi
else
@@ -1075,16 +1197,20 @@ case $action in
note: PRIORITY must be anywhere from A to Z."
[ "$#" -ne 3 ] && die "$errmsg"
[[ "$item" = +([0-9]) ]] || die "$errmsg"
[[ "$newpri" = @([A-Z]) ]] || die "$errmsg"
getTodo "$item"
oldpri=
if [[ "$todo" = \(?\)\ * ]]; then
oldpri=${todo:1:1}
fi
oldpri=$(sed -ne $item's/^(\(.\)) .*/\1/p' "$TODO_FILE")
if [ "$oldpri" != "$newpri" ]; then
sed -i.bak -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE"
fi
if [ $TODOTXT_VERBOSE -gt 0 ]; then
NEWTODO=$(sed "$item!d" "$TODO_FILE")
echo "$item $NEWTODO"
getNewtodo "$item"
echo "$item $newtodo"
if [ "$oldpri" != "$newpri" ]; then
if [ "$oldpri" ]; then
echo "TODO: $item re-prioritized from ($oldpri) to ($newpri)."
@@ -1104,26 +1230,71 @@ note: PRIORITY must be anywhere from A to Z."
;;
"report" )
#archive first
sed '/^x /!d' "$TODO_FILE" >> "$DONE_FILE"
sed -i.bak '/^x /d' "$TODO_FILE"
# archive first
# Recursively invoke the script to allow overriding of the archive
# action.
"$TODO_FULL_SH" archive
NUMLINES=$( sed -n '$ =' "$TODO_FILE" )
if [ ${NUMLINES:-0} = "0" ]; then
echo "datetime todos dones" >> "$REPORT_FILE"
fi
#now report
TOTAL=$( sed -n '$ =' "$TODO_FILE" )
TDONE=$( sed -n '$ =' "$DONE_FILE" )
TECHO=$(echo $(date +%Y-%m-%d-%T); echo ' '; echo ${TOTAL:-0}; echo ' ';
echo ${TDONE:-0})
echo $TECHO >> "$REPORT_FILE"
NEWDATA="${TOTAL:-0} ${TDONE:-0}"
LASTREPORT=$(sed -ne '$p' "$REPORT_FILE")
LASTDATA=${LASTREPORT#* } # Strip timestamp.
if [ "$LASTDATA" = "$NEWDATA" ]; then
echo "$LASTREPORT"
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file is up-to-date."
else
NEWREPORT="$(date +%Y-%m-%dT%T) ${NEWDATA}"
echo "${NEWREPORT}" >> "$REPORT_FILE"
echo "${NEWREPORT}"
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file updated."
cat "$REPORT_FILE"
fi
;;
"deduplicate" )
if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
deduplicateSedCommand='d'
else
deduplicateSedCommand='s/^.*//; p'
fi
# To determine the difference when deduplicated lines are preserved, only
# non-empty lines must be counted.
originalTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' )
# Look for duplicate lines and discard the second occurrence.
# We start with an empty hold space on the first line. For each line:
# G - appends newline + hold space to the pattern space
# s/\n/&&/; - double up the first new line so we catch adjacent dups
# /^\([^\n]*\n\).*\n\1/b dedup
# If the first line of the hold space shows up again later as an
# entire line, it's a duplicate. Jump to the "dedup" label, where
# either of the following is executed, depending on whether empty
# lines should be preserved:
# d - Delete the current pattern space, quit this line and
# move on to the next, or:
# s/^.*//; p - Clear the task text, print this line and move on to
# the next.
# s/\n//; - else (no duplicate), drop the doubled newline
# h; - replace the hold space with the expanded pattern space
# P; - print up to the first newline (that is, the input line)
# b - end processing of the current line
sed -i.bak -n \
-e 'G; s/\n/&&/; /^\([^\n]*\n\).*\n\1/b dedup' \
-e 's/\n//; h; P; b' \
-e ':dedup' \
-e "$deduplicateSedCommand" \
"$TODO_FILE"
newTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' )
deduplicateNum=$(( originalTaskNum - newTaskNum ))
if [ $deduplicateNum -eq 0 ]; then
echo "TODO: No duplicate tasks found"
else
echo "TODO: $deduplicateNum duplicate task(s) removed"
fi
;;
* )
usage;;
esac
cleanup

79
todo_completion Normal file
View File

@@ -0,0 +1,79 @@
#!/bin/bash source-this-script
[ "$BASH_VERSION" ] || return
_todo()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
local -r OPTS="-@ -@@ -+ -++ -d -f -h -p -P -PP -a -n -t -v -vv -V -x"
local -r COMMANDS="\
add a addto addm append app archive command del \
rm depri dp do help list ls listall lsa listcon \
lsc listfile lf listpri lsp listproj lsprj move \
mv prepend prep pri p replace report shorthelp"
# Add custom commands from add-ons, if installed.
# TODO: Filter for executable flag of files found in $TODO_ACTIONS_DIR.
local allCommands="$COMMANDS $('ls' "${TODO_ACTIONS_DIR:-$HOME/.todo.actions.d}/" 2>/dev/null)"
local completions
if [ $COMP_CWORD -eq 1 ]; then
completions="$allCommands $OPTS"
elif [[ $COMP_CWORD -gt 2 && ( \
"${COMP_WORDS[COMP_CWORD-2]}" =~ ^(move|mv)$ || \
"${COMP_WORDS[COMP_CWORD-3]}" =~ ^(move|mv)$ ) ]]; then
# "move ITEM# DEST [SRC]" has file arguments on positions 2 and 3.
completions=$(TODOTXT_VERBOSE=0 todo.sh command listfile)
else
case "$prev" in
command)
completions=$COMMANDS;;
addto|listfile|lf)
completions=$(TODOTXT_VERBOSE=0 todo.sh command listfile);;
-*) completions="$allCommands $OPTS";;
*) case "$cur" in
+*) completions=$(TODOTXT_VERBOSE=0 todo.sh command listproj);;
@*) completions=$(TODOTXT_VERBOSE=0 todo.sh command listcon);;
*) if [[ "$cur" =~ ^[0-9]+$ ]]; then
# Remove the (padded) task number; we prepend the
# user-provided $cur instead.
# Remove the timestamp prepended by the -t option,
# and the done date (for done tasks); there's no
# todo.txt option for that yet.
# But keep priority and "x"; they're short and may
# provide useful context.
# Remove any trailing whitespace; the Bash
# completion inserts a trailing space itself.
# Finally, limit the output to a single line just as
# a safety check of the ls action output.
local todo=$( \
TODOTXT_VERBOSE=0 todo.sh -@ -+ -p -x command ls "^ *${cur} " | \
sed -e 's/^ *[0-9]\+ //' -e 's/\((.) \)[0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} /\1/' \
-e 's/\([xX] \)\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{1,2\}/\1/' \
-e 's/[[:space:]]*$//' \
-e '1q' \
)
# Append task text as a shell comment. This
# completion can be a safety check before a
# destructive todo.txt operation.
[ "$todo" ] && COMPREPLY[0]="$cur # $todo"
return 0
else
return 0
fi
;;
esac
;;
esac
fi
COMPREPLY=( $( compgen -W "$completions" -- $cur ))
return 0
}
complete -F _todo todo.sh
# If you define an alias (e.g. "t") to todo.sh, you need to explicitly enable
# completion for it, too:
#complete -F _todo t