From 779200685366e173b29f62f6a3837b704363f85b Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Thu, 16 Sep 2021 19:44:48 +0200 Subject: [PATCH 01/15] 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. --- tests/actions-test-lib.sh | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/tests/actions-test-lib.sh b/tests/actions-test-lib.sh index 51d4eefc..2530fb8a 100644 --- a/tests/actions-test-lib.sh +++ b/tests/actions-test-lib.sh @@ -1,20 +1,26 @@ #!/bin/bash -make_action() +make_dummy_action() { - unset TODO_ACTIONS_DIR - [ -d .todo.actions.d ] || mkdir .todo.actions.d - cat > ".todo.actions.d/$1" < "$1" < ".todo.actions.d/$1/$1" < Date: Thu, 16 Sep 2021 21:38:47 +0200 Subject: [PATCH 02/15] Refactoring: Extract hasCustomAction() --- todo.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/todo.sh b/todo.sh index fb8d4bce..37eca5e2 100755 --- a/todo.sh +++ b/todo.sh @@ -1036,6 +1036,11 @@ listWordsWithSigil() | sort -u } +hasCustomAction() +{ + [ -d "${1:?}" ] && [ -x "${1:?}/${2:?}" ] +} + export -f cleaninput getPrefix getTodo getNewtodo shellquote filtercommand _list listWordsWithSigil getPadding _format die # == HANDLE ACTION == @@ -1051,11 +1056,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 $? From e1c1c328a21e4c077688b4a50dbdc622f84d587e Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Thu, 16 Sep 2021 22:15:00 +0200 Subject: [PATCH 03/15] 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 --- tests/actions-test-lib.sh | 4 ++-- tests/t8000-actions.sh | 46 +++++++++++++++++++++++++++++++++++++++ todo.sh | 8 ++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/tests/actions-test-lib.sh b/tests/actions-test-lib.sh index 2530fb8a..71cd4fdf 100644 --- a/tests/actions-test-lib.sh +++ b/tests/actions-test-lib.sh @@ -20,7 +20,7 @@ make_action() { unset TODO_ACTIONS_DIR [ -d .todo.actions.d ] || mkdir .todo.actions.d - make_dummy_action ".todo.actions.d/$1" + [ -z "$1" ] || make_dummy_action ".todo.actions.d/$1" } make_action_in_folder() @@ -28,5 +28,5 @@ make_action_in_folder() unset TODO_ACTIONS_DIR [ -d .todo.actions.d ] || mkdir .todo.actions.d mkdir ".todo.actions.d/$1" - make_dummy_action ".todo.actions.d/$1/$1" "in folder $1" + [ -z "$1" ] || make_dummy_action ".todo.actions.d/$1/$1" "in folder $1" } diff --git a/tests/t8000-actions.sh b/tests/t8000-actions.sh index 763c4e92..2facc8f6 100755 --- a/tests/t8000-actions.sh +++ b/tests/t8000-actions.sh @@ -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' <>> todo.sh badlink | sed "s#'[^']*\(\\.todo\\.actions\\.d/[^']\{1,\}\)'#'\1'#g" +Fatal Error: Broken link to custom action: '.todo.actions.d/badlink' + +>>> todo.sh do >/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' <>> todo.sh badfolderlink | sed "s#'[^']*\(\\.todo\\.actions\\.d/[^']\{1,\}\)'#'\1'#g" +Fatal Error: Broken link to custom action: '.todo.actions.d/badfolderlink/badfolderlink' + +>>> todo.sh do >/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' <>> todo.sh do | sed "s#'[^']*\(\\.todo\\.actions\\.d/[^']\{1,\}\)'#'\1'#g" +Fatal Error: Broken link to custom action: '.todo.actions.d/do' + +>>> todo.sh do >/dev/null +=== 1 +EOF + test_done diff --git a/todo.sh b/todo.sh index 37eca5e2..f128e67f 100755 --- a/todo.sh +++ b/todo.sh @@ -1038,7 +1038,13 @@ listWordsWithSigil() hasCustomAction() { - [ -d "${1:?}" ] && [ -x "${1:?}/${2:?}" ] + [ -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 shellquote filtercommand _list listWordsWithSigil getPadding _format die From bd88c20cabcae91bc634c27811599c7d88cb7566 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Sat, 9 Oct 2021 19:57:47 +0200 Subject: [PATCH 04/15] 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. --- todo.cfg | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/todo.cfg b/todo.cfg index 0ab7d9bb..3ccd2181 100644 --- a/todo.cfg +++ b/todo.cfg @@ -76,6 +76,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 From 7a4da603744f7907e56363c4437fa9f858231789 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Mon, 28 Mar 2022 22:23:17 +0200 Subject: [PATCH 05/15] 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 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 925afc8f..8cd987d0 100644 --- a/README.md +++ b/README.md @@ -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 From 273c465af050ac26456e0c29d05ec401a3cc2af7 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Mon, 28 Mar 2022 22:24:49 +0200 Subject: [PATCH 06/15] Documentation: Add Configuration section with overview and recommendation to copy the template --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 8cd987d0..594cb9e4 100644 --- a/README.md +++ b/README.md @@ -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] From aef7d8b9e580b13d58e39a37f139803f207c464f Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Tue, 12 Apr 2022 07:53:07 +0200 Subject: [PATCH 07/15] 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. --- todo.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/todo.sh b/todo.sh index fb8d4bce..d084d58d 100755 --- a/todo.sh +++ b/todo.sh @@ -824,11 +824,6 @@ _addto() { fi } -shellquote() -{ - typeset -r qq=\'; printf %s\\n "'${1//\'/${qq}\\${qq}${qq}}'"; -} - filtercommand() { filter=${1:-} @@ -843,13 +838,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 @@ -1036,7 +1031,7 @@ listWordsWithSigil() | sort -u } -export -f cleaninput getPrefix getTodo getNewtodo shellquote filtercommand _list listWordsWithSigil getPadding _format die +export -f cleaninput getPrefix getTodo getNewtodo filtercommand _list listWordsWithSigil getPadding _format die # == HANDLE ACTION == action=$( printf "%s\n" "$ACTION" | tr '[:upper:]' '[:lower:]' ) From 491979b76a9505b098b57fb042a510f9e89feb5b Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Sat, 7 May 2022 20:03:48 +0200 Subject: [PATCH 08/15] 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. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b6056bc6..d498004c 100644 --- a/Makefile +++ b/Makefile @@ -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 From 28523851d0517fc613df9a9796e21a51ff20f429 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Fri, 17 Jun 2022 23:12:35 +0200 Subject: [PATCH 09/15] 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 --- tests/t1100-replace.sh | 41 +++++++++++++++++++++++++++++++++++++++++ todo.sh | 23 +++++++++++++++++------ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/tests/t1100-replace.sh b/tests/t1100-replace.sh index bfec5e68..67112d41 100755 --- a/tests/t1100-replace.sh +++ b/tests/t1100-replace.sh @@ -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' <>> 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' <>> 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' <>> 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' <>> 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' <>> 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' <>> 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' <>> 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 diff --git a/todo.sh b/todo.sh index fb8d4bce..1887b29a 100755 --- a/todo.sh +++ b/todo.sh @@ -459,20 +459,31 @@ replaceOrPrepend() # 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 From 1a5600c79cd7c586ef4d98abfd010e016dbff7be Mon Sep 17 00:00:00 2001 From: Pegasust Date: Thu, 16 Jun 2022 16:28:05 -0700 Subject: [PATCH 10/15] 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. --- tests/t8010-listaddons.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/t8010-listaddons.sh b/tests/t8010-listaddons.sh index b4e89aa9..cf212e86 100755 --- a/tests/t8010-listaddons.sh +++ b/tests/t8010-listaddons.sh @@ -30,9 +30,11 @@ 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. +# 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 .todo.actions.d/foo ]; then SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8010.4" + rm .todo.actions.d/foo fi test_todo_session 'nonexecutable action' <>> todo.sh listaddons @@ -69,9 +71,11 @@ 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. +# 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 .todo.actions.d/norris/norris ]; then SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8010.8" + rm .todo.actions.d/norris/norris fi test_todo_session 'nonexecutable action in subfolder' <>> todo.sh listaddons From bab2af950186380d6106702768a5a7cce7d84de0 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Sat, 18 Jun 2022 10:55:04 +0200 Subject: [PATCH 11/15] Tests: Refactoring: Extract invalidate_action() into actions-test-lib We don't "shamelessly steal" code, we refactoring it ;-) --- tests/actions-test-lib.sh | 16 ++++++++++++++++ tests/t8010-listaddons.sh | 19 ++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/actions-test-lib.sh b/tests/actions-test-lib.sh index 51d4eefc..daf18a9a 100644 --- a/tests/actions-test-lib.sh +++ b/tests/actions-test-lib.sh @@ -34,3 +34,19 @@ 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 +} diff --git a/tests/t8010-listaddons.sh b/tests/t8010-listaddons.sh index cf212e86..ee0495a9 100755 --- a/tests/t8010-listaddons.sh +++ b/tests/t8010-listaddons.sh @@ -28,14 +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, and remove -# the (still valid) custom action so that it doesn't break following tests. -if [ -x .todo.actions.d/foo ]; then - SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8010.4" - rm .todo.actions.d/foo -fi +invalidate_action .todo.actions.d/foo t8010.4 test_todo_session 'nonexecutable action' <>> todo.sh listaddons bar @@ -68,15 +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, and remove -# the (still valid) custom action so that it doesn't break following tests. -if [ -x .todo.actions.d/norris/norris ]; then - SKIP_TESTS="${SKIP_TESTS}${SKIP_TESTS+ }t8010.8" - rm .todo.actions.d/norris/norris -fi +invalidate_action .todo.actions.d/norris/norris t8010.8 test_todo_session 'nonexecutable action in subfolder' <>> todo.sh listaddons bar From 466265175bdb119f1dbe1ecb5dd78af37d4528f3 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Fri, 17 Jun 2022 23:39:42 +0200 Subject: [PATCH 12/15] 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.) --- tests/t1800-del.sh | 10 ++++------ tests/t1850-move.sh | 4 +--- todo.sh | 15 +++++---------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/tests/t1800-del.sh b/tests/t1800-del.sh index 6d96e89c..2529a933 100755 --- a/tests/t1800-del.sh +++ b/tests/t1800-del.sh @@ -4,8 +4,6 @@ test_description='basic del functionality ' . ./test-lib.sh -SPACE=' ' - test_todo_session 'del usage' <>> todo.sh del B usage: todo.sh del ITEM# [TERM] @@ -60,7 +58,7 @@ test_todo_session 'del with confirmation' <>> printf n | todo.sh del 1 -Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE +\\ TODO: No tasks were deleted. >>> todo.sh -p list @@ -71,15 +69,15 @@ 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. >>> echo | todo.sh del 1 -Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE +\\ TODO: No tasks were deleted. >>> 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. diff --git a/tests/t1850-move.sh b/tests/t1850-move.sh index 9fbdc0b6..2a9e6ea3 100755 --- a/tests/t1850-move.sh +++ b/tests/t1850-move.sh @@ -4,8 +4,6 @@ test_description='basic move functionality ' . ./test-lib.sh -SPACE=' ' - cat > todo.txt <>> 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'. diff --git a/todo.sh b/todo.sh index fb8d4bce..c3d46750 100755 --- a/todo.sh +++ b/todo.sh @@ -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,8 +450,7 @@ 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 @@ -1065,8 +1063,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 @@ -1077,8 +1074,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 @@ -1118,8 +1114,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 From 803881998fed3b34a0f219bd28a03d247183c204 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Sat, 21 Jan 2023 18:59:32 +0100 Subject: [PATCH 13/15] 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. --- todo.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/todo.sh b/todo.sh index fb8d4bce..49d16556 100755 --- a/todo.sh +++ b/todo.sh @@ -349,14 +349,14 @@ dieWithHelp() case "$1" in help) help;; shorthelp) shorthelp;; - esac + esac >&2 shift die "$@" } die() { - echo "$*" + echo >&2 "$*" exit 1 } From ef419f3594c79f329f7f156ade9a2a767a58ee62 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Sat, 21 Jan 2023 19:01:24 +0100 Subject: [PATCH 14/15] 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. --- tests/t1200-pri.sh | 1 + tests/t1500-do.sh | 1 + tests/t1700-depri.sh | 1 + tests/t1800-del.sh | 3 +++ tests/t1910-deduplicate.sh | 1 + tests/t2120-shorthelp.sh | 6 +++--- todo.sh | 23 ++++++++++++++++------- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/tests/t1200-pri.sh b/tests/t1200-pri.sh index fd8bd10c..54b610fd 100755 --- a/tests/t1200-pri.sh +++ b/tests/t1200-pri.sh @@ -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). diff --git a/tests/t1500-do.sh b/tests/t1500-do.sh index 79994980..d359a49d 100755 --- a/tests/t1500-do.sh +++ b/tests/t1500-do.sh @@ -81,6 +81,7 @@ test_todo_session 'fail multiple do attempts' <>> todo.sh -a do 3 +=== 1 TODO: 3 is already marked done. EOF diff --git a/tests/t1700-depri.sh b/tests/t1700-depri.sh index 7ec7e7f9..f459ef10 100755 --- a/tests/t1700-depri.sh +++ b/tests/t1700-depri.sh @@ -82,6 +82,7 @@ test_todo_session 'depriority of unprioritized task' <>> todo.sh depri 3 2 +=== 1 TODO: 3 is not prioritized. 2 notice the sunflowers TODO: 2 deprioritized. diff --git a/tests/t1800-del.sh b/tests/t1800-del.sh index 6d96e89c..bdf66705 100755 --- a/tests/t1800-del.sh +++ b/tests/t1800-del.sh @@ -60,6 +60,7 @@ test_todo_session 'del with confirmation' <>> printf n | todo.sh del 1 +=== 1 Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE TODO: No tasks were deleted. @@ -71,10 +72,12 @@ TODO: No tasks were deleted. TODO: 3 of 3 tasks shown >>> printf x | todo.sh del 1 +=== 1 Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE TODO: No tasks were deleted. >>> echo | todo.sh del 1 +=== 1 Delete '(B) smell the uppercase Roses +flowers @outside'? (y/n)$SPACE TODO: No tasks were deleted. diff --git a/tests/t1910-deduplicate.sh b/tests/t1910-deduplicate.sh index 3617807d..67217864 100755 --- a/tests/t1910-deduplicate.sh +++ b/tests/t1910-deduplicate.sh @@ -32,6 +32,7 @@ EOF test_todo_session 'deduplicate without duplicates' <>> todo.sh deduplicate +=== 1 TODO: No duplicate tasks found EOF diff --git a/tests/t2120-shorthelp.sh b/tests/t2120-shorthelp.sh index f0123b72..82275db5 100755 --- a/tests/t2120-shorthelp.sh +++ b/tests/t2120-shorthelp.sh @@ -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' <>> 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' <>> 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' <>> 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 diff --git a/todo.sh b/todo.sh index 49d16556..50297f2c 100755 --- a/todo.sh +++ b/todo.sh @@ -1170,7 +1170,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 \ @@ -1200,6 +1200,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" @@ -1211,9 +1212,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" ) @@ -1224,6 +1227,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" @@ -1239,15 +1243,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" ) @@ -1363,7 +1369,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 ;; @@ -1374,6 +1380,7 @@ case $action in "pri" | "p" ) shift + status=0 while [ "$#" -gt 0 ] ; do item=$1 newpri=$( printf "%s\n" "$2" | tr '[:lower:]' '[:upper:]' ) @@ -1405,10 +1412,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" ) @@ -1476,7 +1485,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 From ca444e40009efc28f50c9a9002e07c4cc1668955 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Mon, 16 Sep 2024 08:24:07 +0200 Subject: [PATCH 15/15] 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). --- tests/t1250-listpri.sh | 20 ++++++++++++++++++++ todo.sh | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/t1250-listpri.sh b/tests/t1250-listpri.sh index ff374e2f..26303cbc 100755 --- a/tests/t1250-listpri.sh +++ b/tests/t1250-listpri.sh @@ -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' <>> 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 <