diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 114b94a7..68e6fd3d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ - [ ] Fork [the repository](https://github.com/todotxt/todo.txt-cli) and create your branch from `master`. - [ ] If you've added code that should be tested, add tests! - [ ] Ensure the test suite passes. -- [ ] Format your code with [ShellCheck](https://www.shellcheck.net/). +- [ ] Lint your code with [ShellCheck](https://www.shellcheck.net/). - [ ] Include a human-readable description of what the pull request is trying to accomplish. - [ ] Steps for the reviewer(s) on how they can manually QA the changes. - [ ] Have a `fixes #XX` reference to the issue that this pull request fixes. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ca79ca5b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index eeea980c..0d73c835 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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-latest, macos-latest, ubuntu-18.04] + 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index e10522ba..ed4ed586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- `TODOTXT_DEFAULT_ACTION` now also allows action parameters ([#159], [#407]) + +## [2.13.0] - 2024-12-25 + +### Added +- listpri action allows concatenation of multiple priorities [-ranges] +- replace action completely merges any combination of priority / date with existing ([#386]) +- Check for broken symlinks to custom actions and complain ([#359]) + +### Changed + +- Add .sh extension to completion script +- Reuse the todo.sh alias for completion +- Return from user prompt without requiring Enter ([#354]) +- Default to user writable directory in config ([#148]) + +### Fixed +- Fix problem with spaces in directory path ([#276]) +- pri action takes multiple items + priorities on single run ([#346]) + ## [2.12.0] - 2020-08-11 ### Added @@ -461,7 +482,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Consolidated into one master script with usage notes and released. -[Unreleased]: https://github.com/todotxt/todo.txt-cli/compare/v2.12.0...HEAD +[Unreleased]: https://github.com/todotxt/todo.txt-cli/compare/v2.13.0...HEAD +[2.13.0]: https://github.com/todotxt/todo.txt-cli/compare/v2.12.0...v2.13.0 [2.12.0]: https://github.com/todotxt/todo.txt-cli/compare/v2.11.0...v2.12.0 [2.11.0]: https://github.com/todotxt/todo.txt-cli/compare/v2.10.0...v2.11.0 [2.10.0]: https://github.com/todotxt/todo.txt-cli/compare/v2.9.0...v2.10.0 @@ -492,7 +514,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [1.2.0]: https://github.com/todotxt/todo.txt-cli/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/todotxt/todo.txt-cli/compare/v1.0.0...v1.1.0 [#8]: https://github.com/todotxt/todo.txt-cli/pull/8 +[#148]: https://github.com/todotxt/todo.txt-cli/pull/148 [#156]: https://github.com/todotxt/todo.txt-cli/pull/156 +[#159]: https://github.com/todotxt/todo.txt-cli/pull/159 [#160]: https://github.com/todotxt/todo.txt-cli/pull/160 [#169]: https://github.com/todotxt/todo.txt-cli/pull/169 [#217]: https://github.com/todotxt/todo.txt-cli/pull/217 @@ -501,11 +525,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [#228]: https://github.com/todotxt/todo.txt-cli/pull/228 [#230]: https://github.com/todotxt/todo.txt-cli/pull/230 [#246]: https://github.com/todotxt/todo.txt-cli/pull/246 +[#246]: https://github.com/todotxt/todo.txt-cli/pull/246 [#249]: https://github.com/todotxt/todo.txt-cli/pull/249 +[#254]: https://github.com/todotxt/todo.txt-cli/pull/254 [#264]: https://github.com/todotxt/todo.txt-cli/pull/264 [#270]: https://github.com/todotxt/todo.txt-cli/pull/270 [#271]: https://github.com/todotxt/todo.txt-cli/pull/271 +[#276]: https://github.com/todotxt/todo.txt-cli/pull/276 [#289]: https://github.com/todotxt/todo.txt-cli/pull/289 [#295]: https://github.com/todotxt/todo.txt-cli/pull/295 [#300]: https://github.com/todotxt/todo.txt-cli/pull/300 [#301]: https://github.com/todotxt/todo.txt-cli/pull/301 +[#346]: https://github.com/todotxt/todo.txt-cli/pull/346 +[#354]: https://github.com/todotxt/todo.txt-cli/pull/354 +[#359]: https://github.com/todotxt/todo.txt-cli/pull/359 +[#386]: https://github.com/todotxt/todo.txt-cli/pull/386 +[#407]: https://github.com/todotxt/todo.txt-cli/pull/407 diff --git a/GEN-VERSION-FILE b/GEN-VERSION-FILE index 25e729a2..74935102 100755 --- a/GEN-VERSION-FILE +++ b/GEN-VERSION-FILE @@ -1,23 +1,13 @@ -#!/bin/sh +#!/usr/bin/env bash # Based on git's GIT-VERSION-GEN. VF=VERSION-FILE -DEF_VER=v2.2 - -LF=' -' +DEF_VER=v0.0 if test -d .git -o -f .git && - VN=$(git describe --abbrev=0 --tags 2>/dev/null) && - case "$VN" in - *$LF*) (exit 1) ;; - v[0-9]*) - git update-index -q --refresh - test -z "$(git diff-index --name-only HEAD --)" || - VN="$VN-dirty" ;; - esac + VN=$(git describe --dirty --tags 2>/dev/null) then - VN=$(echo "$VN" | sed -e 's/-/./g'); + VN=${VN//-/.} else VN="$DEF_VER" fi diff --git a/Makefile b/Makefile index b6056bc6..e17e7735 100644 --- a/Makefile +++ b/Makefile @@ -33,42 +33,75 @@ else datarootdir = $(prefix)/share/bash_completion.d endif +# generate list of targets from this Makefile +# looks for any lowercase target with a double hash mark (##) on the same line +# and uses the inline comment as the target description +.PHONY: help +.DEFAULT: help +help: ## list public targets + @echo + @echo todo.txt Makefile + @echo + @sed -ne '/^[a-z%-]\+:.*##/ s/:.*##/\t/p' $(word 1, $(MAKEFILE_LIST)) \ + | column -t -s ' ' + @echo + # Dynamically detect/generate version file as necessary -# This file will define a variable called VERSION. -.PHONY: .FORCE-VERSION-FILE -VERSION-FILE: .FORCE-VERSION-FILE +# This file will define a variable called VERSION used in +# both todo.sh and this Makefile. +VERSION-FILE: @./GEN-VERSION-FILE -include VERSION-FILE -# Maybe this will include the version in it. -todo.sh: VERSION-FILE +# dist/build directory name +DISTNAME=todo.txt_cli-$(VERSION) -# For packaging -DISTFILES := todo.cfg todo_completion +# files to copy unmodified into the dist directory +SRC_FILES := todo.cfg todo_completion -DISTNAME=todo.txt_cli-$(VERSION) -dist: $(DISTFILES) todo.sh +# path of SRC_FILES in the dist directory +OUTPUT_FILES := $(patsubst %, $(DISTNAME)/%, $(SRC_FILES)) + +# all dist files +DISTFILES := $(OUTPUT_FILES) $(DISTNAME)/todo.sh + +# create the dist directory +$(DISTNAME): VERSION-FILE mkdir -p $(DISTNAME) - cp -f $(DISTFILES) $(DISTNAME)/ + +# copy SRC_FILES to the dist directory +$(OUTPUT_FILES): $(DISTNAME)/%: % + cp -f $(*) $(DISTNAME)/ + +# generate todo.sh +$(DISTNAME)/todo.sh: VERSION-FILE sed -e 's/@DEV_VERSION@/'$(VERSION)'/' todo.sh > $(DISTNAME)/todo.sh chmod +x $(DISTNAME)/todo.sh + +.PHONY: build +build: $(DISTNAME) $(DISTFILES) ## create the dist directory and files + +.PHONY: dist +dist: build ## create the compressed release files tar cf $(DISTNAME).tar $(DISTNAME) gzip -f -9 $(DISTNAME).tar zip -r -9 $(DISTNAME).zip $(DISTNAME) rm -r $(DISTNAME) .PHONY: clean -clean: test-pre-clean +clean: test-pre-clean VERSION-FILE ## remove dist directory and all release files + rm -rf $(DISTNAME) rm -f $(DISTNAME).tar.gz $(DISTNAME).zip - rm VERSION-FILE -install: installdirs - $(INSTALL_PROGRAM) todo.sh $(DESTDIR)$(bindir)/todo.sh - $(INSTALL_DATA) todo_completion $(DESTDIR)$(datarootdir)/todo +.PHONY: install +install: build installdirs ## local package install + $(INSTALL_PROGRAM) $(DISTNAME)/todo.sh $(DESTDIR)$(bindir)/todo.sh + $(INSTALL_DATA) $(DISTNAME)/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 + sed "s/^\(export[ \t]*TODO_DIR=\).*/\1~\/.todo/" $(DISTNAME)/todo.cfg > $(DESTDIR)$(sysconfdir)/todo/config -uninstall: +.PHONY: uninstall +uninstall: ## uninstall package rm -f $(DESTDIR)$(bindir)/todo.sh rm -f $(DESTDIR)$(datarootdir)/todo rm -f $(DESTDIR)$(sysconfdir)/todo/config @@ -76,6 +109,8 @@ uninstall: rmdir $(DESTDIR)$(datarootdir) rmdir $(DESTDIR)$(sysconfdir)/todo +# create local installation directories +.PHONY: installdirs installdirs: mkdir -p $(DESTDIR)$(bindir) \ $(DESTDIR)$(sysconfdir)/todo \ @@ -87,18 +122,20 @@ installdirs: TESTS = $(wildcard tests/t[0-9][0-9][0-9][0-9]-*.sh) #TEST_OPTIONS=--verbose +# remove test detritus test-pre-clean: rm -rf tests/test-results "tests/trash directory"* +# run tests and generate test result files aggregate-results: $(TESTS) $(TESTS): test-pre-clean cd tests && ./$(notdir $@) $(TEST_OPTIONS) -test: aggregate-results +# run tests, print a test result summary, and remove generated test results +test: aggregate-results ## run tests tests/aggregate-results.sh tests/test-results/t*-* rm -rf tests/test-results # Force tests to get run every time .PHONY: test test-pre-clean aggregate-results $(TESTS) - diff --git a/README.md b/README.md index 4fc5128c..877981ea 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Download the latest stable [release][release] for use on your desktop or server. ```shell brew install todo-txt -cp -n /usr/local/opt/todo-txt/todo.cfg ~/.todo.cfg + +cp -n $(brew --prefix)/opt/todo-txt/todo.cfg ~/.todo.cfg ``` **Note**: The `-n` flag for `cp` makes sure you do not overwrite an existing file. @@ -41,7 +42,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 @@ -53,6 +54,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] @@ -63,6 +69,18 @@ For example, to add a todo item, you can do: ```shell todo.sh add "THING I NEED TO DO +project @context" ``` +### `replace` +Replaces task on line NR with UPDATED TODO. + +```shell +todo.sh replace NR "UPDATED TODO" +``` +### `report` +Adds the number of open tasks and done tasks to report.txt. + +```shell +todo.sh report +``` Read about all the possible commands in the [USAGE][USAGE] file. diff --git a/USAGE.md b/USAGE.md index 4500377b..b6b4f0f7 100644 --- a/USAGE.md +++ b/USAGE.md @@ -38,13 +38,13 @@ todo.sh addto DEST "TEXT TO ADD" ``` ### `append` -Adds TEXT TO APPEND to the end of the task on line ITEM#. +Adds TEXT TO APPEND to the end of the task on line NR. Quotes optional. ```shell -todo.sh append ITEM# "TEXT TO APPEND" -todo.sh app ITEM# "TEXT TO APPEND" +todo.sh append NR "TEXT TO APPEND" +todo.sh app NR "TEXT TO APPEND" ``` ### `archive` @@ -71,26 +71,26 @@ todo.sh deduplicate ``` ### `del` -Deletes the task on line ITEM# in todo.txt. If TERM specified, deletes only TERM from the task. +Deletes the task on line NR in todo.txt. If TERM specified, deletes only TERM from the task. ```shell -todo.sh del ITEM# [TERM] -todo.sh rm ITEM# [TERM] +todo.sh del NR [TERM] +todo.sh rm NR [TERM] ``` ### `depri` -Deprioritizes (removes the priority) from the task(s) on line ITEM# in todo.txt. +Deprioritizes (removes the priority) from the task(s) on line NR in todo.txt. ```shell -todo.sh depri ITEM#[, ITEM#, ITEM#, ...] -todo.sh dp ITEM#[, ITEM#, ITEM#, ...] +todo.sh depri NR [NR ...] +todo.sh dp NR [NR ...] ``` ### `do` -Marks task(s) on line ITEM# as done in todo.txt. +Marks task(s) on line NR as done in todo.txt. ```shell -todo.sh do ITEM#[, ITEM#, ITEM#, ...] +todo.sh do NR [NR ...] ``` ### `help` @@ -164,34 +164,34 @@ todo.sh lsprj [TERM...] ``` ### `move` -Moves a line from source text file (SRC) to destination text file (DEST). Both source and destination file must be located in the directory defined in the configuration directory. When SRC is not defined it's by default todo.txt. +Moves line NR from source text file (SRC) to destination text file (DEST). Both source and destination file must be located in the directory defined in the configuration directory. When SRC is not defined it's by default todo.txt. ```shell -todo.sh move ITEM# DEST [SRC] -todo.sh mv ITEM# DEST [SRC] +todo.sh move NR DEST [SRC] +todo.sh mv NR DEST [SRC] ``` ### `prepend` -Adds TEXT TO PREPEND to the beginning of the task on line ITEM#. Quotes optional. +Adds TEXT TO PREPEND to the beginning of the task on line NR. Quotes optional. ```shell -todo.sh prepend ITEM# "TEXT TO PREPEND" -todo.sh prep ITEM# "TEXT TO PREPEND" +todo.sh prepend NR "TEXT TO PREPEND" +todo.sh prep NR"TEXT TO PREPEND" ``` ### `pri` -Adds PRIORITY to task on line ITEM#. If the task is already prioritized, replaces current priority with new PRIORITY. PRIORITY must be a letter between A and Z. +Adds PRIORITY to task on line NR. If the task is already prioritized, replaces current priority with new PRIORITY. PRIORITY must be a letter between A and Z. ```shell -todo.sh pri ITEM# PRIORITY -todo.sh p ITEM# PRIORITY +todo.sh pri NR PRIORITY +todo.sh p NR PRIORITY ``` ### `replace` -Replaces task on line ITEM# with UPDATED TODO. +Replaces task on line NR with UPDATED TODO. ```shell -todo.sh replace ITEM# "UPDATED TODO" +todo.sh replace NR "UPDATED TODO" ``` ### `report` diff --git a/tests/README b/tests/README index 3f1740d2..b2025f86 100644 --- a/tests/README +++ b/tests/README @@ -125,10 +125,10 @@ Writing Tests ------------- The test script is written as a shell script. It should start -with the standard "#!/bin/bash" with copyright notices, and an +with the standard "#!/usr/bin/env bash" with copyright notices, and an assignment to variable 'test_description', like this: - #!/bin/bash + #!/usr/bin/env bash # # Copyright (c) 2005 Junio C Hamano # diff --git a/tests/actions-test-lib.sh b/tests/actions-test-lib.sh index 51d4eefc..e3cfe232 100644 --- a/tests/actions-test-lib.sh +++ b/tests/actions-test-lib.sh @@ -1,20 +1,26 @@ -#!/bin/bash +#!/usr/bin/env 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" < output; test_cmp expect output && rm -rf .todo.actions.d ' test_expect_success 'custom action (default location 2)' ' - mkdir -p .todo/actions - cp foo .todo/actions/ + mkdir -p .todo/actions && cp foo .todo/actions/ todo.sh foo > output; test_cmp expect output && rm -rf .todo/actions ' test_expect_success 'custom action (env variable)' ' - mkdir myactions - cp foo myactions/ + mkdir -p myactions && cp foo myactions/ TODO_ACTIONS_DIR=myactions todo.sh foo > output; test_cmp expect output && rm -rf myactions ' +test_expect_success 'custom action (default action)' ' + mkdir -p .todo.actions.d && cp foo2 .todo.actions.d/ + TODOTXT_DEFAULT_ACTION="foo2 foo" todo.sh > output; + test_cmp expect output && rm -rf .todo.actions.d +' + +test_todo_session 'default built-in action with multiple arguments' <>> TODOTXT_DEFAULT_ACTION='add +foo @bar baz' todo.sh +1 +foo @bar baz +TODO: 1 added. +EOF + +test_todo_session 'default custom action with multiple arguments' <>> mkdir -p .todo.actions.d && cp foo2 .todo.actions.d/ + +>>> TODOTXT_DEFAULT_ACTION='foo2 foo bar baz' todo.sh +TODO: foo-bar-baz +EOF + +: > todo.txt +export TODOTXT_DEFAULT_ACTION="add foo\\ bar \\\$HOSTNAME O\\'Really\\? \\\"quoted\\\"" +test_todo_session 'default built-in action with arguments that have special characters' <>> todo.sh +1 foo bar \$HOSTNAME O'Really? "quoted" +TODO: 1 added. +EOF + +: > todo.txt +export TODOTXT_DEFAULT_ACTION="foo2 foo\\ bar \\\$HOSTNAME O\\'Really\\? \\\"quoted\\\"" +test_todo_session 'default custom action with arguments that have special characters' <>> mkdir -p .todo.actions.d && cp foo2 .todo.actions.d/ + +>>> todo.sh +TODO: foo bar-\$HOSTNAME-O'Really?-"quoted" +EOF + test_done diff --git a/tests/t0100-code-nobacktick.sh b/tests/t0100-code-nobacktick.sh index eb42fbc8..931c56cf 100755 --- a/tests/t0100-code-nobacktick.sh +++ b/tests/t0100-code-nobacktick.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='no old-style backtick command substitution diff --git a/tests/t1000-addlist.sh b/tests/t1000-addlist.sh index c20d257d..50f0a8b4 100755 --- a/tests/t1000-addlist.sh +++ b/tests/t1000-addlist.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic add and list functionality diff --git a/tests/t1010-add-date.sh b/tests/t1010-add-date.sh index 74f5917d..38260983 100755 --- a/tests/t1010-add-date.sh +++ b/tests/t1010-add-date.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='test the date on add feature diff --git a/tests/t1020-addtolistfile.sh b/tests/t1020-addtolistfile.sh index 5d827db2..87d7b930 100755 --- a/tests/t1020-addtolistfile.sh +++ b/tests/t1020-addtolistfile.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic addto and list functionality diff --git a/tests/t1030-addto-date.sh b/tests/t1030-addto-date.sh index 2cb7400f..88743b5c 100755 --- a/tests/t1030-addto-date.sh +++ b/tests/t1030-addto-date.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='test the date on addto feature diff --git a/tests/t1040-add-priority.sh b/tests/t1040-add-priority.sh index 3adaec61..329485fe 100755 --- a/tests/t1040-add-priority.sh +++ b/tests/t1040-add-priority.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='test the priority on add feature' . ./test-lib.sh diff --git a/tests/t1100-replace.sh b/tests/t1100-replace.sh index bfec5e68..2950d995 100755 --- a/tests/t1100-replace.sh +++ b/tests/t1100-replace.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic replace functionality @@ -14,7 +14,7 @@ todo.sh add notice the daisies > /dev/null test_todo_session 'replace usage' <>> todo.sh replace adf asdfa === 1 -usage: todo.sh replace ITEM# "UPDATED ITEM" +usage: todo.sh replace NR "UPDATED ITEM" EOF test_todo_session 'basic replace' < 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/tests/t1200-pri.sh b/tests/t1200-pri.sh index fd8bd10c..b2bf387d 100755 --- a/tests/t1200-pri.sh +++ b/tests/t1200-pri.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic priority functionality ' @@ -6,7 +6,7 @@ test_description='basic priority functionality test_todo_session 'priority usage' <>> todo.sh pri B B -usage: todo.sh pri ITEM# PRIORITY[, ITEM# PRIORITY, ...] +usage: todo.sh pri NR PRIORITY [NR PRIORITY ...] note: PRIORITY must be anywhere from A to Z. === 1 EOF @@ -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/t1250-listpri.sh b/tests/t1250-listpri.sh index ff374e2f..8cce291a 100755 --- a/tests/t1250-listpri.sh +++ b/tests/t1250-listpri.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='list priority functionality ' @@ -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 < todo.txt <>> todo.sh listproj ++flowers ++roses +EOF + cat > todo.txt <>> todo.sh prepend B B -usage: todo.sh prepend ITEM# "TEXT TO PREPEND" +usage: todo.sh prepend NR "TEXT TO PREPEND" === 1 EOF diff --git a/tests/t1500-do.sh b/tests/t1500-do.sh index 79994980..6ee2c48a 100755 --- a/tests/t1500-do.sh +++ b/tests/t1500-do.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='do functionality ' @@ -8,13 +8,13 @@ test_description='do functionality test_todo_session 'do usage' <>> todo.sh do B B -usage: todo.sh do ITEM#[, ITEM#, ITEM#, ...] +usage: todo.sh do NR [NR ...] === 1 EOF -test_todo_session 'do missing ITEM#' <>> todo.sh do -usage: todo.sh do ITEM#[, ITEM#, ITEM#, ...] +usage: todo.sh do NR [NR ...] === 1 EOF @@ -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/t1600-append.sh b/tests/t1600-append.sh index af25896c..626d1f4c 100755 --- a/tests/t1600-append.sh +++ b/tests/t1600-append.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic append functionality @@ -14,7 +14,7 @@ todo.sh add notice the daisies > /dev/null test_todo_session 'append usage' <>> todo.sh append adf asdfa === 1 -usage: todo.sh append ITEM# "TEXT TO APPEND" +usage: todo.sh append NR "TEXT TO APPEND" EOF test_todo_session 'append error' <>> todo.sh depri B B -usage: todo.sh depri ITEM#[, ITEM#, ITEM#, ...] +usage: todo.sh depri NR [NR ...] === 1 EOF @@ -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..970afe08 100755 --- a/tests/t1800-del.sh +++ b/tests/t1800-del.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic del functionality ' @@ -8,7 +8,7 @@ SPACE=' ' test_todo_session 'del usage' <>> todo.sh del B -usage: todo.sh del ITEM# [TERM] +usage: todo.sh del NR [TERM] === 1 EOF @@ -60,8 +60,9 @@ 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. +=== 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. diff --git a/tests/t1850-move.sh b/tests/t1850-move.sh index 9fbdc0b6..66f95d8e 100755 --- a/tests/t1850-move.sh +++ b/tests/t1850-move.sh @@ -1,11 +1,9 @@ -#!/bin/bash +#!/usr/bin/env bash 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/tests/t1900-archive.sh b/tests/t1900-archive.sh index f8baca5e..9b5f00fb 100755 --- a/tests/t1900-archive.sh +++ b/tests/t1900-archive.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='archive functionality @@ -32,4 +32,9 @@ test_todo_session 'list after archive' <>> todo.sh archive +TODO: $HOME/todo.txt does not contain any done tasks. +EOF + test_done diff --git a/tests/t1910-deduplicate.sh b/tests/t1910-deduplicate.sh index 3617807d..186c6ec2 100755 --- a/tests/t1910-deduplicate.sh +++ b/tests/t1910-deduplicate.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='deduplicate functionality @@ -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/t1950-report.sh b/tests/t1950-report.sh index 5e762e78..9546e043 100755 --- a/tests/t1950-report.sh +++ b/tests/t1950-report.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='report functionality @@ -16,7 +16,7 @@ EOF test_todo_session 'create new report' <>> todo.sh report -TODO: $HOME/todo.txt archived. +TODO: $HOME/todo.txt does not contain any done tasks. 2009-02-13T04:40:00 5 0 TODO: Report file updated. @@ -38,7 +38,7 @@ x 2009-02-13 smell the coffee +wakeup TODO: $HOME/todo.txt archived. >>> todo.sh report -TODO: $HOME/todo.txt archived. +TODO: $HOME/todo.txt does not contain any done tasks. 2009-02-13T04:40:00 4 1 TODO: Report file updated. @@ -83,7 +83,7 @@ test_todo_session 'report is unchanged when no changes' <>> todo.sh report -TODO: $HOME/todo.txt archived. +TODO: $HOME/todo.txt does not contain any done tasks. 2009-02-13T04:40:00 3 2 TODO: Report file is up-to-date. diff --git a/tests/t2000-multiline.sh b/tests/t2000-multiline.sh index fe35100d..06776804 100755 --- a/tests/t2000-multiline.sh +++ b/tests/t2000-multiline.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Multi-line functionality' diff --git a/tests/t2100-help.sh b/tests/t2100-help.sh index 705ca1e7..ee8f57a6 100755 --- a/tests/t2100-help.sh +++ b/tests/t2100-help.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='help functionality diff --git a/tests/t2110-help-action.sh b/tests/t2110-help-action.sh index 06d910c8..f31de714 100755 --- a/tests/t2110-help-action.sh +++ b/tests/t2110-help-action.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='built-in actions help functionality @@ -32,24 +32,24 @@ test_todo_session 'multiple actions help' <<'EOF' shorthelp List the one-line usage of all built-in and add-on actions. \ - append ITEM# "TEXT TO APPEND" - app ITEM# "TEXT TO APPEND" - Adds TEXT TO APPEND to the end of the task on line ITEM#. + append NR "TEXT TO APPEND" + app NR "TEXT TO APPEND" + Adds TEXT TO APPEND to the end of the task on line NR. Quotes optional. \ EOF test_todo_session 'short and long form of action help' <<'EOF' >>> todo.sh help append - append ITEM# "TEXT TO APPEND" - app ITEM# "TEXT TO APPEND" - Adds TEXT TO APPEND to the end of the task on line ITEM#. + append NR "TEXT TO APPEND" + app NR "TEXT TO APPEND" + Adds TEXT TO APPEND to the end of the task on line NR. Quotes optional. \ >>> todo.sh help app - app ITEM# "TEXT TO APPEND" - Adds TEXT TO APPEND to the end of the task on line ITEM#. + app NR "TEXT TO APPEND" + Adds TEXT TO APPEND to the end of the task on line NR. Quotes optional. \ EOF diff --git a/tests/t2120-shorthelp.sh b/tests/t2120-shorthelp.sh index f0123b72..fe50fb05 100755 --- a/tests/t2120-shorthelp.sh +++ b/tests/t2120-shorthelp.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='shorthelp functionality @@ -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/tests/t2200-no-done-report-files.sh b/tests/t2200-no-done-report-files.sh index a0a471fb..feb74304 100755 --- a/tests/t2200-no-done-report-files.sh +++ b/tests/t2200-no-done-report-files.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='todo.sh configuration with a sole todo.txt data file. @@ -38,7 +38,7 @@ test_expect_success 'no done file has been created by the archiving' '[ ! -e don test_todo_session 'perform report' <>> todo.sh -d test.cfg report -TODO: ./todo.txt archived. +TODO: ./todo.txt does not contain any done tasks. 2009-02-13T04:40:00 0 0 TODO: Report file updated. EOF diff --git a/tests/t6000-completion.sh b/tests/t6000-completion.sh index 61cf5948..71036308 100755 --- a/tests/t6000-completion.sh +++ b/tests/t6000-completion.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash completion functionality diff --git a/tests/t6010-completion-contexts.sh b/tests/t6010-completion-contexts.sh index 5cc7862d..fe0cac05 100755 --- a/tests/t6010-completion-contexts.sh +++ b/tests/t6010-completion-contexts.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash context completion functionality diff --git a/tests/t6020-completion-projects.sh b/tests/t6020-completion-projects.sh index 11f554b5..73f7ef0a 100755 --- a/tests/t6020-completion-projects.sh +++ b/tests/t6020-completion-projects.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash project completion functionality diff --git a/tests/t6030-completion-tasks.sh b/tests/t6030-completion-tasks.sh index 35fc055a..ff3b7a8a 100755 --- a/tests/t6030-completion-tasks.sh +++ b/tests/t6030-completion-tasks.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash task number completion functionality diff --git a/tests/t6040-completion-files.sh b/tests/t6040-completion-files.sh index 94ff2ba7..aae89958 100755 --- a/tests/t6040-completion-files.sh +++ b/tests/t6040-completion-files.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash todo file completion functionality @@ -13,8 +13,8 @@ test_todo_completion 'files beginning with d after addto' 'todo.sh addto d' 'don test_todo_completion 'all files after listfile' 'todo.sh listfile ' "$FILES" test_todo_completion 'all files after lf' 'todo.sh -v lf ' "$FILES" test_todo_completion 'nothing after move' 'todo.sh move ' '' -test_todo_completion 'all files after move ITEM#' 'todo.sh move 1 ' "$FILES" -test_todo_completion 'all files after mv ITEM#' 'todo.sh mv 1 ' "$FILES" -test_todo_completion 'all files after move ITEM# DEST' 'todo.sh move 1 todo.sh ' "$FILES" +test_todo_completion 'all files after move NR' 'todo.sh move 1 ' "$FILES" +test_todo_completion 'all files after mv NR' 'todo.sh mv 1 ' "$FILES" +test_todo_completion 'all files after move NR DEST' 'todo.sh move 1 todo.sh ' "$FILES" test_done diff --git a/tests/t6050-completion-addons.sh b/tests/t6050-completion-addons.sh index 79ed0f19..20664590 100755 --- a/tests/t6050-completion-addons.sh +++ b/tests/t6050-completion-addons.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash add-on action completion functionality diff --git a/tests/t6060-completion-addon-files.sh b/tests/t6060-completion-addon-files.sh index eb65662f..07f19713 100755 --- a/tests/t6060-completion-addon-files.sh +++ b/tests/t6060-completion-addon-files.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash add-on action file completion functionality @@ -11,17 +11,17 @@ test_todo_completion 'nothing after unconfigured bar' 'todo.sh bar ' '' _todo_file1_actions='foo|bar' test_todo_completion 'all files after configured bar' 'todo.sh bar ' "$FILES" -test_todo_completion 'nothing after configured bar ITEM#' 'todo.sh bar 1 ' '' +test_todo_completion 'nothing after configured bar NR' 'todo.sh bar 1 ' '' _todo_file2_actions='baz' test_todo_completion 'nothing after configured baz' 'todo.sh baz ' '' -test_todo_completion 'all files after configured baz ITEM#' 'todo.sh baz 1 ' "$FILES" -test_todo_completion 'nothing after configured baz ITEM# MORE' 'todo.sh baz 1 more ' '' +test_todo_completion 'all files after configured baz NR' 'todo.sh baz 1 ' "$FILES" +test_todo_completion 'nothing after configured baz NR MORE' 'todo.sh baz 1 more ' '' _todo_file3_actions='biz' test_todo_completion 'nothing after configured biz' 'todo.sh biz ' '' -test_todo_completion 'nothing after configured biz ITEM#' 'todo.sh biz 1 ' '' -test_todo_completion 'all files after configured biz ITEM# MORE' 'todo.sh biz 1 more ' "$FILES" -test_todo_completion 'nothing after configured biz ITEM# EVEN MORE' 'todo.sh biz 1 even more ' '' +test_todo_completion 'nothing after configured biz NR' 'todo.sh biz 1 ' '' +test_todo_completion 'all files after configured biz NR MORE' 'todo.sh biz 1 more ' "$FILES" +test_todo_completion 'nothing after configured biz NR EVEN MORE' 'todo.sh biz 1 even more ' '' test_done diff --git a/tests/t6080-completion-path.sh b/tests/t6080-completion-path.sh index 7ec777c5..01459e57 100755 --- a/tests/t6080-completion-path.sh +++ b/tests/t6080-completion-path.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash completion with different path functionality diff --git a/tests/t6090-completion-aliases.sh b/tests/t6090-completion-aliases.sh index 7e9f00f3..2cf33632 100755 --- a/tests/t6090-completion-aliases.sh +++ b/tests/t6090-completion-aliases.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Bash completion with different aliases functionality @@ -46,15 +46,22 @@ test_todo_session 'todo 1 and 2 contexts' <>> 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' <>> 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' <>> 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 diff --git a/tests/t8010-listaddons.sh b/tests/t8010-listaddons.sh index b4e89aa9..66e992fa 100755 --- a/tests/t8010-listaddons.sh +++ b/tests/t8010-listaddons.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='listaddons functionality @@ -9,12 +9,16 @@ This test checks listing of custom actions. test_todo_session 'no custom actions' <>> todo.sh listaddons +TODO: '$TODO_ACTIONS_DIR' does not exist. +=== 1 EOF make_action "foo" test_todo_session 'one custom action' <>> todo.sh listaddons foo +-- +TODO: 1 valid addon actions found. EOF make_action "bar" @@ -26,19 +30,18 @@ bar foo ls quux +-- +TODO: 4 valid addon actions found. 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' <>> todo.sh listaddons bar ls quux +-- +TODO: 3 valid addon actions found. EOF make_action_in_folder "chuck" @@ -64,21 +67,19 @@ chuck ls norris quux +-- +TODO: 5 valid addon actions found. 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' <>> todo.sh listaddons bar chuck ls quux +-- +TODO: 4 valid addon actions found. EOF test_done diff --git a/tests/t8020-actions-help.sh b/tests/t8020-actions-help.sh index cc79bc14..09151169 100755 --- a/tests/t8020-actions-help.sh +++ b/tests/t8020-actions-help.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='custom actions help functionality @@ -20,22 +20,22 @@ make_action "quux" test_todo_session 'custom action help' <<'EOF' >>> todo.sh help foo - foo ITEM#[, ITEM#, ...] [TERM...] + foo NR [NR ...] [TERM...] This custom action does foo. \ >>> todo.sh help bar - bar ITEM#[, ITEM#, ...] [TERM...] + bar NR [NR ...] [TERM...] This custom action does bar. \ EOF test_todo_session 'multiple custom actions help' <<'EOF' >>> todo.sh help foo bar - foo ITEM#[, ITEM#, ...] [TERM...] + foo NR [NR ...] [TERM...] This custom action does foo. \ - bar ITEM#[, ITEM#, ...] [TERM...] + bar NR [NR ...] [TERM...] This custom action does bar. \ EOF @@ -46,7 +46,7 @@ TODO: No action "doesnotexist" exists. === 1 >>> todo.sh help foo doesnotexist bar - foo ITEM#[, ITEM#, ...] [TERM...] + foo NR [NR ...] [TERM...] This custom action does foo. \ TODO: No action "doesnotexist" exists. @@ -55,20 +55,20 @@ EOF test_todo_session 'mixed built-in and custom actions help' <<'EOF' >>> todo.sh help foo shorthelp bar - foo ITEM#[, ITEM#, ...] [TERM...] + foo NR [NR ...] [TERM...] This custom action does foo. \ shorthelp List the one-line usage of all built-in and add-on actions. \ - bar ITEM#[, ITEM#, ...] [TERM...] + bar NR [NR ...] [TERM...] This custom action does bar. \ EOF test_todo_session 'custom override of built-in action help' <<'EOF' >>> todo.sh help ls - ls ITEM#[, ITEM#, ...] [TERM...] + ls NR [NR ...] [TERM...] This custom action does ls. \ EOF diff --git a/tests/t9999-testsuite_example.sh b/tests/t9999-testsuite_example.sh index ef25884e..c0aa6b09 100755 --- a/tests/t9999-testsuite_example.sh +++ b/tests/t9999-testsuite_example.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='basic tests imported from previous framework ' @@ -85,12 +85,12 @@ TODO: $HOME/todo.txt archived. TODO: 5 of 5 tasks shown >>> todo.sh report -TODO: $HOME/todo.txt archived. +TODO: $HOME/todo.txt does not contain any done tasks. 2009-02-13T04:40:00 5 1 TODO: Report file updated. >>> todo.sh append g a -usage: todo.sh append ITEM# "TEXT TO APPEND" +usage: todo.sh append NR "TEXT TO APPEND" === 1 >>> todo.sh append 2 and think diff --git a/tests/test-lib.sh b/tests/test-lib.sh index 4640c207..18d00564 100644 --- a/tests/test-lib.sh +++ b/tests/test-lib.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2005 Junio C Hamano # @@ -316,64 +316,6 @@ expected exit code $1, actual ${eval_ret}" echo >&3 "" } -# test_external runs external test scripts that provide continuous -# test output about their progress, and succeeds/fails on -# zero/non-zero exit code. It outputs the test output on stdout even -# in non-verbose mode, and announces the external script with "* run -# : ..." before running it. When providing relative paths, keep in -# mind that all scripts run in "trash directory". -# Usage: test_external description command arguments... -# Example: test_external 'Perl API' perl ../path/to/test.pl -test_external () { - test "$#" -eq 3 || - error >&5 "bug in the test script: not 3 parameters to test_external" - descr="$1" - shift - if ! test_skip "$descr" "$@" - then - # Announce the script to reduce confusion about the - # test output that follows. - say_color "" " run $test_count: $descr ($*)" - # Run command; redirect its stderr to &4 as in - # test_run_, but keep its stdout on our stdout even in - # non-verbose mode. - "$@" 2>&4 - if [ "$?" = 0 ] - then - test_ok_ "$descr" - else - test_failure_ "$descr" "$@" - fi - fi -} - -# Like test_external, but in addition tests that the command generated -# no output on stderr. -test_external_without_stderr () { - # The temporary file has no (and must have no) security - # implications. - tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi - stderr="$tmp/todotxt-external-stderr.$$.tmp" - test_external "$@" 4> "$stderr" - [ -f "$stderr" ] || error "Internal error: $stderr disappeared." - descr="no stderr: $1" - shift - say >&3 "expecting no stderr from previous command" - if [ ! -s "$stderr" ]; then - rm "$stderr" - test_ok_ "$descr" - else - if [ "$verbose" = t ]; then - output=$(echo; echo Stderr is:; cat "$stderr") - else - output= - fi - # rm first in case test_failure exits. - rm "$stderr" - test_failure_ "$descr" "$@" "$output" - fi -} - # This is not among top-level (test_expect_success | test_expect_failure) # but is a prefix that can be used in the test script, like: # diff --git a/tests/testshell.sh b/tests/testshell.sh index 83abeaae..6008771f 100755 --- a/tests/testshell.sh +++ b/tests/testshell.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash test_description='Providing an interactive shell in the proper environment' . ./test-lib.sh diff --git a/todo.cfg b/todo.cfg index 0ab7d9bb..8f50e4b8 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 @@ -89,3 +96,8 @@ export REPORT_FILE="$TODO_DIR/report.txt" # just before the list output is displayed. # # export TODOTXT_FINAL_FILTER='cat' + +## default actions +# Set a default action for calling todo.sh without arguments. +# Also allows for parameters for the action. +# export TODOTXT_DEFAULT_ACTION='' diff --git a/todo.sh b/todo.sh index 810505d1..0fe19756 100755 --- a/todo.sh +++ b/todo.sh @@ -46,13 +46,13 @@ shorthelp() addm "THINGS I NEED TO DO MORE THINGS I NEED TO DO" addto DEST "TEXT TO ADD" - append|app ITEM# "TEXT TO APPEND" + append|app NR "TEXT TO APPEND" archive command [ACTIONS] deduplicate - del|rm ITEM# [TERM] - depri|dp ITEM#[, ITEM#, ITEM#, ...] - done|do ITEM#[, ITEM#, ITEM#, ...] + del|rm NR [TERM] + depri|dp NR [NR ...] + done|do NR [NR ...] help [ACTION...] list|ls [TERM...] listall|lsa [TERM...] @@ -61,10 +61,10 @@ shorthelp() listfile|lf [SRC [TERM...]] listpri|lsp [PRIORITIES] [TERM...] listproj|lsprj [TERM...] - move|mv ITEM# DEST [SRC] - prepend|prep ITEM# "TEXT TO PREPEND" - pri|p ITEM# PRIORITY[, ITEM# PRIORITY, ...] - replace ITEM# "UPDATED TODO" + move|mv NR DEST [SRC] + prepend|prep NR "TEXT TO PREPEND" + pri|p NR PRIORITY [NR PRIORITY ...] + replace NR "UPDATED TODO" report shorthelp @@ -181,9 +181,9 @@ actionsHelp() Adds a line of text to any file located in the todo.txt directory. For example, addto inbox.txt "decide about vacation" - append ITEM# "TEXT TO APPEND" - app ITEM# "TEXT TO APPEND" - Adds TEXT TO APPEND to the end of the task on line ITEM#. + append NR "TEXT TO APPEND" + app NR "TEXT TO APPEND" + Adds TEXT TO APPEND to the end of the task on line NR. Quotes optional. archive @@ -196,19 +196,19 @@ actionsHelp() deduplicate Removes duplicate lines from todo.txt. - del ITEM# [TERM] - rm ITEM# [TERM] - Deletes the task on line ITEM# in todo.txt. + del NR [TERM] + rm NR [TERM] + Deletes the task on line NR in todo.txt. If TERM specified, deletes only TERM from the task. - depri ITEM#[, ITEM#, ITEM#, ...] - dp ITEM#[, ITEM#, ITEM#, ...] + depri NR [NR ...] + dp NR [NR ...] Deprioritizes (removes the priority) from the task(s) - on line ITEM# in todo.txt. + on line NR in todo.txt. - done ITEM#[, ITEM#, ITEM#, ...] - do ITEM#[, ITEM#, ITEM#, ...] - Marks task(s) on line ITEM# as done in todo.txt. + done NR [NR ...] + do NR [NR ...] + Marks task(s) on line NR as done in todo.txt. help [ACTION...] Display help about usage, options, built-in and add-on actions, @@ -255,7 +255,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 @@ -267,26 +267,26 @@ actionsHelp() todo.txt. If TERM specified, considers only tasks that contain TERM(s). - move ITEM# DEST [SRC] - mv ITEM# DEST [SRC] - Moves a line from source text file (SRC) to destination text file (DEST). + move NR DEST [SRC] + mv NR DEST [SRC] + Moves the line NR from source text file (SRC) to destination text file (DEST). Both source and destination file must be located in the directory defined in the configuration directory. When SRC is not defined it's by default todo.txt. - prepend ITEM# "TEXT TO PREPEND" - prep ITEM# "TEXT TO PREPEND" - Adds TEXT TO PREPEND to the beginning of the task on line ITEM#. + prepend NR "TEXT TO PREPEND" + prep NR "TEXT TO PREPEND" + Adds TEXT TO PREPEND to the beginning of the task on line NR. Quotes optional. - pri ITEM# PRIORITY - p ITEM# PRIORITY - Adds PRIORITY to task on line ITEM#. If the task is already + pri NR PRIORITY + p NR PRIORITY + Adds PRIORITY to task on line NR. If the task is already prioritized, replaces current priority with new PRIORITY. PRIORITY must be a letter between A and Z. - replace ITEM# "UPDATED TODO" - Replaces task on line ITEM# with UPDATED TODO. + replace NR "UPDATED TODO" + Replaces task on line NR with UPDATED TODO. report Adds the number of open tasks and done tasks to report.txt. @@ -348,7 +348,7 @@ dieWithHelp() case "$1" in help) help;; shorthelp) shorthelp;; - esac + esac >&2 shift die "$@" @@ -356,7 +356,7 @@ dieWithHelp() die() { - echo "$*" + echo >&2 "$*" exit 1 } @@ -364,13 +364,12 @@ confirm() { [ "$TODOTXT_FORCE" = 0 ] || return 0 - printf %s "${1:?}? (y/n) " - local answer - if (( BASH_VERSINFO[0] > 4 || BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 1 )); then - read -r -e -N 1 answer - else - read -r -e answer + local readArgs=(-e -r) + if [ -n "${BASH_VERSINFO:-}" ] && ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 1) )); then + readArgs+=(-N 1) # Bash 4.1+ supports -N nchars fi + local answer + read -rp "${1:?}? (y/n) " "${readArgs[@]}" answer echo [ "$answer" = "y" ] } @@ -438,62 +437,74 @@ getNewtodo() replaceOrPrepend() { - action=$1; shift + action=$1; shift + case "$action" in + replace) + backref= + querytext="Replacement: " + ;; + prepend) + backref=' &' + querytext="Prepend: " + ;; + esac + shift; item=$1; shift + getTodo "$item" + + if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then + 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\}' + 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="$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/^${originalPriority}${originalPrepdate}//" -e "$item s|^.*|${priority}${prepdate}${input}${backref}|" "$TODO_FILE" + if [ "$TODOTXT_VERBOSE" -gt 0 ]; then + getNewtodo "$item" case "$action" in replace) - backref= - querytext="Replacement: " + echo "$item $todo" + echo "TODO: Replaced task with:" + echo "$item $newtodo" ;; prepend) - backref=' &' - querytext="Prepend: " + echo "$item $newtodo" ;; esac - shift; item=$1; shift - getTodo "$item" - - if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then - echo -n "$querytext" - read -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 [ -n "$prepdate" ] && [ "$action" = "replace" ] && [ -n "$(echo "$input" | sed -e "s/${priAndDateExpr}.*/\\1\\2/")" ]; then - # If the replaced text starts with a [priority +] date, it will replace - # the existing date, too. - prepdate= - 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" - if [ "$TODOTXT_VERBOSE" -gt 0 ]; then - getNewtodo "$item" - case "$action" in - replace) - echo "$item $todo" - echo "TODO: Replaced task with:" - echo "$item $newtodo" - ;; - prepend) - echo "$item $newtodo" - ;; - esac - fi + fi } fixMissingEndOfLine() { # Parameters: $1: todo file; empty means $TODO_FILE. - sed -i.bak -e '$a\' "${1:-$TODO_FILE}" + todo_path="${1:-$TODO_FILE}" + [[ -f $todo_path && $(tail -c1 "$todo_path") ]] && echo "" >> "$todo_path" } uppercasePriority() @@ -720,6 +731,7 @@ done # === SANITY CHECKS (thanks Karl!) === [ -r "$TODOTXT_CFG_FILE" ] || dieWithHelp "$1" "Fatal Error: Cannot read configuration file ${TODOTXT_CFG_FILE:-${configFileLocations[0]}}" +# shellcheck source=./todo.cfg . "$TODOTXT_CFG_FILE" # === APPLY OVERRIDES @@ -757,7 +769,13 @@ if [ -n "$OVR_TODOTXT_FINAL_FILTER" ]; then TODOTXT_FINAL_FILTER="$OVR_TODOTXT_FINAL_FILTER" fi -ACTION=${1:-$TODOTXT_DEFAULT_ACTION} +isDefaultAction= +if [ -n "$1" ]; then + ACTION=$1 +else + ACTION=$TODOTXT_DEFAULT_ACTION + isDefaultAction=t +fi [ -z "$ACTION" ] && usage [ -d "$TODO_DIR" ] || mkdir -p "$TODO_DIR" 2>/dev/null || dieWithHelp "$1" "Fatal Error: $TODO_DIR is not a directory" @@ -817,12 +835,6 @@ _addto() fi } -shellquote() -{ - local -r qq=$'\'' - printf "%s\n" "'${1//\'/${qq}\\${qq}${qq}}'" -} - filtercommand() { filter=${1:-} @@ -830,18 +842,20 @@ filtercommand() post_filter=${1:-} shift - for search_term; do - # See if the first character of $search_term is a dash - if [ "${search_term:0:1}" != '-' ]; then - # First character isn't a dash: hide lines that don't match - # this $search_term - filter="${filter:-}${filter:+ | }grep -i $(shellquote "$search_term")" + for search_term + do + ## See if the first character of $search_term is a dash + if [ "${search_term:0:1}" != '-' ] + then + ## First character isn't a dash: hide lines that don't match + ## this $search_term + 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}")" + ## Remove the first character (-) before adding to our filter command + printf -v filter '%sgrep -v -i %q' "${filter:-}${filter:+ | }" "${search_term:1}" fi done @@ -855,10 +869,10 @@ filtercommand() _list() { local FILE="$1" - # If the file starts with a "/" use absolute path. Otherwise, - # try to find it in either $TODO_DIR or using a relative path - if [ "${1:0:1}" == / ]; then - # Absolute path + ## If the file starts with a "/" use absolute path. Otherwise, + ## try to find it in either $TODO_DIR or using a relative path + if [ "${1:0:1}" == / ] && [ -f "$FILE" ]; then + ## Absolute path src="$FILE" elif [ -f "$TODO_DIR/$FILE" ]; then # Path relative to todo.sh directory @@ -894,7 +908,7 @@ getPadding() _format() { # Parameters: $1: todo input file; when empty formats stdin - # $2: ITEM# number width; if empty auto-detects from $1 / $TODO_FILE. + # $2: NR number width; if empty auto-detects from $1 / $TODO_FILE. # Precondition: None # Postcondition: $NUMTASKS and $TOTALTASKS contain statistics (unless $TODOTXT_VERBOSE=0). @@ -912,16 +926,18 @@ _format() fi items=$( if [ -n "$FILE" ]; then + # shellcheck disable=SC2283 sed = "$FILE" else + # shellcheck disable=SC2283 sed = - fi \ - | sed -e ''' + fi \ + | sed -e ' N s/^/ / s/ *\([ 0-9]\{'"$PADDING"',\}\)\n/\1 / /^[ 0-9]\{1,\} *$/d - ''' + ' ) # Build and apply the filter. @@ -933,15 +949,15 @@ _format() fi filtered_items=$( echo -n "$filtered_items" \ - | sed ''' + | sed ' s/^ /00000/; s/^ /0000/; s/^ /000/; s/^ /00/; s/^ /0/; - ''' \ + ' \ | eval "${TODOTXT_SORT_COMMAND}" \ - | awk ''' + | awk ' function highlight(colorVar, color) { color = ENVIRON[colorVar] gsub(/\\+033/, "\033", color) @@ -996,13 +1012,13 @@ _format() } printf "%s\n", end_clr } - ''' \ - | sed ''' + ' \ + | sed ' s/'"${HIDE_PROJECTS_SUBSTITUTION:-^}"'//g s/'"${HIDE_CONTEXTS_SUBSTITUTION:-^}"'//g s/'"${HIDE_CUSTOM_SUBSTITUTION:-^}"'//g - ''' \ - | eval "${TODOTXT_FINAL_FILTER}" \ + ' \ + | eval ${TODOTXT_FINAL_FILTER} \ ) [ -n "$filtered_items" ] && echo "$filtered_items" @@ -1031,7 +1047,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:]') @@ -1043,22 +1070,27 @@ action=$(printf "%s\n" "$ACTION" | tr '[:upper:]' '[:lower:]') if [ "$action" == "command" ]; then # Get rid of "command" from arguments list 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" ]; then + ## Reset action to new first argument + action=$( printf "%s\n" "$1" | tr '[:upper:]' '[:lower:]' ) +elif hasCustomAction "$TODO_ACTIONS_DIR/$action" "$action" +then "$TODO_ACTIONS_DIR/$action/$action" "$@" exit $? -elif [ -d "$TODO_ACTIONS_DIR" ] && [ -x "$TODO_ACTIONS_DIR/$action" ]; then +elif hasCustomAction "$TODO_ACTIONS_DIR" "$action" +then "$TODO_ACTIONS_DIR/$action" "$@" exit $? +elif [ "$isDefaultAction" ] && [ -n "$TODOTXT_DEFAULT_ACTION" ]; then + # Recursive invocation with the contents of the default action parsed as a + # command-line. + eval "exec \"\${BASH_SOURCE[0]}\" $TODOTXT_DEFAULT_ACTION" fi # Only run if $action isn't found in .todo.actions.d 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 @@ -1069,8 +1101,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 @@ -1089,10 +1120,11 @@ case $action in IFS=$SAVEIFS ;; -"addto") - [ -z "$2" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\"" +"addto" ) + errmsg="usage: $TODO_SH addto DEST \"TODO ITEM\"" + [ -z "$2" ] && die "$errmsg" dest="$TODO_DIR/$2" - [ -z "$3" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\"" + [ -z "$3" ] && die "$errmsg" shift shift input=$* @@ -1104,14 +1136,13 @@ case $action in fi ;; -"append" | "app") - errmsg="usage: $TODO_SH append ITEM# \"TEXT TO APPEND\"" +"append" | "app" ) + errmsg="usage: $TODO_SH append NR \"TEXT TO APPEND\"" shift; item=$1; shift 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 @@ -1134,17 +1165,22 @@ case $action in "archive") # defragment blank lines sed -i.bak -e '/./!d' "$TODO_FILE" - [ "$TODOTXT_VERBOSE" -gt 0 ] && grep "^x " "$TODO_FILE" - grep "^x " "$TODO_FILE" >> "$DONE_FILE" - sed -i.bak '/^x /d' "$TODO_FILE" - if [ "$TODOTXT_VERBOSE" -gt 0 ]; then - echo "TODO: $TODO_FILE archived." + if grep "^x " "$TODO_FILE" >> "$DONE_FILE"; then + [ "$TODOTXT_VERBOSE" -gt 0 ] && grep "^x " "$TODO_FILE" + sed -i.bak '/^x /d' "$TODO_FILE" + if [ "$TODOTXT_VERBOSE" -gt 0 ]; then + echo "TODO: $TODO_FILE archived." + fi + else + if [ "$TODOTXT_VERBOSE" -gt 0 ]; then + echo "TODO: $TODO_FILE does not contain any done tasks." + fi fi ;; "del" | "rm") # replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1 - errmsg="usage: $TODO_SH del ITEM# [TERM]" + errmsg="usage: $TODO_SH del NR [TERM]" item=$2 getTodo "$item" @@ -1162,7 +1198,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 \ @@ -1185,37 +1221,41 @@ case $action in fi ;; -"depri" | "dp") - errmsg="usage: $TODO_SH depri ITEM#[, ITEM#, ITEM#, ...]" +"depri" | "dp" ) + errmsg="usage: $TODO_SH depri NR [NR ...]" shift; [ $# -eq 0 ] && die "$errmsg" # 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" - if [[ "$todo" = \(?\)\ * ]]; then - sed -i.bak -e "${item}s/^(.) //" "$TODO_FILE" - if [ "$TODOTXT_VERBOSE" -gt 0 ]; then - getNewtodo "$item" - echo "$item $newtodo" - echo "TODO: $item deprioritized." - fi - else - echo "TODO: $item is not prioritized." - fi + if [[ "$todo" = \(?\)\ * ]]; then + sed -i.bak -e "${item}s/^(.) //" "$TODO_FILE" + if [ "$TODOTXT_VERBOSE" -gt 0 ]; then + getNewtodo "$item" + echo "$item $newtodo" + echo "TODO: $item deprioritized." + fi + else + echo >&2 "TODO: $item is not prioritized." + status=1 + fi done + exit $status ;; -"do" | "done") - errmsg="usage: $TODO_SH do ITEM#[, ITEM#, ITEM#, ...]" +"do" | "done" ) + errmsg="usage: $TODO_SH do NR [NR ...]" # shift so we get arguments to the do request shift; [ $# -eq 0 ] && die "$errmsg" # 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" @@ -1231,15 +1271,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") @@ -1319,14 +1361,14 @@ 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" "$@" ;; "move" | "mv") # replace moved line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1 - errmsg="usage: $TODO_SH mv ITEM# DEST [SRC]" + errmsg="usage: $TODO_SH mv NR DEST [SRC]" item=$2 dest="$TODO_DIR/$3" src="$TODO_DIR/$4" @@ -1355,22 +1397,23 @@ case $action in echo "TODO: $item moved from '$src' to '$dest'." fi else - echo "TODO: No tasks moved." + die "TODO: No tasks moved." fi ;; -"prepend" | "prep") - errmsg="usage: $TODO_SH prepend ITEM# \"TEXT TO PREPEND\"" +"prepend" | "prep" ) + errmsg="usage: $TODO_SH prepend NR \"TEXT TO PREPEND\"" replaceOrPrepend 'prepend' "$@" ;; "pri" | "p") shift - while [ $# -gt 0 ]; do + status=0 + while [ "$#" -gt 0 ] ; do item=$1 newpri=$(printf "%s\n" "$2" | tr '[:lower:]' '[:upper:]') - errmsg="usage: $TODO_SH pri ITEM# PRIORITY[, ITEM# PRIORITY, ...] + errmsg="usage: $TODO_SH pri NR PRIORITY [NR PRIORITY ...] note: PRIORITY must be anywhere from A to Z." [ $# -lt 2 ] && die "$errmsg" @@ -1397,14 +1440,16 @@ 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") - errmsg="usage: $TODO_SH replace ITEM# \"UPDATED ITEM\"" +"replace" ) + errmsg="usage: $TODO_SH replace NR \"UPDATED ITEM\"" replaceOrPrepend 'replace' "$@" ;; @@ -1467,8 +1512,8 @@ 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" + if [ $deduplicateNum -eq 0 ]; then + die "TODO: No duplicate tasks found" else echo "TODO: $deduplicateNum duplicate task(s) removed" fi @@ -1477,13 +1522,27 @@ note: PRIORITY must be anywhere from A to Z." "listaddons") if [ -d "$TODO_ACTIONS_DIR" ]; then cd -- "$TODO_ACTIONS_DIR" || exit $? - for action in *; do + actionsCnt=0 + for action in * + do if [ -f "$action" ] && [ -x "$action" ]; then echo "$action" + ((actionsCnt+=1)) elif [ -d "$action" ] && [ -x "$action/$action" ]; then echo "$action" + ((actionsCnt+=1)) fi done + if ! [ "$actionsCnt" -gt 0 ]; then + die "TODO: '$TODO_ACTIONS_DIR' does not contain valid actions." + else + if [ "$TODOTXT_VERBOSE" -gt 0 ]; then + echo "--" + echo "TODO: $actionsCnt valid addon actions found." + fi + fi + else + die "TODO: '$TODO_ACTIONS_DIR' does not exist." fi ;; diff --git a/todo_completion b/todo_completion index 9c069845..12610da3 100644 --- a/todo_completion +++ b/todo_completion @@ -18,15 +18,15 @@ _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" elif [[ $COMP_CWORD -gt 2 && ( \ "${COMP_WORDS[COMP_CWORD-2]}" =~ ^($MOVE_COMMAND_PATTERN${_todo_file2_actions:+|${_todo_file2_actions}})$ || \ "${COMP_WORDS[COMP_CWORD-3]}" =~ ^($MOVE_COMMAND_PATTERN${_todo_file3_actions:+|${_todo_file3_actions}})$ ) ]]; then - # "move ITEM# DEST [SRC]" has file arguments on positions 2 and 3. - completions=$(eval TODOTXT_VERBOSE=0 "$_todo_sh" command listfile 2>/dev/null) + # "move NR DEST [SRC]" has file arguments on positions 2 and 3. + completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listfile 2>/dev/null) else case "$prev" in command) @@ -102,22 +102,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