Compare commits

...

30 Commits

Author SHA1 Message Date
9e65d3602e Merge remote-tracking branch 'upstream/master' 2024-12-23 13:06:08 +01:00
Ingo Karkat
9623f77af8 ENH: Reuse the todo.sh alias for completion
Having to define a completion function wrapper is cumbersome. I had seen the trick of simply using ${COMP_WORDS[0]} (i.e. the used todo.sh command itself) from Paul Mansfield (https://github.com/the1ts/todo.txt-plugins/blob/develop/bash_completion/todo.txt#L7), which neatly avoids this.
By keeping the _todo_sh variable, this is a one-line change and it still allows the old way of using the override in a wrapper function. (So users aren't forced to change their customizations when upgrading.)
Tests are adapted to verify that the alias is used, and still verify the wrapper function as well.
The documentation is simplified because there's normally no need for the completion wrapper function.
2024-10-31 06:28:34 +01:00
Ingo Karkat
a916aa97df Update build platforms and outdated checkout action
Merge pull request #438 from inkarkat/bump/workflow
2024-09-26 14:51:42 +02:00
Ingo Karkat
b00c212fcc FIX: Regression: Compatibility: Bash on MacOS 12 does not support ${var^^} (after ca444e4)
This went undetected because the CI/CD pipeline was broken again due to an outdated MacOS image.
Continue to use tr for uppercasing. Enable pipefail so that grep (which needs to happen before the uppercasing) result is still considered.

(This corrects commit ca444e4000)
2024-09-21 12:39:22 +02:00
Ingo Karkat
c2b3d01024 Build: ENH: Add dependabot configuration to offer updates to outdated GitHub actions
This will automatically scan our workflow once a week and open PR(s) with updates to GitHub actions.
2024-09-21 12:39:22 +02:00
Ingo Karkat
26a82c3c2f Build: ENH: Enable manual workflow run
Helpful for testing.
2024-09-21 12:39:22 +02:00
Ingo Karkat
75dc8cdaed Bump: checkout action to v4
To silence this warning:
> The following actions uses node12 which is deprecated and will be forced to run on node16: actions/checkout@v2
2024-09-21 12:39:22 +02:00
Ingo Karkat
a6d69811d4 Bump: Build platforms to latest available: Ubuntu 24.04, MacOS 14
MacOS 11 is unsupported and the builds are just hanging due to the missing image.
2024-09-21 12:38:47 +02:00
Ingo Karkat
401653fc75 Finally integrate various features and fixes from inkarkat
Merge pull request #405 from inkarkat/master
2024-09-21 10:03:21 +02:00
Ingo Karkat
ca444e4000 ENH: Allow concatenation of multiple priorities [-ranges] for listpri
It can be useful to filter for non-consecutive priority ranges. By enforcing uppercase in that added syntax, overlap with general TERM(s) (e.g. "foo-bar") can be mostly avoided (and in the rare case of having to filter by all-uppercase TERM(s) one can always pass the default A-Z filter, anyway).
2024-09-16 08:24:07 +02:00
Ingo Karkat
ea17510e4e Merge remote-tracking branch 'upstream/master' 2023-06-30 17:13:04 +02:00
Ingo Karkat
884b5deb6e Merge branches 'enh/cfg-verbose', 'doc/readme-config' and 'rename-completion' 2023-01-22 16:14:36 +01:00
Ingo Karkat
2937a8b316 Merge branch 'fix/stderr' 2023-01-21 19:51:40 +01:00
Ingo Karkat
ef419f3594 Use die() / print to stderr for error conditions
To indicate that something went wrong (e.g. the task already was unprioritized).
Note: For actions that handle multiple ITEMs in a loop, we cannot use die() as that would abort processing of any following ITEM(s). Instead, use a status variable and set it to 1 on error, then exit at the end.
2023-01-21 19:01:24 +01:00
Ingo Karkat
803881998f FIX: Use standard error for die() and dieWithHelp()
By convention, error output should be printed to standard error, not standard out. Same for the usage help that may accompany the error message.
2023-01-21 18:59:32 +01:00
Ingo Karkat
4cd6822946 Merge branches 'read-p' and 'testfix/cygwin-customaction' 2022-06-18 12:46:28 +02:00
Ingo Karkat
466265175b Refactoring: Use read -p MSG instead of doing echo -n MSG separately
I've seen strange readline editing behavior when the editing doesn't start at the first column: I can actually backspace into the prepended message (with Del, Ctrl-W or Ctrl-U), and then the whole edit becomes messed up.

read can output a prompt on its own (hopefully in all versions of Bash that we aim to support - the tests will tell), and that doesn't have this problem, and it's also a bit cleaner and shorter.

The prompt is only displayed if input is coming from a terminal. For the tests (currently only deletion and move confirmations are covered), this means that the prompt itself cannot be covered, and an empty line instead has to be expected. (On the positive side, this removes the ugly trick with $SPACE.)
2022-06-18 12:26:17 +02:00
Ingo Karkat
bab2af9501 Tests: Refactoring: Extract invalidate_action() into actions-test-lib
We don't "shamelessly steal" code, we refactoring it ;-)
2022-06-18 10:57:28 +02:00
Pegasust
1a5600c79c test listaddons: Make pass on Cygwin
If a custom action cannot be made non-executable, it needs to be removed as well (and the test skipped); otherwise its existence will break following tests that assume it's inactive.
2022-06-18 10:49:22 +02:00
Ingo Karkat
b25c791af7 Merge branch 'fix/replace-pri' 2022-06-17 23:38:33 +02:00
Ingo Karkat
28523851d0 replace: Completely merge given priority / date with existing
So that any combination of priority / date entered in the replacement will replace the corresponding original ones, but if they are left out, the original ones will be kept.
In essence, omitted stuff will be kept, added stuff will override, only deletion of existing stuff is not possible (but this is replace, after all).

Fixes #386
2022-06-17 23:12:35 +02:00
Ingo Karkat
491979b76a Renaming: Add .sh extension to completion script
This doesn't matter if (as currently recommended) the script is placed into a eagerly loaded location (like /etc/bash_completion.d/) - any name will do.
However, there's now lazy loading of completion scripts (in /usr/share/bash-completion/completions/), and that only works when the completion script is named exactly like the command the completion is for. As our command is todo.sh (ignoring aliases, which become more complex with lazy loading), the corresponding completion needs to be todo.sh (with the .sh extension) as well. Renaming does not do any harm for our recommended location, but makes it easier for users (and packagers who prepare a todo.sh package) that want to use lazy loading.

See https://github.com/todotxt/todo.txt-cli/issues/383 for the complete discussion.
2022-05-07 20:03:48 +02:00
Ingo Karkat
bf001cef6b Merge branches 'fix/broken-action-symlink' and 'refactor/shellquote' 2022-04-12 08:17:01 +02:00
Ingo Karkat
aef7d8b9e5 Refactoring: Replace shellquote() with printf %q
I didn't know about printf's capability when I introduced quoting 10 years ago. The %q format will do the quoting, and "-v VAR" can be used to reassign to the variable.

Note: The shellquote() function has been exported for reuse by add-ons. I don't think anyone has ever used that (it was mostly intended for my own, extensive extensions, and I never used it), and is trivial to move away from, anyway.
2022-04-12 07:53:07 +02:00
Ingo Karkat
273c465af0 Documentation: Add Configuration section with overview and recommendation to copy the template 2022-03-28 22:24:49 +02:00
Ingo Karkat
7a4da60374 Documentation: Clarify that CONFIG_DIR is for the configuration template
And only coincidentally picked up as the global configuration (if CONFIG_DIR=/etc).

Fixes #377
2022-03-28 22:23:17 +02:00
Ingo Karkat
bd88c20cab Add TODOTXT_VERBOSE to the configuration
There's no command-line option to reduce verbosity (just -v to increase it), so users who would like to remove the additional messages (cp. https://github.com/todotxt/todo.txt-cli/discussions/364) have to configure this, but the variable is hard to find.
Include the default value in commented-out form and some documentation of the possible values.
2021-10-09 19:57:47 +02:00
Ingo Karkat
e1c1c328a2 Robustness: Check for broken symlinks to custom actions and complain
Instead of potentially falling back to the built-in action that a custom action was intended to override, but (e.g. due to file system reorganizations) now results in a broken link. The extension functionality that is then skipped may result in undesired results, but this may not be immedately obvious to the user (if the extension is not particularly verbose), so some data corruption could occur if this remains undetected.
To avoid duplicating (or somehow extracting) all the built-in actions, simply detect _any_ broken symlink; i.e. offer a superset of the required functionality. So this would also complain about a broken symlink to a non-executable custom (auxiliary) file (rarely used) if that is mistakenly passed as a custom action (unlikely).

Fixes #359
2021-09-16 22:15:00 +02:00
Ingo Karkat
1185ab1d5e Refactoring: Extract hasCustomAction() 2021-09-16 21:38:47 +02:00
Ingo Karkat
7792006853 Tests: Refactoring: Extract make_dummy_action()
The action script creation in both test helper functions is pretty similar; extract a function for that so that the invocation is a single command.
2021-09-16 21:38:02 +02:00
20 changed files with 257 additions and 105 deletions

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

View File

@@ -5,15 +5,16 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
jobs:
test:
strategy:
matrix:
platform: [ubuntu-20.04, macos-11]
platform: [ubuntu-24.04, macos-14]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: make
- run: make dist
- run: make test

View File

@@ -64,7 +64,7 @@ clean: test-pre-clean
install: installdirs
$(INSTALL_PROGRAM) todo.sh $(DESTDIR)$(bindir)/todo.sh
$(INSTALL_DATA) todo_completion $(DESTDIR)$(datarootdir)/todo
$(INSTALL_DATA) todo_completion $(DESTDIR)$(datarootdir)/todo.sh
[ -e $(DESTDIR)$(sysconfdir)/todo/config ] || \
sed "s/^\(export[ \t]*TODO_DIR=\).*/\1~\/.todo/" todo.cfg > $(DESTDIR)$(sysconfdir)/todo/config

View File

@@ -46,7 +46,7 @@ make test
*NOTE:* Makefile defaults to several default paths for installed files. Adjust to your system:
- `INSTALL_DIR`: PATH for executables (default /usr/local/bin)
- `CONFIG_DIR`: PATH for todo.txt config
- `CONFIG_DIR`: PATH for the todo.txt configuration template
- `BASH_COMPLETION`: PATH for autocompletion scripts (default to /etc/bash_completion.d)
```shell
@@ -58,6 +58,11 @@ make install CONFIG_DIR=/etc INSTALL_DIR=/usr/bin BASH_COMPLETION=/usr/share/bas
https://aur.archlinux.org/packages/todotxt/
## Configuration
No configuration is required; however, most users tweak the default settings (e.g. relocating the todo.txt directory to a subdirectory of the user's home directory, or onto a cloud drive (via the `TODO_DIR` variable)), modify the colors, add additional highlighting of projects, contexts, dates, and so on. A configuration template with a commented-out list of all available options is included.
It is recommended to _copy_ that template into one of the locations listed by `todo.sh help` on `-d CONFIG_FILE`, even if it is installed in the global configuration location (`/etc/todo/config`).
## Usage
```shell
todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]

View File

@@ -1,20 +1,26 @@
#!/bin/bash
make_dummy_action()
{
local actionName; actionName="$(basename "${1:?}")"
cat > "$1" <<EOF
#!/bin/bash
[ "\$1" = "usage" ] && {
echo " $actionName ITEM#[, ITEM#, ...] [TERM...]"
echo " This custom action does $actionName."
echo ""
exit
}
echo "custom action $actionName$2"
EOF
chmod +x "$1"
}
make_action()
{
unset TODO_ACTIONS_DIR
[ -d .todo.actions.d ] || mkdir .todo.actions.d
cat > ".todo.actions.d/$1" <<EOF
#!/bin/bash
[ "\$1" = "usage" ] && {
echo " $1 ITEM#[, ITEM#, ...] [TERM...]"
echo " This custom action does $1."
echo ""
exit
}
echo "custom action $1"
EOF
chmod +x ".todo.actions.d/$1"
[ -z "$1" ] || make_dummy_action ".todo.actions.d/$1"
}
make_action_in_folder()
@@ -22,15 +28,21 @@ make_action_in_folder()
unset TODO_ACTIONS_DIR
[ -d .todo.actions.d ] || mkdir .todo.actions.d
mkdir ".todo.actions.d/$1"
cat > ".todo.actions.d/$1/$1" <<EOF
#!/bin/bash
[ "\$1" = "usage" ] && {
echo " $1 ITEM#[, ITEM#, ...] [TERM...]"
echo " This custom action does $1."
echo ""
exit
[ -z "$1" ] || make_dummy_action ".todo.actions.d/$1/$1" "in folder $1"
}
echo "custom action $1 in folder $1"
EOF
chmod +x ".todo.actions.d/$1/$1"
invalidate_action()
{
local customActionFilespec="${1:?}"; shift
local testName="${1:?}"; shift
chmod -x "$customActionFilespec"
# On Cygwin, clearing the executable flag may have no effect, as the Windows
# ACL may still grant execution rights. In this case, we skip the test, and
# remove the (still valid) custom action so that it doesn't break following
# tests.
if [ -x "$customActionFilespec" ]; then
SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }${testName}"
rm -- "$customActionFilespec"
fi
}

View File

@@ -140,6 +140,18 @@ TODO: Replaced task with:
1 2010-07-04 this also has a new date
EOF
cat /dev/null > todo.txt
test_todo_session 'replace handling prepended priority on add' <<EOF
>>> todo.sh -t add "new task"
1 2009-02-13 new task
TODO: 1 added.
>>> todo.sh replace 1 '(B) this also has a priority now'
1 2009-02-13 new task
TODO: Replaced task with:
1 (B) 2009-02-13 this also has a priority now
EOF
cat /dev/null > todo.txt
test_todo_session 'replace handling priority and prepended date on add' <<EOF
>>> todo.sh -t add "new task"
@@ -156,6 +168,18 @@ TODO: Replaced task with:
1 (A) 2009-02-13 this is just a new one
EOF
cat /dev/null > todo.txt
test_todo_session 'replace handling prepended priority and date on add' <<EOF
>>> todo.sh -t add "new task"
1 2009-02-13 new task
TODO: 1 added.
>>> todo.sh replace 1 '(C) 2010-07-04 this also has a priority and new date'
1 2009-02-13 new task
TODO: Replaced task with:
1 (C) 2010-07-04 this also has a priority and new date
EOF
echo '(A) 2009-02-13 this is just a new one' > todo.txt
test_todo_session 'replace with prepended date replaces existing date' <<EOF
>>> todo.sh replace 1 2010-07-04 this also has a new date
@@ -164,6 +188,14 @@ TODO: Replaced task with:
1 (A) 2010-07-04 this also has a new date
EOF
echo '(A) 2009-02-13 this is just a new one' > todo.txt
test_todo_session 'replace with prepended priority replaces existing priority' <<EOF
>>> todo.sh replace 1 '(B) this also has a new priority'
1 (A) 2009-02-13 this is just a new one
TODO: Replaced task with:
1 (B) 2009-02-13 this also has a new priority
EOF
echo '2009-02-13 this is just a new one' > todo.txt
test_todo_session 'replace with prepended priority and date replaces existing date' <<EOF
>>> todo.sh replace 1 '(B) 2010-07-04 this also has a new date'
@@ -172,4 +204,13 @@ TODO: Replaced task with:
1 (B) 2010-07-04 this also has a new date
EOF
echo '(A) 2009-02-13 this is just a new one' > todo.txt
test_todo_session 'replace with prepended priority and date replaces existing priority and date' <<EOF
>>> todo.sh replace 1 '(B) 2010-07-04 this also has a new prio+date'
1 (A) 2009-02-13 this is just a new one
TODO: Replaced task with:
1 (B) 2010-07-04 this also has a new prio+date
EOF
test_done

View File

@@ -90,6 +90,7 @@ TODO: 2 re-prioritized from (C) to (A).
TODO: 3 of 3 tasks shown
>>> todo.sh pri 2 a
=== 1
2 (A) notice the sunflowers
TODO: 2 already prioritized (A).

View File

@@ -96,6 +96,26 @@ TODO: 0 of 5 tasks shown
--
TODO: 1 of 5 tasks shown
EOF
test_todo_session 'listpri filtering concatenation of priorities and -ranges' <<EOF
>>> todo.sh -p listpri CX
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 ABR-Y
1 (B) smell the uppercase Roses +flowers @outside
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
EOF
cat > todo.txt <<EOF
(B) ccc xxx this line should be third.

View File

@@ -81,6 +81,7 @@ test_todo_session 'fail multiple do attempts' <<EOF
TODO: 3 marked as done.
>>> todo.sh -a do 3
=== 1
TODO: 3 is already marked done.
EOF

View File

@@ -82,6 +82,7 @@ test_todo_session 'depriority of unprioritized task' <<EOF
TODO: 3 of 3 tasks shown
>>> todo.sh depri 3 2
=== 1
TODO: 3 is not prioritized.
2 notice the sunflowers
TODO: 2 deprioritized.

View File

@@ -60,8 +60,9 @@ test_todo_session 'del with confirmation' <<EOF
TODO: 3 of 3 tasks shown
>>> printf n | todo.sh del 1
Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE
\\
TODO: No tasks were deleted.
=== 1
>>> todo.sh -p list
2 (A) notice the sunflowers
@@ -71,15 +72,17 @@ TODO: No tasks were deleted.
TODO: 3 of 3 tasks shown
>>> printf x | todo.sh del 1
Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE
\\
TODO: No tasks were deleted.
=== 1
>>> echo | todo.sh del 1
Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE
\\
TODO: No tasks were deleted.
=== 1
>>> printf y | todo.sh del 1
Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE
\\
1 (B) smell the uppercase Roses +flowers @outside
TODO: 1 deleted.

View File

@@ -4,8 +4,6 @@ test_description='basic move functionality
'
. ./test-lib.sh
SPACE=' '
cat > todo.txt <<EOF
(B) smell the uppercase Roses +flowers @outside
(A) notice the sunflowers
@@ -42,7 +40,7 @@ x 2009-02-13 smell the coffee +wakeup
EOF
test_todo_session 'basic move with confirmation' <<EOF
>>> printf y | todo.sh move 1 done.txt 2>&1 | sed -e "s#'[^']\{1,\}/\([^/']\{1,\}\)'#'\1'#g" -e 's#from .\{1,\}/\([^/]\{1,\}\) to .\{1,\}/\([^/]\{1,\}\)?#from \1 to \2?#g'
Move '(B) smell the uppercase Roses +flowers @outside' from todo.txt to done.txt? (y/n)$SPACE
\\
1 (B) smell the uppercase Roses +flowers @outside
TODO: 1 moved from 'todo.txt' to 'done.txt'.

View File

@@ -32,6 +32,7 @@ EOF
test_todo_session 'deduplicate without duplicates' <<EOF
>>> todo.sh deduplicate
=== 1
TODO: No duplicate tasks found
EOF

View File

@@ -47,7 +47,7 @@ echo 'export TODO_ACTIONS_DIR=$HOME/custom.actions' >> custom.cfg
export TODOTXT_GLOBAL_CFG_FILE=global.cfg
test_todo_session '-h and fatal error without config' <<EOF
>>> todo.sh -h | sed '/^ \\{0,2\\}[A-Z]/!d'
>>> todo.sh -h 2>&1 | sed '/^ \\{0,2\\}[A-Z]/!d'
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
Actions:
Actions can be added and overridden using scripts in the actions
@@ -58,7 +58,7 @@ EOF
# Config option comes too late; "Add-on Actions" is *not* mentioned here.
test_todo_session '-h and fatal error with trailing custom config' <<EOF
>>> todo.sh -h -d custom.cfg | sed '/^ \\{0,2\\}[A-Z]/!d'
>>> todo.sh -h -d custom.cfg 2>&1 | sed '/^ \\{0,2\\}[A-Z]/!d'
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
Actions:
Actions can be added and overridden using scripts in the actions
@@ -69,7 +69,7 @@ EOF
# Config option processed; "Add-on Actions" is mentioned here.
test_todo_session '-h output with preceding custom config' <<EOF
>>> todo.sh -d custom.cfg -h | sed '/^ \\{0,2\\}[A-Z]/!d'
>>> todo.sh -d custom.cfg -h 2>&1 | sed '/^ \\{0,2\\}[A-Z]/!d'
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
Actions:
Actions can be added and overridden using scripts in the actions

View File

@@ -46,15 +46,22 @@ test_todo_session 'todo 1 and 2 contexts' <<EOF
EOF
# Define a second completion function that injects the different configuration
# file. In real use, this would be installed via
# file and uppercases all output. (This is a silly behavior change that still
# requires a completion function override.)
# In real use, this would be installed via
# complete -F _todo2 todo2
_uppercase_todo()
{
todo.sh "$@" | tr '[:lower:]' '[:upper:]'
}
_todo2()
{
local _todo_sh='todo.sh -d "$HOME/todo2.cfg"'
local _todo_sh='_uppercase_todo -d "$HOME/todo2.cfg"'
_todo "$@"
}
test_todo_completion 'all todo1 contexts' 'todo1 list @' '@garden @outdoor @outside'
test_todo_custom_completion _todo2 'all todo2 contexts' 'todo2 list @' '@home @oriental'
test_todo_completion 'all todo2 contexts' 'todo2 list @' '@home @oriental'
test_todo_custom_completion _todo2 'all uppercased todo2 contexts' 'doesNotMatter list @' '@HOME @ORIENTAL'
test_done

View File

@@ -44,4 +44,50 @@ custom action bad
=== 42
EOF
make_action
ln -s /actionsdir/doesnotexist/badlink .todo.actions.d/badlink
# On Cygwin, the Windows ACL may still grant execution rights. In this case, we
# skip the test.
if [ -x .todo.actions.d/badlink ]; then
SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8000.6 t8000.7"
fi
test_todo_session 'broken symlink' <<EOF
>>> todo.sh badlink 2>&1 | sed "s#'[^']*\(\\.todo\\.actions\\.d/[^']\{1,\}\)'#'\1'#g"
Fatal Error: Broken link to custom action: '.todo.actions.d/badlink'
>>> todo.sh do 2>/dev/null
=== 1
EOF
make_action
mkdir .todo.actions.d/badfolderlink
ln -s /actionsdir/doesnotexist/badfolderlink .todo.actions.d/badfolderlink/badfolderlink
# On Cygwin, the Windows ACL may still grant execution rights. In this case, we
# skip the test.
if [ -x .todo.actions.d/badfolderlink/badfolderlink ]; then
SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8000.8 t8000.9"
fi
test_todo_session 'broken symlink in folder' <<EOF
>>> todo.sh badfolderlink 2>&1 | sed "s#'[^']*\(\\.todo\\.actions\\.d/[^']\{1,\}\)'#'\1'#g"
Fatal Error: Broken link to custom action: '.todo.actions.d/badfolderlink/badfolderlink'
>>> todo.sh do 2>/dev/null
=== 1
EOF
make_action
ln -s /actionsdir/doesnotexist/do .todo.actions.d/do
# On Cygwin, the Windows ACL may still grant execution rights. In this case, we
# skip the test.
if [ -x .todo.actions.d/do ]; then
SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8000.10 t8000.11"
fi
test_todo_session 'broken symlink overrides built-in action' <<EOF
>>> todo.sh do 2>&1 | sed "s#'[^']*\(\\.todo\\.actions\\.d/[^']\{1,\}\)'#'\1'#g"
Fatal Error: Broken link to custom action: '.todo.actions.d/do'
>>> todo.sh do 2>/dev/null
=== 1
EOF
test_done

View File

@@ -28,12 +28,7 @@ ls
quux
EOF
chmod -x .todo.actions.d/foo
# On Cygwin, clearing the executable flag may have no effect, as the Windows ACL
# may still grant execution rights. In this case, we skip the test.
if [ -x .todo.actions.d/foo ]; then
SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8010.4"
fi
invalidate_action .todo.actions.d/foo t8010.4
test_todo_session 'nonexecutable action' <<EOF
>>> todo.sh listaddons
bar
@@ -66,13 +61,7 @@ norris
quux
EOF
# nthorne: shamelessly stolen from above..
chmod -x .todo.actions.d/norris/norris
# On Cygwin, clearing the executable flag may have no effect, as the Windows ACL
# may still grant execution rights. In this case, we skip the test.
if [ -x .todo.actions.d/norris/norris ]; then
SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8010.8"
fi
invalidate_action .todo.actions.d/norris/norris t8010.8
test_todo_session 'nonexecutable action in subfolder' <<EOF
>>> todo.sh listaddons
bar

View File

@@ -80,6 +80,13 @@ export REPORT_FILE="$TODO_DIR/report.txt"
# === BEHAVIOR ===
## verbosity
#
# By default, additional information and confirmation of actions (like
# "TODO: 1 added") are printed. You can suppress this via 0 or add extra
# verbosity via 2.
# export TODOTXT_VERBOSE=1
## customize list output
#
# TODOTXT_SORT_COMMAND will filter after line numbers are

95
todo.sh
View File

@@ -254,7 +254,7 @@ actionsHelp()
listpri [PRIORITIES] [TERM...]
lsp [PRIORITIES] [TERM...]
Displays all tasks prioritized PRIORITIES.
PRIORITIES can be a single one (A) or a range (A-C).
PRIORITIES can be a [concatenation of] single (A) or 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
@@ -349,14 +349,14 @@ dieWithHelp()
case "$1" in
help) help;;
shorthelp) shorthelp;;
esac
esac >&2
shift
die "$@"
}
die()
{
echo "$*"
echo >&2 "$*"
exit 1
}
@@ -364,12 +364,11 @@ confirm()
{
[ $TODOTXT_FORCE = 0 ] || return 0
printf %s "${1:?}? (y/n) "
local readArgs=(-e -r)
[ -n "${BASH_VERSINFO:-}" ] && [ \( ${BASH_VERSINFO[0]} -eq 4 -a ${BASH_VERSINFO[1]} -ge 1 \) -o ${BASH_VERSINFO[0]} -gt 4 ] &&
readArgs+=(-N 1) # Bash 4.1+ supports -N nchars
local answer
read "${readArgs[@]}" answer
read -p "${1:?}? (y/n) " "${readArgs[@]}" answer
echo
[ "$answer" = "y" ]
}
@@ -451,28 +450,38 @@ replaceOrPrepend()
getTodo "$item"
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
echo -n "$querytext"
read -r -i "$todo" -e input
read -p "$querytext" -r -i "$todo" -e input
else
input=$*
fi
# Retrieve existing priority and prepended date
local -r priAndDateExpr='^\((.) \)\{0,1\}\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{0,1\}'
priority=$(sed -e "$item!d" -e "${item}s/${priAndDateExpr}.*/\\1/" "$TODO_FILE")
prepdate=$(sed -e "$item!d" -e "${item}s/${priAndDateExpr}.*/\\2/" "$TODO_FILE")
if [ "$prepdate" ] && [ "$action" = "replace" ] && [ "$(echo "$input"|sed -e "s/${priAndDateExpr}.*/\\1\\2/")" ]; then
originalPriority=$(sed -e "$item!d" -e "${item}s/${priAndDateExpr}.*/\\1/" "$TODO_FILE")
priority="$originalPriority"
originalPrepdate=$(sed -e "$item!d" -e "${item}s/${priAndDateExpr}.*/\\2/" "$TODO_FILE")
prepdate="$originalPrepdate"
if [ "$action" = "replace" ]; then
replacementPrepdate="$(echo "$input"|sed -e "s/${priAndDateExpr}.*/\\2/")"
if [ "$replacementPrepdate" ]; then
# If the replaced text starts with a [priority +] date, it will replace
# the existing date, too.
prepdate=
prepdate="$replacementPrepdate"
fi
replacementPriority="$(echo "$input"|sed -e "s/${priAndDateExpr}.*/\\1/")"
if [ "$replacementPriority" ]; then
# If the replaced text starts with a priority, it will replace
# the existing priority, too.
priority="$replacementPriority"
fi
input="$(echo "$input"|sed -e "s/${priAndDateExpr}//")"
fi
# Temporarily remove any existing priority and prepended date, perform the
# change (replace/prepend) and re-insert the existing priority and prepended
# date again.
cleaninput "for sed"
sed -i.bak -e "$item s/^${priority}${prepdate}//" -e "$item s|^.*|${priority}${prepdate}${input}${backref}|" "$TODO_FILE"
sed -i.bak -e "$item s/^${originalPriority}${originalPrepdate}//" -e "$item s|^.*|${priority}${prepdate}${input}${backref}|" "$TODO_FILE"
if [ "$TODOTXT_VERBOSE" -gt 0 ]; then
getNewtodo "$item"
case "$action" in
@@ -826,11 +835,6 @@ _addto() {
fi
}
shellquote()
{
typeset -r qq=\'; printf %s\\n "'${1//\'/${qq}\\${qq}${qq}}'";
}
filtercommand()
{
filter=${1:-}
@@ -845,13 +849,13 @@ filtercommand()
then
## First character isn't a dash: hide lines that don't match
## this $search_term
filter="${filter:-}${filter:+ | }grep -i $(shellquote "$search_term")"
printf -v filter '%sgrep -i %q' "${filter:-}${filter:+ | }" "$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}")"
printf -v filter '%sgrep -v -i %q' "${filter:-}${filter:+ | }" "${search_term:1}"
fi
done
@@ -1045,7 +1049,18 @@ listWordsWithSigil()
| sort -u
}
export -f cleaninput getPrefix getTodo getNewtodo shellquote filtercommand _list listWordsWithSigil getPadding _format die
hasCustomAction()
{
[ -d "${1:?}" ] || return 1
[ -x "$1/${2:?}" ] && return 0
if [ -h "$1/$2" ] && [ ! -e "$1/$2" ]
then
dieWithHelp "$2" "Fatal Error: Broken link to custom action: '$1/$2'"
fi
return 1
}
export -f cleaninput getPrefix getTodo getNewtodo filtercommand _list listWordsWithSigil getPadding _format die
# == HANDLE ACTION ==
action=$( printf "%s\n" "$ACTION" | tr '[:upper:]' '[:lower:]' )
@@ -1060,11 +1075,11 @@ then
shift
## Reset action to new first argument
action=$( printf "%s\n" "$1" | tr '[:upper:]' '[:lower:]' )
elif [ -d "$TODO_ACTIONS_DIR/$action" ] && [ -x "$TODO_ACTIONS_DIR/$action/$action" ]
elif hasCustomAction "$TODO_ACTIONS_DIR/$action" "$action"
then
"$TODO_ACTIONS_DIR/$action/$action" "$@"
exit $?
elif [ -d "$TODO_ACTIONS_DIR" ] && [ -x "$TODO_ACTIONS_DIR/$action" ]
elif hasCustomAction "$TODO_ACTIONS_DIR" "$action"
then
"$TODO_ACTIONS_DIR/$action" "$@"
exit $?
@@ -1074,8 +1089,7 @@ fi
case $action in
"add" | "a")
if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
echo -n "Add: "
read -e -r input
read -p "Add: " -e -r input
else
[ -z "$2" ] && die "usage: $TODO_SH add \"TODO ITEM\""
shift
@@ -1086,8 +1100,7 @@ case $action in
"addm")
if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
echo -n "Add: "
read -e -r input
read -p "Add: " -e -r input
else
[ -z "$2" ] && die "usage: $TODO_SH addm \"TODO ITEM\""
shift
@@ -1127,8 +1140,7 @@ case $action in
getTodo "$item"
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
echo -n "Append: "
read -e -r input
read -p "Append: " -e -r input
else
input=$*
fi
@@ -1179,7 +1191,7 @@ case $action in
echo "TODO: $item deleted."
fi
else
echo "TODO: No tasks were deleted."
die "TODO: No tasks were deleted."
fi
else
sed -i.bak \
@@ -1209,6 +1221,7 @@ case $action in
# Split multiple depri's, if comma separated change to whitespace separated
# Loop the 'depri' function for each item
status=0
for item in ${*//,/ }; do
getTodo "$item"
@@ -1220,9 +1233,11 @@ case $action in
echo "TODO: $item deprioritized."
fi
else
echo "TODO: $item is not prioritized."
echo >&2 "TODO: $item is not prioritized."
status=1
fi
done
exit $status
;;
"do" | "done" )
@@ -1233,6 +1248,7 @@ case $action in
# Split multiple do's, if comma separated change to whitespace separated
# Loop the 'do' function for each item
status=0
for item in ${*//,/ }; do
getTodo "$item"
@@ -1248,15 +1264,17 @@ case $action in
echo "TODO: $item marked as done."
fi
else
echo "TODO: $item is already marked done."
echo >&2 "TODO: $item is already marked done."
status=1
fi
done
if [ $TODOTXT_AUTO_ARCHIVE = 1 ]; then
# Recursively invoke the script to allow overriding of the archive
# action.
"$TODO_FULL_SH" archive
"$TODO_FULL_SH" archive || status=$?
fi
exit $status
;;
"help" )
@@ -1336,7 +1354,7 @@ case $action in
"listpri" | "lsp" )
shift ## was "listpri", new $1 is priority to list or first TERM
pri=$(printf "%s\n" "$1" | tr '[:lower:]' '[:upper:]' | grep -e '^[A-Z]$' -e '^[A-Z]-[A-Z]$') && shift || pri="A-Z"
pri=$(set -o pipefail; printf "%s\n" "$1" | grep '^\([A-Za-z]\|[A-Za-z]-[A-Za-z]\|[A-Z][A-Z-]*[A-Z]\)$' | tr '[:lower:]' '[:upper:]') && shift || pri="A-Z"
post_filter_command="${post_filter_command:-}${post_filter_command:+ | }grep '^ *[0-9]\+ ([${pri}]) '"
_list "$TODO_FILE" "$@"
;;
@@ -1372,7 +1390,7 @@ case $action in
echo "TODO: $item moved from '$src' to '$dest'."
fi
else
echo "TODO: No tasks moved."
die "TODO: No tasks moved."
fi
;;
@@ -1383,6 +1401,7 @@ case $action in
"pri" | "p" )
shift
status=0
while [ "$#" -gt 0 ] ; do
item=$1
newpri=$( printf "%s\n" "$2" | tr '[:lower:]' '[:upper:]' )
@@ -1414,10 +1433,12 @@ note: PRIORITY must be anywhere from A to Z."
fi
fi
if [ "$oldpri" = "$newpri" ]; then
echo "TODO: $item already prioritized ($newpri)."
echo >&2 "TODO: $item already prioritized ($newpri)."
status=1
fi
shift; shift
done
exit $status
;;
"replace" )
@@ -1485,7 +1506,7 @@ note: PRIORITY must be anywhere from A to Z."
newTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' )
deduplicateNum=$(( originalTaskNum - newTaskNum ))
if [ $deduplicateNum -eq 0 ]; then
echo "TODO: No duplicate tasks found"
die "TODO: No duplicate tasks found"
else
echo "TODO: $deduplicateNum duplicate task(s) removed"
fi

View File

@@ -18,7 +18,7 @@ _todo()
mv prepend prep pri p replace report shorthelp"
local -r MOVE_COMMAND_PATTERN='move|mv'
local _todo_sh=${_todo_sh:-todo.sh}
local _todo_sh=${_todo_sh:-${COMP_WORDS[0]}}
local completions
if [ "$COMP_CWORD" -eq 1 ]; then
completions="$COMMANDS $(eval TODOTXT_VERBOSE=0 $_todo_sh command listaddons 2>/dev/null) $OPTS"
@@ -101,22 +101,14 @@ complete -F _todo todo.sh
# ~/.bashrc (or wherever else you're defining your alias). If you simply
# uncomment it here, you will need to redo this on every todo.txt update!
# If you have renamed the todo.sh executable, or if it is not accessible through
# PATH, you need to add and use a wrapper completion function, like this:
#_todoElsewhere()
# The completion uses the alias itself, so any custom arguments (like a custom
# configuration (-d "$HOME/todo2.cfg")) are used there as well.
# If you don't want this, or need to further tweak the todo.sh command that's
# used by the completion, you can add and use a wrapper completion function that
# redefines _todo_sh before invoking _todo():
#_todo_tweak()
#{
# local _todo_sh='/path/to/todo2.sh'
# local _todo_sh='todo.sh -d "$HOME/todo-tweaked.cfg"'
# _todo "$@"
#}
#complete -F _todoElsewhere /path/to/todo2.sh
# If you use aliases to use different configuration(s), you need to add and use
# a wrapper completion function for each configuration if you want to complete
# from the actual configured task locations:
#alias todo2='todo.sh -d "$HOME/todo2.cfg"'
#_todo2()
#{
# local _todo_sh='todo.sh -d "$HOME/todo2.cfg"'
# _todo "$@"
#}
#complete -F _todo2 todo2
#complete -F _todo_tweak todo.sh