diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 34020845..0112da62 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -10,7 +10,7 @@ **What is the expected behavior?** -**Which versions todo.sh are you using?** +**Which version of todo.sh are you using?** > Run `todo.sh -V` @@ -19,4 +19,3 @@ **Which version of bash are you using?** > Run `bash --version` - diff --git a/GEN-VERSION-FILE b/GEN-VERSION-FILE index aec88dfa..74935102 100755 --- a/GEN-VERSION-FILE +++ b/GEN-VERSION-FILE @@ -14,13 +14,13 @@ fi VN=$(expr "$VN" : v*'\(.*\)') -if test -r $VF +if test -r "$VF" then - VC=$(sed -e 's/^VERSION=//' <$VF) + VC=$(sed -e 's/^VERSION=//' <"$VF") else VC=unset fi test "$VN" = "$VC" || { echo >&2 "VERSION=$VN" - echo "VERSION=$VN" >$VF + echo "VERSION=$VN" >"$VF" } diff --git a/todo.sh b/todo.sh index 130ed3f2..0fe19756 100755 --- a/todo.sh +++ b/todo.sh @@ -3,20 +3,21 @@ # === HEAVY LIFTING === shopt -s extglob extquote -# NOTE: Todo.sh requires a configuration file to run. +# NOTE: Todo.sh requires a configuration file to run. # Place it in one of the default locations or use the -d option for a custom location. [ -f VERSION-FILE ] && . VERSION-FILE || VERSION="@DEV_VERSION@" -version() { +version() +{ cat <<-EndVersion TODO.TXT Command Line Interface v$VERSION - Homepage: http://todotxt.org - Code repository: https://github.com/todotxt/todo.txt-cli/ + Homepage: http://todotxt.org/ + Code repository: https://github.com/todotxt/todo.txt-cli Contributors: https://github.com/todotxt/todo.txt-cli/graphs/contributors License: https://github.com/todotxt/todo.txt-cli/blob/master/LICENSE EndVersion - exit 1 + exit 0 } # Set script name and full path early. @@ -156,8 +157,8 @@ $indentedJoinedConfigFileLocations TODOTXT_SIGIL_AFTER_PATTERN="" optionally allow chars after +p / @c EndVerboseHelp - actionsHelp - addonHelp + actionsHelp + addonHelp } actionsHelp() @@ -229,7 +230,7 @@ actionsHelp() listall [TERM...] lsa [TERM...] Displays all the lines in todo.txt AND done.txt that contain TERM(s) - sorted by priority with line numbers. Hides all tasks that + sorted by priority with line numbers. Hides all tasks that contain TERM(s) preceded by a minus sign (i.e. -TERM). If no TERM specified, lists entire todo.txt AND done.txt concatenated and sorted. @@ -245,7 +246,7 @@ actionsHelp() listfile [SRC [TERM...]] lf [SRC [TERM...]] Displays all the lines in SRC file located in the todo.txt directory, - sorted by priority with line numbers. If TERM specified, lists + sorted by priority with line numbers. If TERM specified, lists all lines that contain TERM(s) in SRC file. Hides all tasks that contain TERM(s) preceded by a minus sign (i.e. -TERM). Without any arguments, the names of all text files in the todo.txt @@ -299,22 +300,21 @@ actionsHelp() addonHelp() { if [ -d "$TODO_ACTIONS_DIR" ]; then - didPrintAddonActionsHeader= - for action in "$TODO_ACTIONS_DIR"/* - do + local didPrintAddonActionsHeader + for action in "$TODO_ACTIONS_DIR"/*; do if [ -f "$action" ] && [ -x "$action" ]; then - if [ ! "$didPrintAddonActionsHeader" ]; then + if [ -z "$didPrintAddonActionsHeader" ]; then cat <<-EndAddonActionsHeader - Add-on Actions: - EndAddonActionsHeader + Add-on Actions: + EndAddonActionsHeader didPrintAddonActionsHeader=1 fi "$action" usage elif [ -d "$action" ] && [ -x "$action"/"$(basename "$action")" ]; then - if [ ! "$didPrintAddonActionsHeader" ]; then + if [ -z "$didPrintAddonActionsHeader" ]; then cat <<-EndAddonActionsHeader - Add-on Actions: - EndAddonActionsHeader + Add-on Actions: + EndAddonActionsHeader didPrintAddonActionsHeader=1 fi "$action"/"$(basename "$action")" usage @@ -325,8 +325,7 @@ addonHelp() actionUsage() { - for actionName - do + for actionName; do action="${TODO_ACTIONS_DIR}/${actionName}" if [ -f "$action" ] && [ -x "$action" ]; then "$action" usage @@ -334,7 +333,7 @@ actionUsage() "$action"/"$(basename "$action")" usage else builtinActionUsage=$(actionsHelp | sed -n -e "/^ ${actionName//\//\\/} /,/^\$/p" -e "/^ ${actionName//\//\\/}$/,/^\$/p") - if [ "$builtinActionUsage" ]; then + if [ -n "$builtinActionUsage" ]; then echo "$builtinActionUsage" echo else @@ -415,11 +414,12 @@ getTodo() local item=$1 [ -z "$item" ] && die "$errmsg" - [ "${item//[0-9]/}" ] && die "$errmsg" + [ -n "${item//[0-9]/}" ] && die "$errmsg" todo=$(sed "$item!d" "${2:-$TODO_FILE}") [ -z "$todo" ] && die "$(getPrefix "$2"): No task $item." } + getNewtodo() { # Parameters: $1: task number @@ -429,7 +429,7 @@ getNewtodo() local item=$1 [ -z "$item" ] && die "Programming error: $item should exist." - [ "${item//[0-9]/}" ] && die "Programming error: $item should be numeric." + [ -n "${item//[0-9]/}" ] && die "Programming error: $item should be numeric." newtodo=$(sed "$item!d" "${2:-$TODO_FILE}") [ -z "$newtodo" ] && die "$(getPrefix "$2"): No updated task $item." @@ -496,7 +496,7 @@ replaceOrPrepend() echo "$item $newtodo" ;; esac - + fi } @@ -513,14 +513,13 @@ uppercasePriority() # Postcondition: Modifies $input. lower=( {a..z} ) upper=( {A..Z} ) - for ((i=0; i<26; i++)) - do + for ((i=0; i<26; i++)); do upperPriority="${upperPriority};s/^[(]${lower[i]}[)]/(${upper[i]})/" done input=$(echo "$input" | sed "$upperPriority") } -#Preserving environment variables so they don't get clobbered by the config file +# Preserving environment variables so they don't get clobbered by the config file OVR_TODOTXT_AUTO_ARCHIVE="$TODOTXT_AUTO_ARCHIVE" OVR_TODOTXT_FORCE="$TODOTXT_FORCE" OVR_TODOTXT_PRESERVE_LINE_NUMBERS="$TODOTXT_PRESERVE_LINE_NUMBERS" @@ -537,38 +536,35 @@ OVR_TODOTXT_FINAL_FILTER="$TODOTXT_FINAL_FILTER" export GREP_OPTIONS="" # == PROCESS OPTIONS == -while getopts ":fhpcnNaAtTvVx+@Pd:" Option -do +while getopts ":fhpcnNaAtTvVx+@Pd:" Option; do case $Option in '@') - ## HIDE_CONTEXT_NAMES starts at zero (false); increment it to one - ## (true) the first time this flag is seen. Each time the flag - ## is seen after that, increment it again so that an even - ## number shows context names and an odd number hides context - ## names. + # HIDE_CONTEXT_NAMES starts at zero (false); increment it to one + # (true) the first time this flag is seen. Each time the flag + # is seen after that, increment it again so that an even + # number shows context names and an odd number hides context + # names. : $(( HIDE_CONTEXT_NAMES++ )) - if [ $(( HIDE_CONTEXT_NAMES % 2 )) -eq 0 ] - then - ## Zero or even value -- show context names + if (( HIDE_CONTEXT_NAMES % 2 == 0 )); then + # Zero or even value -- show context names unset HIDE_CONTEXTS_SUBSTITUTION else - ## One or odd value -- hide context names + # One or odd value -- hide context names export HIDE_CONTEXTS_SUBSTITUTION='[[:space:]]@[[:graph:]]\{1,\}' fi ;; '+') - ## HIDE_PROJECT_NAMES starts at zero (false); increment it to one - ## (true) the first time this flag is seen. Each time the flag - ## is seen after that, increment it again so that an even - ## number shows project names and an odd number hides project - ## names. + # HIDE_PROJECT_NAMES starts at zero (false); increment it to one + # (true) the first time this flag is seen. Each time the flag + # is seen after that, increment it again so that an even + # number shows project names and an odd number hides project + # names. : $(( HIDE_PROJECT_NAMES++ )) - if [ $(( HIDE_PROJECT_NAMES % 2 )) -eq 0 ] - then - ## Zero or even value -- show project names + if (( HIDE_PROJECT_NAMES % 2 == 0 )); then + # Zero or even value -- show project names unset HIDE_PROJECTS_SUBSTITUTION else - ## One or odd value -- hide project names + # One or odd value -- hide project names export HIDE_PROJECTS_SUBSTITUTION='[[:space:]][+][[:graph:]]\{1,\}' fi ;; @@ -604,18 +600,17 @@ do OVR_TODOTXT_PLAIN=1 ;; P) - ## HIDE_PRIORITY_LABELS starts at zero (false); increment it to one - ## (true) the first time this flag is seen. Each time the flag - ## is seen after that, increment it again so that an even - ## number shows priority labels and an odd number hides priority - ## labels. + # HIDE_PRIORITY_LABELS starts at zero (false); increment it to one + # (true) the first time this flag is seen. Each time the flag + # is seen after that, increment it again so that an even + # number shows priority labels and an odd number hides priority + # labels. : $(( HIDE_PRIORITY_LABELS++ )) - if [ $(( HIDE_PRIORITY_LABELS % 2 )) -eq 0 ] - then - ## Zero or even value -- show priority labels + if (( HIDE_PRIORITY_LABELS % 2 == 0 )); then + # Zero or even value -- show priority labels unset HIDE_PRIORITY_SUBSTITUTION else - ## One or odd value -- hide priority labels + # One or odd value -- hide priority labels export HIDE_PRIORITY_SUBSTITUTION="([A-Z])[[:space:]]" fi ;; @@ -654,9 +649,9 @@ TODOTXT_SORT_COMMAND=${TODOTXT_SORT_COMMAND:-env LC_COLLATE=C sort -f -k2} TODOTXT_DISABLE_FILTER=${TODOTXT_DISABLE_FILTER:-} TODOTXT_FINAL_FILTER=${TODOTXT_FINAL_FILTER:-cat} TODOTXT_GLOBAL_CFG_FILE=${TODOTXT_GLOBAL_CFG_FILE:-/etc/todo/config} -TODOTXT_SIGIL_BEFORE_PATTERN=${TODOTXT_SIGIL_BEFORE_PATTERN:-} # Allow any other non-whitespace entity before +project and @context; should be an optional match; example: \(w:\)\{0,1\} to allow w:@context. -TODOTXT_SIGIL_VALID_PATTERN=${TODOTXT_SIGIL_VALID_PATTERN:-.*} # Limit the valid characters (from the default any non-whitespace sequence) for +project and @context; example: [a-zA-Z]\{3,\} to only allow alphabetic ones that are at least three characters long. -TODOTXT_SIGIL_AFTER_PATTERN=${TODOTXT_SIGIL_AFTER_PATTERN:-} # Allow any other non-whitespace entity after +project and @context; should be an optional match; example: )\{0,1\} to allow (with the corresponding TODOTXT_SIGIL_BEFORE_PATTERN) enclosing in parentheses. +TODOTXT_SIGIL_BEFORE_PATTERN=${TODOTXT_SIGIL_BEFORE_PATTERN:-} # Allow any other non-whitespace entity before +project and @context; should be an optional match; example: \(w:\)\{0,1\} to allow w:@context. +TODOTXT_SIGIL_VALID_PATTERN=${TODOTXT_SIGIL_VALID_PATTERN:-.*} # Limit the valid characters (from the default any non-whitespace sequence) for +project and @context; example: [a-zA-Z]\{3,\} to only allow alphabetic ones that are at least three characters long. +TODOTXT_SIGIL_AFTER_PATTERN=${TODOTXT_SIGIL_AFTER_PATTERN:-} # Allow any other non-whitespace entity after +project and @context; should be an optional match; example: )\{0,1\} to allow (with the corresponding TODOTXT_SIGIL_BEFORE_PATTERN) enclosing in parentheses. # Export all TODOTXT_* variables export "${!TODOTXT_@}" @@ -712,33 +707,27 @@ configFileLocations=( "$TODOTXT_GLOBAL_CFG_FILE" ) -[ -e "$TODOTXT_CFG_FILE" ] || for CFG_FILE_ALT in "${configFileLocations[@]}" -do - if [ -e "$CFG_FILE_ALT" ] - then +[ -e "$TODOTXT_CFG_FILE" ] || for CFG_FILE_ALT in "${configFileLocations[@]}"; do + if [ -e "$CFG_FILE_ALT" ]; then TODOTXT_CFG_FILE="$CFG_FILE_ALT" break fi done -if [ -z "$TODO_ACTIONS_DIR" ] || [ ! -d "$TODO_ACTIONS_DIR" ] -then +if [ -z "$TODO_ACTIONS_DIR" ] || [ ! -d "$TODO_ACTIONS_DIR" ]; then TODO_ACTIONS_DIR="$HOME/.todo/actions" export TODO_ACTIONS_DIR fi [ -d "$TODO_ACTIONS_DIR" ] || for TODO_ACTIONS_DIR_ALT in \ "$HOME/.todo.actions.d" \ - "${XDG_CONFIG_HOME:-$HOME/.config}/todo/actions" -do - if [ -d "$TODO_ACTIONS_DIR_ALT" ] - then + "${XDG_CONFIG_HOME:-$HOME/.config}/todo/actions"; do + if [ -d "$TODO_ACTIONS_DIR_ALT" ]; then TODO_ACTIONS_DIR="$TODO_ACTIONS_DIR_ALT" break fi done - # === SANITY CHECKS (thanks Karl!) === [ -r "$TODOTXT_CFG_FILE" ] || dieWithHelp "$1" "Fatal Error: Cannot read configuration file ${TODOTXT_CFG_FILE:-${configFileLocations[0]}}" @@ -746,38 +735,38 @@ done . "$TODOTXT_CFG_FILE" # === APPLY OVERRIDES -if [ -n "$OVR_TODOTXT_AUTO_ARCHIVE" ] ; then - TODOTXT_AUTO_ARCHIVE="$OVR_TODOTXT_AUTO_ARCHIVE" +if [ -n "$OVR_TODOTXT_AUTO_ARCHIVE" ]; then + TODOTXT_AUTO_ARCHIVE="$OVR_TODOTXT_AUTO_ARCHIVE" fi -if [ -n "$OVR_TODOTXT_FORCE" ] ; then - TODOTXT_FORCE="$OVR_TODOTXT_FORCE" +if [ -n "$OVR_TODOTXT_FORCE" ]; then + TODOTXT_FORCE="$OVR_TODOTXT_FORCE" fi -if [ -n "$OVR_TODOTXT_PRESERVE_LINE_NUMBERS" ] ; then - TODOTXT_PRESERVE_LINE_NUMBERS="$OVR_TODOTXT_PRESERVE_LINE_NUMBERS" +if [ -n "$OVR_TODOTXT_PRESERVE_LINE_NUMBERS" ]; then + TODOTXT_PRESERVE_LINE_NUMBERS="$OVR_TODOTXT_PRESERVE_LINE_NUMBERS" fi -if [ -n "$OVR_TODOTXT_PLAIN" ] ; then - TODOTXT_PLAIN="$OVR_TODOTXT_PLAIN" +if [ -n "$OVR_TODOTXT_PLAIN" ]; then + TODOTXT_PLAIN="$OVR_TODOTXT_PLAIN" fi -if [ -n "$OVR_TODOTXT_DATE_ON_ADD" ] ; then - TODOTXT_DATE_ON_ADD="$OVR_TODOTXT_DATE_ON_ADD" +if [ -n "$OVR_TODOTXT_DATE_ON_ADD" ]; then + TODOTXT_DATE_ON_ADD="$OVR_TODOTXT_DATE_ON_ADD" fi -if [ -n "$OVR_TODOTXT_PRIORITY_ON_ADD" ] ; then +if [ -n "$OVR_TODOTXT_PRIORITY_ON_ADD" ]; then TODOTXT_PRIORITY_ON_ADD="$OVR_TODOTXT_PRIORITY_ON_ADD" fi -if [ -n "$OVR_TODOTXT_DISABLE_FILTER" ] ; then - TODOTXT_DISABLE_FILTER="$OVR_TODOTXT_DISABLE_FILTER" +if [ -n "$OVR_TODOTXT_DISABLE_FILTER" ]; then + TODOTXT_DISABLE_FILTER="$OVR_TODOTXT_DISABLE_FILTER" fi -if [ -n "$OVR_TODOTXT_VERBOSE" ] ; then - TODOTXT_VERBOSE="$OVR_TODOTXT_VERBOSE" +if [ -n "$OVR_TODOTXT_VERBOSE" ]; then + TODOTXT_VERBOSE="$OVR_TODOTXT_VERBOSE" fi -if [ -n "$OVR_TODOTXT_DEFAULT_ACTION" ] ; then - TODOTXT_DEFAULT_ACTION="$OVR_TODOTXT_DEFAULT_ACTION" +if [ -n "$OVR_TODOTXT_DEFAULT_ACTION" ]; then + TODOTXT_DEFAULT_ACTION="$OVR_TODOTXT_DEFAULT_ACTION" fi -if [ -n "$OVR_TODOTXT_SORT_COMMAND" ] ; then - TODOTXT_SORT_COMMAND="$OVR_TODOTXT_SORT_COMMAND" +if [ -n "$OVR_TODOTXT_SORT_COMMAND" ]; then + TODOTXT_SORT_COMMAND="$OVR_TODOTXT_SORT_COMMAND" fi -if [ -n "$OVR_TODOTXT_FINAL_FILTER" ] ; then - TODOTXT_FINAL_FILTER="$OVR_TODOTXT_FINAL_FILTER" +if [ -n "$OVR_TODOTXT_FINAL_FILTER" ]; then + TODOTXT_FINAL_FILTER="$OVR_TODOTXT_FINAL_FILTER" fi isDefaultAction= @@ -788,9 +777,9 @@ else 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" -( cd "$TODO_DIR" ) || dieWithHelp "$1" "Fatal Error: Unable to cd to $TODO_DIR" +[ -z "$ACTION" ] && usage +[ -d "$TODO_DIR" ] || mkdir -p "$TODO_DIR" 2>/dev/null || dieWithHelp "$1" "Fatal Error: $TODO_DIR is not a directory" +( cd -- "$TODO_DIR" ) || dieWithHelp "$1" "Fatal Error: Unable to cd to $TODO_DIR" [ -z "$TODOTXT_PRIORITY_ON_ADD" ] \ || echo "$TODOTXT_PRIORITY_ON_ADD" | grep -q "^[A-Z]$" \ || die "TODOTXT_PRIORITY_ON_ADD should be a capital letter from A to Z (it is now \"$TODOTXT_PRIORITY_ON_ADD\")." @@ -799,11 +788,11 @@ fi [ -z "$DONE_FILE" ] && DONE_FILE="$TODO_DIR/done.txt" [ -z "$REPORT_FILE" ] && REPORT_FILE="$TODO_DIR/report.txt" -[ -f "$TODO_FILE" ] || [ -c "$TODO_FILE" ] || > "$TODO_FILE" -[ -f "$DONE_FILE" ] || [ -c "$DONE_FILE" ] || > "$DONE_FILE" -[ -f "$REPORT_FILE" ] || [ -c "$REPORT_FILE" ] || > "$REPORT_FILE" +[ -f "$TODO_FILE" ] || [ -c "$TODO_FILE" ] || : > "$TODO_FILE" +[ -f "$DONE_FILE" ] || [ -c "$DONE_FILE" ] || : > "$DONE_FILE" +[ -f "$REPORT_FILE" ] || [ -c "$REPORT_FILE" ] || : > "$REPORT_FILE" -if [ $TODOTXT_PLAIN = 1 ]; then +if [ "$TODOTXT_PLAIN" = 1 ]; then for clr in ${!PRI_@}; do export "$clr"="$NONE" done @@ -817,22 +806,24 @@ if [ $TODOTXT_PLAIN = 1 ]; then COLOR_META=$NONE fi -[[ "$HIDE_PROJECTS_SUBSTITUTION" ]] && COLOR_PROJECT="$NONE" -[[ "$HIDE_CONTEXTS_SUBSTITUTION" ]] && COLOR_CONTEXT="$NONE" +[[ -n "$HIDE_PROJECTS_SUBSTITUTION" ]] && COLOR_PROJECT="$NONE" +[[ -n "$HIDE_CONTEXTS_SUBSTITUTION" ]] && COLOR_CONTEXT="$NONE" -_addto() { +_addto() +{ file="$1" input="$2" cleaninput uppercasePriority if [[ "$TODOTXT_DATE_ON_ADD" -eq 1 ]]; then + local now now=$(date '+%Y-%m-%d') input=$(echo "$input" | sed -e 's/^\(([A-Z]) \)\{0,1\}/\1'"$now /") fi if [[ -n "$TODOTXT_PRIORITY_ON_ADD" ]]; then if ! echo "$input" | grep -q '^([A-Z])'; then - input=$(echo -n "($TODOTXT_PRIORITY_ON_ADD) " ; echo "$input") + input=$(echo -n "($TODOTXT_PRIORITY_ON_ADD) "; echo "$input") fi fi fixMissingEndOfLine "$file" @@ -860,8 +851,8 @@ filtercommand() ## 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 + # First character is a dash: hide lines that match this + # $search_term # ## Remove the first character (-) before adding to our filter command printf -v filter '%sgrep -v -i %q' "${filter:-}${filter:+ | }" "${search_term:1}" @@ -875,7 +866,8 @@ filtercommand() printf %s "$filter" } -_list() { +_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 @@ -883,20 +875,20 @@ _list() { ## Absolute path src="$FILE" elif [ -f "$TODO_DIR/$FILE" ]; then - ## Path relative to todo.sh directory + # Path relative to todo.sh directory src="$TODO_DIR/$FILE" elif [ -f "$FILE" ]; then - ## Path relative to current working directory + # Path relative to current working directory src="$FILE" elif [ -f "$TODO_DIR/${FILE}.txt" ]; then - ## Path relative to todo.sh directory, missing file extension + # Path relative to todo.sh directory, missing file extension src="$TODO_DIR/${FILE}.txt" else die "TODO: File $FILE does not exist." fi - ## Get our search arguments, if any - shift ## was file name, new $1 is first search term + # Get our search arguments, if any + shift # was file name, new $1 is first search term _format "$src" '' "$@" @@ -905,12 +897,14 @@ _list() { echo "$(getPrefix "$src"): ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown" fi } + getPadding() { - ## We need one level of padding for each power of 10 $LINES uses. + # We need one level of padding for each power of 10 $LINES uses. LINES=$(sed -n '$ =' "${1:-$TODO_FILE}") printf %s ${#LINES} } + _format() { # Parameters: $1: todo input file; when empty formats stdin @@ -921,17 +915,17 @@ _format() FILE=$1 shift - ## Figure out how much padding we need to use, unless this was passed to us. + # Figure out how much padding we need to use, unless this was passed to us. PADDING=${1:-$(getPadding "$FILE")} shift - ## Number the file, then run the filter command, - ## then sort and mangle output some more + # Number the file, then run the filter command, + # then sort and mangle output some more if [[ $TODOTXT_DISABLE_FILTER = 1 ]]; then TODOTXT_FINAL_FILTER="cat" fi items=$( - if [ "$FILE" ]; then + if [ -n "$FILE" ]; then # shellcheck disable=SC2283 sed = "$FILE" else @@ -946,25 +940,25 @@ _format() ' ) - ## Build and apply the filter. + # Build and apply the filter. filter_command=$(filtercommand "${pre_filter_command:-}" "${post_filter_command:-}" "$@") - if [ "${filter_command}" ]; then + if [ -n "${filter_command}" ]; then filtered_items=$(echo -n "$items" | eval "${filter_command}") else filtered_items=$items fi filtered_items=$( - echo -n "$filtered_items" \ - | sed ''' + echo -n "$filtered_items" \ + | sed ' s/^ /00000/; s/^ /0000/; s/^ /000/; s/^ /00/; s/^ /0/; - ''' \ + ' \ | eval "${TODOTXT_SORT_COMMAND}" \ - | awk ''' - function highlight(colorVar, color) { + | awk ' + function highlight(colorVar, color) { color = ENVIRON[colorVar] gsub(/\\+033/, "\033", color) return color @@ -1018,7 +1012,7 @@ _format() } printf "%s\n", end_clr } - ''' \ + ' \ | sed ' s/'"${HIDE_PROJECTS_SUBSTITUTION:-^}"'//g s/'"${HIDE_CONTEXTS_SUBSTITUTION:-^}"'//g @@ -1026,11 +1020,11 @@ _format() ' \ | eval ${TODOTXT_FINAL_FILTER} \ ) - [ "$filtered_items" ] && echo "$filtered_items" + [ -n "$filtered_items" ] && echo "$filtered_items" if [ "$TODOTXT_VERBOSE" -gt 0 ]; then - NUMTASKS=$( echo -n "$filtered_items" | sed -n '$ =' ) - TOTALTASKS=$( echo -n "$items" | sed -n '$ =' ) + NUMTASKS=$(echo -n "$filtered_items" | sed -n '$ =') + TOTALTASKS=$(echo -n "$items" | sed -n '$ =') fi if [ "$TODOTXT_VERBOSE" -gt 1 ]; then echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}" @@ -1043,14 +1037,14 @@ listWordsWithSigil() shift FILE=$TODO_FILE - [ "$TODOTXT_SOURCEVAR" ] && eval "FILE=$TODOTXT_SOURCEVAR" - eval "$(filtercommand 'cat "${FILE[@]}"' '' "$@")" \ - | grep -o "[^ ]*${sigil}[^ ]\\+" \ - | sed -n \ - -e "s#^${TODOTXT_SIGIL_BEFORE_PATTERN//#/\\#}##" \ - -e "s#${TODOTXT_SIGIL_AFTER_PATTERN//#/\\#}\$##" \ - -e "/^${sigil}${TODOTXT_SIGIL_VALID_PATTERN//\//\\/}$/p" \ - | sort -u + [ -n "$TODOTXT_SOURCEVAR" ] && eval "FILE=$TODOTXT_SOURCEVAR" + eval "$(filtercommand 'cat "${FILE[@]}"' '' "$@")" \ + | grep -o "[^ ]*${sigil}[^ ]\\+" \ + | sed -n \ + -e "s#^${TODOTXT_SIGIL_BEFORE_PATTERN//#/\\#}##" \ + -e "s#${TODOTXT_SIGIL_AFTER_PATTERN//#/\\#}\$##" \ + -e "/^${sigil}${TODOTXT_SIGIL_VALID_PATTERN//\//\\/}$/p" \ + | sort -u } hasCustomAction() @@ -1067,15 +1061,14 @@ hasCustomAction() export -f cleaninput getPrefix getTodo getNewtodo filtercommand _list listWordsWithSigil getPadding _format die # == HANDLE ACTION == -action=$( printf "%s\n" "$ACTION" | tr '[:upper:]' '[:lower:]' ) - -## If the first argument is "command", run the rest of the arguments -## using todo.sh builtins. -## Else, run a actions script with the name of the command if it exists -## or fallback to using a builtin -if [ "$action" == command ] -then - ## Get rid of "command" from arguments list +action=$(printf "%s\n" "$ACTION" | tr '[:upper:]' '[:lower:]') + +# If the first argument is "command", run the rest of the arguments +# using todo.sh builtins. +# Else, run a actions script with the name of the command if it exists +# or fallback to using a builtin +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:]' ) @@ -1093,7 +1086,7 @@ elif [ "$isDefaultAction" ] && [ -n "$TODOTXT_DEFAULT_ACTION" ]; then eval "exec \"\${BASH_SOURCE[0]}\" $TODOTXT_DEFAULT_ACTION" fi -## Only run if $action isn't found in .todo.actions.d +# Only run if $action isn't found in .todo.actions.d case $action in "add" | "a") if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then @@ -1121,7 +1114,7 @@ case $action in IFS=$'\n' # Treat each line separately - for line in $input ; do + for line in $input; do _addto "$TODO_FILE" "$line" done IFS=$SAVEIFS @@ -1169,7 +1162,7 @@ case $action in fi ;; -"archive" ) +"archive") # defragment blank lines sed -i.bak -e '/./!d' "$TODO_FILE" if grep "^x " "$TODO_FILE" >> "$DONE_FILE"; then @@ -1185,7 +1178,7 @@ case $action in fi ;; -"del" | "rm" ) +"del" | "rm") # replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1 errmsg="usage: $TODO_SH del NR [TERM]" item=$2 @@ -1193,7 +1186,7 @@ case $action in if [ -z "$3" ]; then if confirm "Delete '$todo'"; then - if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then + if [ "$TODOTXT_PRESERVE_LINE_NUMBERS" = 0 ]; then # delete line (changes line numbers) sed -i.bak -e "${item}s/^.*//" -e '/./!d' "$TODO_FILE" else @@ -1258,7 +1251,7 @@ case $action in errmsg="usage: $TODO_SH do NR [NR ...]" # shift so we get arguments to the do request shift; - [ "$#" -eq 0 ] && die "$errmsg" + [ $# -eq 0 ] && die "$errmsg" # Split multiple do's, if comma separated change to whitespace separated # Loop the 'do' function for each item @@ -1283,7 +1276,7 @@ case $action in fi done - if [ $TODOTXT_AUTO_ARCHIVE = 1 ]; then + if [ "$TODOTXT_AUTO_ARCHIVE" = 1 ]; then # Recursively invoke the script to allow overriding of the archive # action. "$TODO_FULL_SH" archive || status=$? @@ -1291,13 +1284,13 @@ case $action in exit $status ;; -"help" ) - shift ## Was help; new $1 is first help topic / action name +"help") + shift # Was help; new $1 is first help topic / action name if [ $# -gt 0 ]; then # Don't use PAGER here; we don't expect much usage output from one / few actions. actionUsage "$@" else - if [ -t 1 ] ; then # STDOUT is a TTY + if [ -t 1 ]; then # STDOUT is a TTY if command -v "${PAGER:-less}" >/dev/null 2>&1; then # we have a working PAGER (or less as a default) help | "${PAGER:-less}" && exit 0 @@ -1307,8 +1300,8 @@ case $action in fi ;; -"shorthelp" ) - if [ -t 1 ] ; then # STDOUT is a TTY +"shorthelp") + if [ -t 1 ]; then # STDOUT is a TTY if command -v "${PAGER:-less}" >/dev/null 2>&1; then # we have a working PAGER (or less as a default) shorthelp | "${PAGER:-less}" && exit 0 @@ -1317,22 +1310,22 @@ case $action in shorthelp # just in case something failed above, we go ahead and just spew to STDOUT ;; -"list" | "ls" ) - shift ## Was ls; new $1 is first search term +"list" | "ls") + shift # Was ls; new $1 is first search term _list "$TODO_FILE" "$@" ;; -"listall" | "lsa" ) - shift ## Was lsa; new $1 is first search term +"listall" | "lsa") + shift # Was lsa; new $1 is first search term - TOTAL=$( sed -n '$ =' "$TODO_FILE" ) + TOTAL=$(sed -n '$ =' "$TODO_FILE") PADDING=${#TOTAL} post_filter_command="${post_filter_command:-}${post_filter_command:+ | }awk -v TOTAL=$TOTAL -v PADDING=$PADDING '{ \$1 = sprintf(\"%\" PADDING \"d\", (\$1 > TOTAL ? 0 : \$1)); print }' " cat "$TODO_FILE" "$DONE_FILE" | TODOTXT_VERBOSE=0 _format '' "$PADDING" "$@" if [ "$TODOTXT_VERBOSE" -gt 0 ]; then - TDONE=$( sed -n '$ =' "$DONE_FILE" ) + TDONE=$(sed -n '$ =' "$DONE_FILE") TASKNUM=$(TODOTXT_PLAIN=1 TODOTXT_VERBOSE=0 _format "$TODO_FILE" 1 "$@" | sed -n '$ =') DONENUM=$(TODOTXT_PLAIN=1 TODOTXT_VERBOSE=0 _format "$DONE_FILE" 1 "$@" | sed -n '$ =') echo "--" @@ -1342,38 +1335,38 @@ case $action in fi ;; -"listfile" | "lf" ) - shift ## Was listfile, next $1 is file name +"listfile" | "lf") + shift # Was listfile, next $1 is file name if [ $# -eq 0 ]; then [ "$TODOTXT_VERBOSE" -gt 0 ] && echo "Files in the todo.txt directory:" - cd "$TODO_DIR" && ls -1 -- *.txt + cd -- "$TODO_DIR" && ls -1 -- *.txt else FILE="$1" - shift ## Was filename; next $1 is first search term + shift # Was filename; next $1 is first search term _list "$FILE" "$@" fi ;; -"listcon" | "lsc" ) +"listcon" | "lsc") shift listWordsWithSigil '@' "$@" ;; -"listproj" | "lsprj" ) +"listproj" | "lsprj") shift listWordsWithSigil '+' "$@" ;; -"listpri" | "lsp" ) - shift ## was "listpri", new $1 is priority to list or first TERM +"listpri" | "lsp") + shift # was "listpri", new $1 is priority to list or first TERM 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" ) +"move" | "mv") # replace moved line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1 errmsg="usage: $TODO_SH mv NR DEST [SRC]" item=$2 @@ -1389,7 +1382,7 @@ case $action in getTodo "$item" "$src" [ -z "$todo" ] && die "$item: No such item in $src." if confirm "Move '$todo' from $src to $dest"; then - if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then + if [ "$TODOTXT_PRESERVE_LINE_NUMBERS" = 0 ]; then # delete line (changes line numbers) sed -i.bak -e "${item}s/^.*//" -e '/./!d' "$src" else @@ -1413,17 +1406,17 @@ case $action in replaceOrPrepend 'prepend' "$@" ;; -"pri" | "p" ) +"pri" | "p") shift status=0 while [ "$#" -gt 0 ] ; do item=$1 - newpri=$( printf "%s\n" "$2" | tr '[:lower:]' '[:upper:]' ) + newpri=$(printf "%s\n" "$2" | tr '[:lower:]' '[:upper:]') errmsg="usage: $TODO_SH pri NR PRIORITY [NR PRIORITY ...] note: PRIORITY must be anywhere from A to Z." - [ "$#" -lt 2 ] && die "$errmsg" + [ $# -lt 2 ] && die "$errmsg" [[ "$newpri" = @([A-Z]) ]] || die "$errmsg" getTodo "$item" @@ -1439,7 +1432,7 @@ note: PRIORITY must be anywhere from A to Z." getNewtodo "$item" echo "$item $newtodo" if [ "$oldpri" != "$newpri" ]; then - if [ "$oldpri" ]; then + if [ -n "$oldpri" ]; then echo "TODO: $item re-prioritized from ($oldpri) to ($newpri)." else echo "TODO: $item prioritized ($newpri)." @@ -1460,14 +1453,14 @@ note: PRIORITY must be anywhere from A to Z." replaceOrPrepend 'replace' "$@" ;; -"report" ) +"report") # archive first # Recursively invoke the script to allow overriding of the archive # action. "$TODO_FULL_SH" archive - TOTAL=$( sed -n '$ =' "$TODO_FILE" ) - TDONE=$( sed -n '$ =' "$DONE_FILE" ) + TOTAL=$(sed -n '$ =' "$TODO_FILE") + TDONE=$(sed -n '$ =' "$DONE_FILE") NEWDATA="${TOTAL:-0} ${TDONE:-0}" LASTREPORT=$(sed -ne '$p' "$REPORT_FILE") LASTDATA=${LASTREPORT#* } # Strip timestamp. @@ -1482,8 +1475,8 @@ note: PRIORITY must be anywhere from A to Z." fi ;; -"deduplicate" ) - if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then +"deduplicate") + if [ "$TODOTXT_PRESERVE_LINE_NUMBERS" = 0 ]; then deduplicateSedCommand='d' else deduplicateSedCommand='s/^.*//; p' @@ -1491,7 +1484,7 @@ note: PRIORITY must be anywhere from A to Z." # To determine the difference when deduplicated lines are preserved, only # non-empty lines must be counted. - originalTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' ) + originalTaskNum=$(sed -e '/./!d' "$TODO_FILE" | sed -n '$ =') # Look for duplicate lines and discard the second occurrence. # We start with an empty hold space on the first line. For each line: @@ -1517,7 +1510,7 @@ note: PRIORITY must be anywhere from A to Z." -e "$deduplicateSedCommand" \ "$TODO_FILE" - newTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' ) + newTaskNum=$(sed -e '/./!d' "$TODO_FILE" | sed -n '$ =') deduplicateNum=$(( originalTaskNum - newTaskNum )) if [ $deduplicateNum -eq 0 ]; then die "TODO: No duplicate tasks found" @@ -1526,9 +1519,9 @@ note: PRIORITY must be anywhere from A to Z." fi ;; -"listaddons" ) +"listaddons") if [ -d "$TODO_ACTIONS_DIR" ]; then - cd "$TODO_ACTIONS_DIR" || exit $? + cd -- "$TODO_ACTIONS_DIR" || exit $? actionsCnt=0 for action in * do @@ -1553,6 +1546,6 @@ note: PRIORITY must be anywhere from A to Z." fi ;; -* ) +*) usage;; esac diff --git a/todo_completion b/todo_completion old mode 100755 new mode 100644 index 3287efea..12610da3 --- a/todo_completion +++ b/todo_completion @@ -12,8 +12,8 @@ _todo() local -r OPTS="-@ -@@ -+ -++ -d -f -h -p -P -PP -a -n -t -v -vv -V -x" local -r COMMANDS="\ - add a addto addm append app archive command del \ - rm depri dp do help list ls listaddons listall lsa listcon \ + add a addto addm append app archive command del \ + rm depri dp do help list ls listaddons listall lsa listcon \ lsc listfile lf listpri lsp listproj lsprj move \ mv prepend prep pri p replace report shorthelp" local -r MOVE_COMMAND_PATTERN='move|mv' @@ -21,7 +21,7 @@ _todo() 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" + 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 @@ -32,23 +32,23 @@ _todo() command) completions=$COMMANDS;; help) - completions="$COMMANDS $(eval TODOTXT_VERBOSE=0 $_todo_sh command listaddons 2>/dev/null)";; - -*) 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)";; + -*) completions="$COMMANDS $(eval TODOTXT_VERBOSE=0 "$_todo_sh" command listaddons 2>/dev/null) $OPTS";; *) if [[ "$prev" =~ ^(addto|listfile|lf${_todo_file1_actions:+|${_todo_file1_actions}})$ ]]; then - completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listfile 2>/dev/null) + completions=$(eval TODOTXT_VERBOSE=0 "$_todo_sh" command listfile 2>/dev/null) else case "$cur" in - +*) completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listproj 2>/dev/null) - COMPREPLY=( $( compgen -W "$completions" -- $cur )) + +*) completions=$(eval TODOTXT_VERBOSE=0 "$_todo_sh" command listproj 2>/dev/null) + COMPREPLY=( $(compgen -W "$completions" -- "$cur")) [ ${#COMPREPLY[@]} -gt 0 ] && return 0 # Fall back to projects extracted from done tasks. - completions=$(eval 'TODOTXT_VERBOSE=0 TODOTXT_SOURCEVAR=\$DONE_FILE' $_todo_sh command listproj 2>/dev/null) + completions=$(eval 'TODOTXT_VERBOSE=0 TODOTXT_SOURCEVAR=\$DONE_FILE' "$_todo_sh" command listproj 2>/dev/null) ;; - @*) completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listcon 2>/dev/null) - COMPREPLY=( $( compgen -W "$completions" -- $cur )) + @*) completions=$(eval TODOTXT_VERBOSE=0 "$_todo_sh" command listcon 2>/dev/null) + COMPREPLY=( $(compgen -W "$completions" -- "$cur")) [ ${#COMPREPLY[@]} -gt 0 ] && return 0 # Fall back to contexts extracted from done tasks. - completions=$(eval 'TODOTXT_VERBOSE=0 TODOTXT_SOURCEVAR=\$DONE_FILE' $_todo_sh command listcon 2>/dev/null) + completions=$(eval 'TODOTXT_VERBOSE=0 TODOTXT_SOURCEVAR=\$DONE_FILE' "$_todo_sh" command listcon 2>/dev/null) ;; *) if [[ "$cur" =~ ^[0-9]+$ ]]; then declare -a sedTransformations=( @@ -70,14 +70,15 @@ _todo() # a safety check of the ls action output. -e '1q' ) - local todo=$( \ - eval TODOTXT_VERBOSE=0 $_todo_sh '-@ -+ -p -x command ls "^ *${cur} "' 2>/dev/null | \ + local todo + todo=$( \ + eval TODOTXT_VERBOSE=0 "$_todo_sh" '-@ -+ -p -x command ls "^ *${cur} "' 2>/dev/null | \ sed "${sedTransformations[@]}" \ ) # Append task text as a shell comment. This # completion can be a safety check before a # destructive todo.txt operation. - [ "$todo" ] && COMPREPLY[0]="$cur # $todo" + [ -n "$todo" ] && COMPREPLY[0]="$cur # $todo" return 0 else return 0 @@ -89,7 +90,7 @@ _todo() esac fi - COMPREPLY=( $( compgen -W "$completions" -- $cur )) + COMPREPLY=( $(compgen -W "$completions" -- "$cur")) return 0 } complete -F _todo todo.sh