Merge remote-tracking branch 'upstream/master'

This commit is contained in:
2024-12-23 13:06:08 +01: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 ] branches: [ master ]
pull_request: pull_request:
branches: [ master ] branches: [ master ]
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
jobs: jobs:
test: test:
strategy: strategy:
matrix: matrix:
platform: [ubuntu-20.04, macos-11] platform: [ubuntu-24.04, macos-14]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- run: make - run: make
- run: make dist - run: make dist
- run: make test - run: make test

View File

@@ -64,7 +64,7 @@ clean: test-pre-clean
install: installdirs install: installdirs
$(INSTALL_PROGRAM) todo.sh $(DESTDIR)$(bindir)/todo.sh $(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 ] || \ [ -e $(DESTDIR)$(sysconfdir)/todo/config ] || \
sed "s/^\(export[ \t]*TODO_DIR=\).*/\1~\/.todo/" todo.cfg > $(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: *NOTE:* Makefile defaults to several default paths for installed files. Adjust to your system:
- `INSTALL_DIR`: PATH for executables (default /usr/local/bin) - `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) - `BASH_COMPLETION`: PATH for autocompletion scripts (default to /etc/bash_completion.d)
```shell ```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/ 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 ## Usage
```shell ```shell
todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description] todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]

View File

@@ -1,20 +1,26 @@
#!/bin/bash #!/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() make_action()
{ {
unset TODO_ACTIONS_DIR unset TODO_ACTIONS_DIR
[ -d .todo.actions.d ] || mkdir .todo.actions.d [ -d .todo.actions.d ] || mkdir .todo.actions.d
cat > ".todo.actions.d/$1" <<EOF [ -z "$1" ] || make_dummy_action ".todo.actions.d/$1"
#!/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"
} }
make_action_in_folder() make_action_in_folder()
@@ -22,15 +28,21 @@ make_action_in_folder()
unset TODO_ACTIONS_DIR unset TODO_ACTIONS_DIR
[ -d .todo.actions.d ] || mkdir .todo.actions.d [ -d .todo.actions.d ] || mkdir .todo.actions.d
mkdir ".todo.actions.d/$1" mkdir ".todo.actions.d/$1"
cat > ".todo.actions.d/$1/$1" <<EOF [ -z "$1" ] || make_dummy_action ".todo.actions.d/$1/$1" "in folder $1"
#!/bin/bash
[ "\$1" = "usage" ] && {
echo " $1 ITEM#[, ITEM#, ...] [TERM...]"
echo " This custom action does $1."
echo ""
exit
} }
echo "custom action $1 in folder $1"
EOF invalidate_action()
chmod +x ".todo.actions.d/$1/$1" {
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 1 2010-07-04 this also has a new date
EOF 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 cat /dev/null > todo.txt
test_todo_session 'replace handling priority and prepended date on add' <<EOF test_todo_session 'replace handling priority and prepended date on add' <<EOF
>>> todo.sh -t add "new task" >>> 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 1 (A) 2009-02-13 this is just a new one
EOF 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 echo '(A) 2009-02-13 this is just a new one' > todo.txt
test_todo_session 'replace with prepended date replaces existing date' <<EOF test_todo_session 'replace with prepended date replaces existing date' <<EOF
>>> todo.sh replace 1 2010-07-04 this also has a new 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 1 (A) 2010-07-04 this also has a new date
EOF 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 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 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' >>> 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 1 (B) 2010-07-04 this also has a new date
EOF 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 test_done

View File

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

View File

@@ -96,6 +96,26 @@ TODO: 0 of 5 tasks shown
-- --
TODO: 1 of 5 tasks shown TODO: 1 of 5 tasks shown
EOF 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 cat > todo.txt <<EOF
(B) ccc xxx this line should be third. (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: 3 marked as done.
>>> todo.sh -a do 3 >>> todo.sh -a do 3
=== 1
TODO: 3 is already marked done. TODO: 3 is already marked done.
EOF EOF

View File

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

View File

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

View File

@@ -4,8 +4,6 @@ test_description='basic move functionality
' '
. ./test-lib.sh . ./test-lib.sh
SPACE=' '
cat > todo.txt <<EOF cat > todo.txt <<EOF
(B) smell the uppercase Roses +flowers @outside (B) smell the uppercase Roses +flowers @outside
(A) notice the sunflowers (A) notice the sunflowers
@@ -42,7 +40,7 @@ x 2009-02-13 smell the coffee +wakeup
EOF EOF
test_todo_session 'basic move with confirmation' <<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' >>> 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 1 (B) smell the uppercase Roses +flowers @outside
TODO: 1 moved from 'todo.txt' to 'done.txt'. TODO: 1 moved from 'todo.txt' to 'done.txt'.

View File

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

View File

@@ -47,7 +47,7 @@ echo 'export TODO_ACTIONS_DIR=$HOME/custom.actions' >> custom.cfg
export TODOTXT_GLOBAL_CFG_FILE=global.cfg export TODOTXT_GLOBAL_CFG_FILE=global.cfg
test_todo_session '-h and fatal error without config' <<EOF 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] Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
Actions: Actions:
Actions can be added and overridden using scripts in the 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. # Config option comes too late; "Add-on Actions" is *not* mentioned here.
test_todo_session '-h and fatal error with trailing custom config' <<EOF 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] Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
Actions: Actions:
Actions can be added and overridden using scripts in the 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. # Config option processed; "Add-on Actions" is mentioned here.
test_todo_session '-h output with preceding custom config' <<EOF 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] Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
Actions: Actions:
Actions can be added and overridden using scripts in the 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 EOF
# Define a second completion function that injects the different configuration # 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 # complete -F _todo2 todo2
_uppercase_todo()
{
todo.sh "$@" | tr '[:lower:]' '[:upper:]'
}
_todo2() _todo2()
{ {
local _todo_sh='todo.sh -d "$HOME/todo2.cfg"' local _todo_sh='_uppercase_todo -d "$HOME/todo2.cfg"'
_todo "$@" _todo "$@"
} }
test_todo_completion 'all todo1 contexts' 'todo1 list @' '@garden @outdoor @outside' 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 test_done

View File

@@ -44,4 +44,50 @@ custom action bad
=== 42 === 42
EOF 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 test_done

View File

@@ -28,12 +28,7 @@ ls
quux quux
EOF EOF
chmod -x .todo.actions.d/foo invalidate_action .todo.actions.d/foo t8010.4
# 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
test_todo_session 'nonexecutable action' <<EOF test_todo_session 'nonexecutable action' <<EOF
>>> todo.sh listaddons >>> todo.sh listaddons
bar bar
@@ -66,13 +61,7 @@ norris
quux quux
EOF EOF
# nthorne: shamelessly stolen from above.. invalidate_action .todo.actions.d/norris/norris t8010.8
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
test_todo_session 'nonexecutable action in subfolder' <<EOF test_todo_session 'nonexecutable action in subfolder' <<EOF
>>> todo.sh listaddons >>> todo.sh listaddons
bar bar

View File

@@ -80,6 +80,13 @@ export REPORT_FILE="$TODO_DIR/report.txt"
# === BEHAVIOR === # === 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 ## customize list output
# #
# TODOTXT_SORT_COMMAND will filter after line numbers are # TODOTXT_SORT_COMMAND will filter after line numbers are

95
todo.sh
View File

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

View File

@@ -18,7 +18,7 @@ _todo()
mv prepend prep pri p replace report shorthelp" mv prepend prep pri p replace report shorthelp"
local -r MOVE_COMMAND_PATTERN='move|mv' local -r MOVE_COMMAND_PATTERN='move|mv'
local _todo_sh=${_todo_sh:-todo.sh} local _todo_sh=${_todo_sh:-${COMP_WORDS[0]}}
local completions local completions
if [ "$COMP_CWORD" -eq 1 ]; then if [ "$COMP_CWORD" -eq 1 ]; then
completions="$COMMANDS $(eval TODOTXT_VERBOSE=0 $_todo_sh command listaddons 2>/dev/null) $OPTS" 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 # ~/.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! # 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 # The completion uses the alias itself, so any custom arguments (like a custom
# PATH, you need to add and use a wrapper completion function, like this: # configuration (-d "$HOME/todo2.cfg")) are used there as well.
#_todoElsewhere() # 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 "$@" # _todo "$@"
#} #}
#complete -F _todoElsewhere /path/to/todo2.sh #complete -F _todo_tweak todo.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