Skip to content
This repository
Browse code

Merge pull request #129 from erichs/todo.sh

add todo.txt-cli as a plugin
  • Loading branch information...
commit e1da40a47510c37f688572bfbaf01fe26ae36189 2 parents 98fbc29 + 339cfe9
Erich Smith erichs authored
2  .gitignore
... ... @@ -1,6 +1,6 @@
1 1 */enabled/*
2 2 .DS_Store
3   -custom/*.bash
  3 +custom/*
4 4 !custom/example.bash
5 5 .rvmrc
6 6 aliases/custom.aliases.bash
26 plugins/available/base.plugin.bash
@@ -151,18 +151,20 @@ usage ()
151 151 fi
152 152 }
153 153
154   -t ()
155   -{
156   - about 'one thing todo'
157   - param 'if not set, display todo item'
158   - param '1: todo text'
159   - group 'base'
160   - if [[ "$*" == "" ]] ; then
161   - cat ~/.t
162   - else
163   - echo "$*" > ~/.t
164   - fi
165   -}
  154 +if [ ! -e $BASH_IT/plugins/enabled/todo.plugin.bash ]; then
  155 +# if user has installed todo plugin, skip this...
  156 + t ()
  157 + {
  158 + about 'one thing todo'
  159 + param 'if not set, display todo item'
  160 + param '1: todo text'
  161 + if [[ "$*" == "" ]] ; then
  162 + cat ~/.t
  163 + else
  164 + echo "$*" > ~/.t
  165 + fi
  166 + }
  167 +fi
166 168
167 169 command_exists ()
168 170 {
20 plugins/available/todo.plugin.bash
... ... @@ -0,0 +1,20 @@
  1 +#!/bin/bash
  2 +
  3 +# you may override any of the exported variables below in your .bash_profile
  4 +
  5 +if [ -z "$TODO_DIR" ]; then
  6 + export TODO_DIR=$BASH_IT/custom # store todo items in user's custom dir, ignored by git
  7 +fi
  8 +if [ -z "$TODOTXT_DEFAULT_ACTION" ]; then
  9 + export TODOTXT_DEFAULT_ACTION=ls # typing 't' by itself will list current todos
  10 +fi
  11 +if [ -z "$TODO_SRC_DIR" ]; then
  12 + export TODO_SRC_DIR=$BASH_IT/plugins/available/todo
  13 +fi
  14 +
  15 +# respect ENV var set in .bash_profile, default is 't'
  16 +alias $TODO='$TODO_SRC_DIR/todo.sh -d $TODO_SRC_DIR/todo.cfg'
  17 +
  18 +export PATH=$PATH:$TODO_SRC_DIR
  19 +source $TODO_SRC_DIR/todo_completion # bash completion for todo.sh
  20 +complete -F _todo $TODO # enable completion for 't' alias
75 plugins/available/todo/todo.cfg
... ... @@ -0,0 +1,75 @@
  1 +# === EDIT FILE LOCATIONS BELOW ===
  2 +
  3 +# Your todo/done/report.txt locations
  4 +export TODO_FILE="$TODO_DIR/todo.txt"
  5 +export DONE_FILE="$TODO_DIR/done.txt"
  6 +export REPORT_FILE="$TODO_DIR/report.txt"
  7 +
  8 +# You can customize your actions directory location
  9 +#export TODO_ACTIONS_DIR="$HOME/.todo.actions.d"
  10 +
  11 +# == EDIT FILE LOCATIONS ABOVE ===
  12 +
  13 +# === COLOR MAP ===
  14 +
  15 +## Text coloring and formatting is done by inserting ANSI escape codes.
  16 +## If you have re-mapped your color codes, or use the todo.txt
  17 +## output in another output system (like Conky), you may need to
  18 +## over-ride by uncommenting and editing these defaults.
  19 +## If you change any of these here, you also need to uncomment
  20 +## the defaults in the COLORS section below. Otherwise, todo.txt
  21 +## will still use the defaults!
  22 +
  23 +# export BLACK='\\033[0;30m'
  24 +# export RED='\\033[0;31m'
  25 +# export GREEN='\\033[0;32m'
  26 +# export BROWN='\\033[0;33m'
  27 +# export BLUE='\\033[0;34m'
  28 +# export PURPLE='\\033[0;35m'
  29 +# export CYAN='\\033[0;36m'
  30 +# export LIGHT_GREY='\\033[0;37m'
  31 +# export DARK_GREY='\\033[1;30m'
  32 +# export LIGHT_RED='\\033[1;31m'
  33 +# export LIGHT_GREEN='\\033[1;32m'
  34 +# export YELLOW='\\033[1;33m'
  35 +# export LIGHT_BLUE='\\033[1;34m'
  36 +# export LIGHT_PURPLE='\\033[1;35m'
  37 +# export LIGHT_CYAN='\\033[1;36m'
  38 +# export WHITE='\\033[1;37m'
  39 +# export DEFAULT='\\033[0m'
  40 +
  41 +# === COLORS ===
  42 +
  43 +## Uncomment and edit to override these defaults.
  44 +## Reference the constants from the color map above,
  45 +## or use $NONE to disable highlighting.
  46 +#
  47 +# Priorities can be any upper-case letter.
  48 +# A,B,C are highlighted; you can add coloring for more.
  49 +#
  50 +# export PRI_A=$YELLOW # color for A priority
  51 +# export PRI_B=$GREEN # color for B priority
  52 +# export PRI_C=$LIGHT_BLUE # color for C priority
  53 +# export PRI_D=... # define your own
  54 +# export PRI_X=$WHITE # color unless explicitly defined
  55 +
  56 +# There is highlighting for tasks that have been done,
  57 +# but haven't been archived yet.
  58 +#
  59 +# export COLOR_DONE=$LIGHT_GREY
  60 +
  61 +# === BEHAVIOR ===
  62 +
  63 +## customize list output
  64 +#
  65 +# TODOTXT_SORT_COMMAND will filter after line numbers are
  66 +# inserted, but before colorization, and before hiding of
  67 +# priority, context, and project.
  68 +#
  69 +# export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2'
  70 +
  71 +# TODOTXT_FINAL_FILTER will filter list output after colorization,
  72 +# priority hiding, context hiding, and project hiding. That is,
  73 +# just before the list output is displayed.
  74 +#
  75 +# export TODOTXT_FINAL_FILTER='cat'
1,321 plugins/available/todo/todo.sh
... ... @@ -0,0 +1,1321 @@
  1 +#! /bin/bash
  2 +
  3 +# === HEAVY LIFTING ===
  4 +shopt -s extglob extquote
  5 +
  6 +# NOTE: Todo.sh requires the .todo/config configuration file to run.
  7 +# Place the .todo/config file in your home directory or use the -d option for a custom location.
  8 +
  9 +[ -f VERSION-FILE ] && . VERSION-FILE || VERSION="2.9"
  10 +version() {
  11 + cat <<-EndVersion
  12 + TODO.TXT Command Line Interface v$VERSION
  13 +
  14 + First release: 5/11/2006
  15 + Original conception by: Gina Trapani (http://ginatrapani.org)
  16 + Contributors: http://github.com/ginatrapani/todo.txt-cli/network
  17 + License: GPL, http://www.gnu.org/copyleft/gpl.html
  18 + More information and mailing list at http://todotxt.com
  19 + Code repository: http://github.com/ginatrapani/todo.txt-cli/tree/master
  20 + EndVersion
  21 + exit 1
  22 +}
  23 +
  24 +# Set script name and full path early.
  25 +TODO_SH=$(basename "$0")
  26 +TODO_FULL_SH="$0"
  27 +export TODO_SH TODO_FULL_SH
  28 +
  29 +oneline_usage="$TODO_SH [-fhpantvV] [-d todo_config] action [task_number] [task_description]"
  30 +
  31 +usage()
  32 +{
  33 + cat <<-EndUsage
  34 + Usage: $oneline_usage
  35 + Try '$TODO_SH -h' for more information.
  36 + EndUsage
  37 + exit 1
  38 +}
  39 +
  40 +shorthelp()
  41 +{
  42 + cat <<-EndHelp
  43 + Usage: $oneline_usage
  44 +
  45 + Actions:
  46 + add|a "THING I NEED TO DO +project @context"
  47 + addm "THINGS I NEED TO DO
  48 + MORE THINGS I NEED TO DO"
  49 + addto DEST "TEXT TO ADD"
  50 + append|app ITEM# "TEXT TO APPEND"
  51 + archive
  52 + command [ACTIONS]
  53 + deduplicate
  54 + del|rm ITEM# [TERM]
  55 + depri|dp ITEM#[, ITEM#, ITEM#, ...]
  56 + do ITEM#[, ITEM#, ITEM#, ...]
  57 + help
  58 + list|ls [TERM...]
  59 + listall|lsa [TERM...]
  60 + listaddons
  61 + listcon|lsc
  62 + listfile|lf [SRC [TERM...]]
  63 + listpri|lsp [PRIORITIES] [TERM...]
  64 + listproj|lsprj [TERM...]
  65 + move|mv ITEM# DEST [SRC]
  66 + prepend|prep ITEM# "TEXT TO PREPEND"
  67 + pri|p ITEM# PRIORITY
  68 + replace ITEM# "UPDATED TODO"
  69 + report
  70 + shorthelp
  71 +
  72 + Actions can be added and overridden using scripts in the actions
  73 + directory.
  74 + EndHelp
  75 +
  76 + # Only list the one-line usage from the add-on actions. This assumes that
  77 + # add-ons use the same usage indentation structure as todo.sh.
  78 + addonHelp | grep -e '^ Add-on Actions:' -e '^ [[:alpha:]]'
  79 +
  80 + cat <<-EndHelpFooter
  81 +
  82 + See "help" for more details.
  83 + EndHelpFooter
  84 + exit 0
  85 +}
  86 +
  87 +help()
  88 +{
  89 + cat <<-EndOptionsHelp
  90 + Usage: $oneline_usage
  91 +
  92 + Options:
  93 + -@
  94 + Hide context names in list output. Use twice to show context
  95 + names (default).
  96 + -+
  97 + Hide project names in list output. Use twice to show project
  98 + names (default).
  99 + -c
  100 + Color mode
  101 + -d CONFIG_FILE
  102 + Use a configuration file other than the default ~/.todo/config
  103 + -f
  104 + Forces actions without confirmation or interactive input
  105 + -h
  106 + Display a short help message; same as action "shorthelp"
  107 + -p
  108 + Plain mode turns off colors
  109 + -P
  110 + Hide priority labels in list output. Use twice to show
  111 + priority labels (default).
  112 + -a
  113 + Don't auto-archive tasks automatically on completion
  114 + -A
  115 + Auto-archive tasks automatically on completion
  116 + -n
  117 + Don't preserve line numbers; automatically remove blank lines
  118 + on task deletion
  119 + -N
  120 + Preserve line numbers
  121 + -t
  122 + Prepend the current date to a task automatically
  123 + when it's added.
  124 + -T
  125 + Do not prepend the current date to a task automatically
  126 + when it's added.
  127 + -v
  128 + Verbose mode turns on confirmation messages
  129 + -vv
  130 + Extra verbose mode prints some debugging information and
  131 + additional help text
  132 + -V
  133 + Displays version, license and credits
  134 + -x
  135 + Disables TODOTXT_FINAL_FILTER
  136 +
  137 +
  138 + EndOptionsHelp
  139 +
  140 + [ $TODOTXT_VERBOSE -gt 1 ] && cat <<-'EndVerboseHelp'
  141 + Environment variables:
  142 + TODOTXT_AUTO_ARCHIVE is same as option -a (0)/-A (1)
  143 + TODOTXT_CFG_FILE=CONFIG_FILE is same as option -d CONFIG_FILE
  144 + TODOTXT_FORCE=1 is same as option -f
  145 + TODOTXT_PRESERVE_LINE_NUMBERS is same as option -n (0)/-N (1)
  146 + TODOTXT_PLAIN is same as option -p (1)/-c (0)
  147 + TODOTXT_DATE_ON_ADD is same as option -t (1)/-T (0)
  148 + TODOTXT_VERBOSE=1 is same as option -v
  149 + TODOTXT_DISABLE_FILTER=1 is same as option -x
  150 + TODOTXT_DEFAULT_ACTION="" run this when called with no arguments
  151 + TODOTXT_SORT_COMMAND="sort ..." customize list output
  152 + TODOTXT_FINAL_FILTER="sed ..." customize list after color, P@+ hiding
  153 + TODOTXT_SOURCEVAR=\$DONE_FILE use another source for listcon, listproj
  154 +
  155 +
  156 + EndVerboseHelp
  157 + cat <<-EndActionsHelp
  158 + Built-in Actions:
  159 + add "THING I NEED TO DO +project @context"
  160 + a "THING I NEED TO DO +project @context"
  161 + Adds THING I NEED TO DO to your todo.txt file on its own line.
  162 + Project and context notation optional.
  163 + Quotes optional.
  164 +
  165 + addm "FIRST THING I NEED TO DO +project1 @context
  166 + SECOND THING I NEED TO DO +project2 @context"
  167 + Adds FIRST THING I NEED TO DO to your todo.txt on its own line and
  168 + Adds SECOND THING I NEED TO DO to you todo.txt on its own line.
  169 + Project and context notation optional.
  170 +
  171 + addto DEST "TEXT TO ADD"
  172 + Adds a line of text to any file located in the todo.txt directory.
  173 + For example, addto inbox.txt "decide about vacation"
  174 +
  175 + append ITEM# "TEXT TO APPEND"
  176 + app ITEM# "TEXT TO APPEND"
  177 + Adds TEXT TO APPEND to the end of the task on line ITEM#.
  178 + Quotes optional.
  179 +
  180 + archive
  181 + Moves all done tasks from todo.txt to done.txt and removes blank lines.
  182 +
  183 + command [ACTIONS]
  184 + Runs the remaining arguments using only todo.sh builtins.
  185 + Will not call any .todo.actions.d scripts.
  186 +
  187 + deduplicate
  188 + Removes duplicate lines from todo.txt.
  189 +
  190 + del ITEM# [TERM]
  191 + rm ITEM# [TERM]
  192 + Deletes the task on line ITEM# in todo.txt.
  193 + If TERM specified, deletes only TERM from the task.
  194 +
  195 + depri ITEM#[, ITEM#, ITEM#, ...]
  196 + dp ITEM#[, ITEM#, ITEM#, ...]
  197 + Deprioritizes (removes the priority) from the task(s)
  198 + on line ITEM# in todo.txt.
  199 +
  200 + do ITEM#[, ITEM#, ITEM#, ...]
  201 + Marks task(s) on line ITEM# as done in todo.txt.
  202 +
  203 + help
  204 + Display this help message.
  205 +
  206 + list [TERM...]
  207 + ls [TERM...]
  208 + Displays all tasks that contain TERM(s) sorted by priority with line
  209 + numbers. Each task must match all TERM(s) (logical AND); to display
  210 + tasks that contain any TERM (logical OR), use
  211 + "TERM1\|TERM2\|..." (with quotes), or TERM1\\\|TERM2 (unquoted).
  212 + Hides all tasks that contain TERM(s) preceded by a
  213 + minus sign (i.e. -TERM). If no TERM specified, lists entire todo.txt.
  214 +
  215 + listall [TERM...]
  216 + lsa [TERM...]
  217 + Displays all the lines in todo.txt AND done.txt that contain TERM(s)
  218 + sorted by priority with line numbers. Hides all tasks that
  219 + contain TERM(s) preceded by a minus sign (i.e. -TERM). If no
  220 + TERM specified, lists entire todo.txt AND done.txt
  221 + concatenated and sorted.
  222 +
  223 + listaddons
  224 + Lists all added and overridden actions in the actions directory.
  225 +
  226 + listcon
  227 + lsc
  228 + Lists all the task contexts that start with the @ sign in todo.txt.
  229 +
  230 + listfile [SRC [TERM...]]
  231 + lf [SRC [TERM...]]
  232 + Displays all the lines in SRC file located in the todo.txt directory,
  233 + sorted by priority with line numbers. If TERM specified, lists
  234 + all lines that contain TERM(s) in SRC file. Hides all tasks that
  235 + contain TERM(s) preceded by a minus sign (i.e. -TERM).
  236 + Without any arguments, the names of all text files in the todo.txt
  237 + directory are listed.
  238 +
  239 + listpri [PRIORITIES] [TERM...]
  240 + lsp [PRIORITIES] [TERM...]
  241 + Displays all tasks prioritized PRIORITIES.
  242 + PRIORITIES can be a single one (A) or a range (A-C).
  243 + If no PRIORITIES specified, lists all prioritized tasks.
  244 + If TERM specified, lists only prioritized tasks that contain TERM(s).
  245 + Hides all tasks that contain TERM(s) preceded by a minus sign
  246 + (i.e. -TERM).
  247 +
  248 + listproj
  249 + lsprj
  250 + Lists all the projects (terms that start with a + sign) in
  251 + todo.txt.
  252 +
  253 + move ITEM# DEST [SRC]
  254 + mv ITEM# DEST [SRC]
  255 + Moves a line from source text file (SRC) to destination text file (DEST).
  256 + Both source and destination file must be located in the directory defined
  257 + in the configuration directory. When SRC is not defined
  258 + it's by default todo.txt.
  259 +
  260 + prepend ITEM# "TEXT TO PREPEND"
  261 + prep ITEM# "TEXT TO PREPEND"
  262 + Adds TEXT TO PREPEND to the beginning of the task on line ITEM#.
  263 + Quotes optional.
  264 +
  265 + pri ITEM# PRIORITY
  266 + p ITEM# PRIORITY
  267 + Adds PRIORITY to task on line ITEM#. If the task is already
  268 + prioritized, replaces current priority with new PRIORITY.
  269 + PRIORITY must be a letter between A and Z.
  270 +
  271 + replace ITEM# "UPDATED TODO"
  272 + Replaces task on line ITEM# with UPDATED TODO.
  273 +
  274 + report
  275 + Adds the number of open tasks and done tasks to report.txt.
  276 +
  277 + shorthelp
  278 + List the one-line usage of all built-in and add-on actions.
  279 +
  280 + EndActionsHelp
  281 +
  282 + addonHelp
  283 + exit 1
  284 +}
  285 +
  286 +addonHelp()
  287 +{
  288 + if [ -d "$TODO_ACTIONS_DIR" ]; then
  289 + didPrintAddonActionsHeader=
  290 + for action in "$TODO_ACTIONS_DIR"/*
  291 + do
  292 + if [ -f "$action" -a -x "$action" ]; then
  293 + if [ ! "$didPrintAddonActionsHeader" ]; then
  294 + cat <<-EndAddonActionsHeader
  295 + Add-on Actions:
  296 + EndAddonActionsHeader
  297 + didPrintAddonActionsHeader=1
  298 + fi
  299 + "$action" usage
  300 + fi
  301 + done
  302 + fi
  303 +}
  304 +
  305 +die()
  306 +{
  307 + echo "$*"
  308 + exit 1
  309 +}
  310 +
  311 +cleaninput()
  312 +{
  313 + # Parameters: When $1 = "for sed", performs additional escaping for use
  314 + # in sed substitution with "|" separators.
  315 + # Precondition: $input contains text to be cleaned.
  316 + # Postcondition: Modifies $input.
  317 +
  318 + # Replace CR and LF with space; tasks always comprise a single line.
  319 + input=${input//$'\r'/ }
  320 + input=${input//$'\n'/ }
  321 +
  322 + if [ "$1" = "for sed" ]; then
  323 + # This action uses sed with "|" as the substitution separator, and & as
  324 + # the matched string; these must be escaped.
  325 + # Backslashes must be escaped, too, and before the other stuff.
  326 + input=${input//\\/\\\\}
  327 + input=${input//|/\\|}
  328 + input=${input//&/\\&}
  329 + fi
  330 +}
  331 +
  332 +getPrefix()
  333 +{
  334 + # Parameters: $1: todo file; empty means $TODO_FILE.
  335 + # Returns: Uppercase FILE prefix to be used in place of "TODO:" where
  336 + # a different todo file can be specified.
  337 + local base=$(basename "${1:-$TODO_FILE}")
  338 + echo "${base%%.[^.]*}" | tr 'a-z' 'A-Z'
  339 +}
  340 +
  341 +getTodo()
  342 +{
  343 + # Parameters: $1: task number
  344 + # $2: Optional todo file
  345 + # Precondition: $errmsg contains usage message.
  346 + # Postcondition: $todo contains task text.
  347 +
  348 + local item=$1
  349 + [ -z "$item" ] && die "$errmsg"
  350 + [ "${item//[0-9]/}" ] && die "$errmsg"
  351 +
  352 + todo=$(sed "$item!d" "${2:-$TODO_FILE}")
  353 + [ -z "$todo" ] && die "$(getPrefix "$2"): No task $item."
  354 +}
  355 +getNewtodo()
  356 +{
  357 + # Parameters: $1: task number
  358 + # $2: Optional todo file
  359 + # Precondition: None.
  360 + # Postcondition: $newtodo contains task text.
  361 +
  362 + local item=$1
  363 + [ -z "$item" ] && die 'Programming error: $item should exist.'
  364 + [ "${item//[0-9]/}" ] && die 'Programming error: $item should be numeric.'
  365 +
  366 + newtodo=$(sed "$item!d" "${2:-$TODO_FILE}")
  367 + [ -z "$newtodo" ] && die "$(getPrefix "$2"): No updated task $item."
  368 +}
  369 +
  370 +replaceOrPrepend()
  371 +{
  372 + action=$1; shift
  373 + case "$action" in
  374 + replace)
  375 + backref=
  376 + querytext="Replacement: "
  377 + ;;
  378 + prepend)
  379 + backref=' &'
  380 + querytext="Prepend: "
  381 + ;;
  382 + esac
  383 + shift; item=$1; shift
  384 + getTodo "$item"
  385 +
  386 + if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
  387 + echo -n "$querytext"
  388 + read input
  389 + else
  390 + input=$*
  391 + fi
  392 + cleaninput "for sed"
  393 +
  394 + # Retrieve existing priority and prepended date
  395 + priority=$(sed -e "$item!d" -e $item's/^\((.) \)\{0,1\}\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{0,1\}.*/\1/' "$TODO_FILE")
  396 + prepdate=$(sed -e "$item!d" -e $item's/^\((.) \)\{0,1\}\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{0,1\}.*/\2/' "$TODO_FILE")
  397 +
  398 + if [ "$prepdate" -a "$action" = "replace" ] && [ "$(echo "$input"|sed -e 's/^\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\}\)\{0,1\}.*/\1/')" ]; then
  399 + # If the replaced text starts with a date, it will replace the existing
  400 + # date, too.
  401 + prepdate=
  402 + fi
  403 +
  404 + # Temporarily remove any existing priority and prepended date, perform the
  405 + # change (replace/prepend) and re-insert the existing priority and prepended
  406 + # date again.
  407 + sed -i.bak -e "$item s/^${priority}${prepdate}//" -e "$item s|^.*|${priority}${prepdate}${input}${backref}|" "$TODO_FILE"
  408 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  409 + getNewtodo "$item"
  410 + case "$action" in
  411 + replace)
  412 + echo "$item $todo"
  413 + echo "TODO: Replaced task with:"
  414 + echo "$item $newtodo"
  415 + ;;
  416 + prepend)
  417 + echo "$item $newtodo"
  418 + ;;
  419 + esac
  420 + fi
  421 +}
  422 +
  423 +#Preserving environment variables so they don't get clobbered by the config file
  424 +OVR_TODOTXT_AUTO_ARCHIVE="$TODOTXT_AUTO_ARCHIVE"
  425 +OVR_TODOTXT_FORCE="$TODOTXT_FORCE"
  426 +OVR_TODOTXT_PRESERVE_LINE_NUMBERS="$TODOTXT_PRESERVE_LINE_NUMBERS"
  427 +OVR_TODOTXT_PLAIN="$TODOTXT_PLAIN"
  428 +OVR_TODOTXT_DATE_ON_ADD="$TODOTXT_DATE_ON_ADD"
  429 +OVR_TODOTXT_DISABLE_FILTER="$TODOTXT_DISABLE_FILTER"
  430 +OVR_TODOTXT_VERBOSE="$TODOTXT_VERBOSE"
  431 +OVR_TODOTXT_DEFAULT_ACTION="$TODOTXT_DEFAULT_ACTION"
  432 +OVR_TODOTXT_SORT_COMMAND="$TODOTXT_SORT_COMMAND"
  433 +OVR_TODOTXT_FINAL_FILTER="$TODOTXT_FINAL_FILTER"
  434 +
  435 +# == PROCESS OPTIONS ==
  436 +while getopts ":fhpcnNaAtTvVx+@Pd:" Option
  437 +do
  438 + case $Option in
  439 + '@' )
  440 + ## HIDE_CONTEXT_NAMES starts at zero (false); increment it to one
  441 + ## (true) the first time this flag is seen. Each time the flag
  442 + ## is seen after that, increment it again so that an even
  443 + ## number shows context names and an odd number hides context
  444 + ## names.
  445 + : $(( HIDE_CONTEXT_NAMES++ ))
  446 + if [ $(( $HIDE_CONTEXT_NAMES % 2 )) -eq 0 ]
  447 + then
  448 + ## Zero or even value -- show context names
  449 + unset HIDE_CONTEXTS_SUBSTITUTION
  450 + else
  451 + ## One or odd value -- hide context names
  452 + export HIDE_CONTEXTS_SUBSTITUTION='[[:space:]]@[[:graph:]]\{1,\}'
  453 + fi
  454 + ;;
  455 + '+' )
  456 + ## HIDE_PROJECT_NAMES starts at zero (false); increment it to one
  457 + ## (true) the first time this flag is seen. Each time the flag
  458 + ## is seen after that, increment it again so that an even
  459 + ## number shows project names and an odd number hides project
  460 + ## names.
  461 + : $(( HIDE_PROJECT_NAMES++ ))
  462 + if [ $(( $HIDE_PROJECT_NAMES % 2 )) -eq 0 ]
  463 + then
  464 + ## Zero or even value -- show project names
  465 + unset HIDE_PROJECTS_SUBSTITUTION
  466 + else
  467 + ## One or odd value -- hide project names
  468 + export HIDE_PROJECTS_SUBSTITUTION='[[:space:]][+][[:graph:]]\{1,\}'
  469 + fi
  470 + ;;
  471 + a )
  472 + OVR_TODOTXT_AUTO_ARCHIVE=0
  473 + ;;
  474 + A )
  475 + OVR_TODOTXT_AUTO_ARCHIVE=1
  476 + ;;
  477 + c )
  478 + OVR_TODOTXT_PLAIN=0
  479 + ;;
  480 + d )
  481 + TODOTXT_CFG_FILE=$OPTARG
  482 + ;;
  483 + f )
  484 + OVR_TODOTXT_FORCE=1
  485 + ;;
  486 + h )
  487 + # Short-circuit option parsing and forward to the action.
  488 + # Cannot just invoke shorthelp() because we need the configuration
  489 + # processed to locate the add-on actions directory.
  490 + set -- '-h' 'shorthelp'
  491 + ;;
  492 + n )
  493 + OVR_TODOTXT_PRESERVE_LINE_NUMBERS=0
  494 + ;;
  495 + N )
  496 + OVR_TODOTXT_PRESERVE_LINE_NUMBERS=1
  497 + ;;
  498 + p )
  499 + OVR_TODOTXT_PLAIN=1
  500 + ;;
  501 + P )
  502 + ## HIDE_PRIORITY_LABELS starts at zero (false); increment it to one
  503 + ## (true) the first time this flag is seen. Each time the flag
  504 + ## is seen after that, increment it again so that an even
  505 + ## number shows priority labels and an odd number hides priority
  506 + ## labels.
  507 + : $(( HIDE_PRIORITY_LABELS++ ))
  508 + if [ $(( $HIDE_PRIORITY_LABELS % 2 )) -eq 0 ]
  509 + then
  510 + ## Zero or even value -- show priority labels
  511 + unset HIDE_PRIORITY_SUBSTITUTION
  512 + else
  513 + ## One or odd value -- hide priority labels
  514 + export HIDE_PRIORITY_SUBSTITUTION="([A-Z])[[:space:]]"
  515 + fi
  516 + ;;
  517 + t )
  518 + OVR_TODOTXT_DATE_ON_ADD=1
  519 + ;;
  520 + T )
  521 + OVR_TODOTXT_DATE_ON_ADD=0
  522 + ;;
  523 + v )
  524 + : $(( TODOTXT_VERBOSE++ ))
  525 + ;;
  526 + V )
  527 + version
  528 + ;;
  529 + x )
  530 + OVR_TODOTXT_DISABLE_FILTER=1
  531 + ;;
  532 + esac
  533 +done
  534 +shift $(($OPTIND - 1))
  535 +
  536 +# defaults if not yet defined
  537 +TODOTXT_VERBOSE=${TODOTXT_VERBOSE:-1}
  538 +TODOTXT_PLAIN=${TODOTXT_PLAIN:-0}
  539 +TODOTXT_CFG_FILE=${TODOTXT_CFG_FILE:-$HOME/.todo/config}
  540 +TODOTXT_FORCE=${TODOTXT_FORCE:-0}
  541 +TODOTXT_PRESERVE_LINE_NUMBERS=${TODOTXT_PRESERVE_LINE_NUMBERS:-1}
  542 +TODOTXT_AUTO_ARCHIVE=${TODOTXT_AUTO_ARCHIVE:-1}
  543 +TODOTXT_DATE_ON_ADD=${TODOTXT_DATE_ON_ADD:-0}
  544 +TODOTXT_DEFAULT_ACTION=${TODOTXT_DEFAULT_ACTION:-}
  545 +TODOTXT_SORT_COMMAND=${TODOTXT_SORT_COMMAND:-env LC_COLLATE=C sort -f -k2}
  546 +TODOTXT_DISABLE_FILTER=${TODOTXT_DISABLE_FILTER:-}
  547 +TODOTXT_FINAL_FILTER=${TODOTXT_FINAL_FILTER:-cat}
  548 +
  549 +# Export all TODOTXT_* variables
  550 +export ${!TODOTXT_@}
  551 +
  552 +# Default color map
  553 +export NONE=''
  554 +export BLACK='\\033[0;30m'
  555 +export RED='\\033[0;31m'
  556 +export GREEN='\\033[0;32m'
  557 +export BROWN='\\033[0;33m'
  558 +export BLUE='\\033[0;34m'
  559 +export PURPLE='\\033[0;35m'
  560 +export CYAN='\\033[0;36m'
  561 +export LIGHT_GREY='\\033[0;37m'
  562 +export DARK_GREY='\\033[1;30m'
  563 +export LIGHT_RED='\\033[1;31m'
  564 +export LIGHT_GREEN='\\033[1;32m'
  565 +export YELLOW='\\033[1;33m'
  566 +export LIGHT_BLUE='\\033[1;34m'
  567 +export LIGHT_PURPLE='\\033[1;35m'
  568 +export LIGHT_CYAN='\\033[1;36m'
  569 +export WHITE='\\033[1;37m'
  570 +export DEFAULT='\\033[0m'
  571 +
  572 +# Default priority->color map.
  573 +export PRI_A=$YELLOW # color for A priority
  574 +export PRI_B=$GREEN # color for B priority
  575 +export PRI_C=$LIGHT_BLUE # color for C priority
  576 +export PRI_X=$WHITE # color unless explicitly defined
  577 +
  578 +# Default highlight colors.
  579 +export COLOR_DONE=$LIGHT_GREY # color for done (but not yet archived) tasks
  580 +
  581 +# Default sentence delimiters for todo.sh append.
  582 +# If the text to be appended to the task begins with one of these characters, no
  583 +# whitespace is inserted in between. This makes appending to an enumeration
  584 +# (todo.sh add 42 ", foo") syntactically correct.
  585 +export SENTENCE_DELIMITERS=',.:;'
  586 +
  587 +[ -e "$TODOTXT_CFG_FILE" ] || {
  588 + CFG_FILE_ALT="$HOME/todo.cfg"
  589 +
  590 + if [ -e "$CFG_FILE_ALT" ]
  591 + then
  592 + TODOTXT_CFG_FILE="$CFG_FILE_ALT"
  593 + fi
  594 +}
  595 +
  596 +[ -e "$TODOTXT_CFG_FILE" ] || {
  597 + CFG_FILE_ALT="$HOME/.todo.cfg"
  598 +
  599 + if [ -e "$CFG_FILE_ALT" ]
  600 + then
  601 + TODOTXT_CFG_FILE="$CFG_FILE_ALT"
  602 + fi
  603 +}
  604 +
  605 +[ -e "$TODOTXT_CFG_FILE" ] || {
  606 + CFG_FILE_ALT=$(dirname "$0")"/todo.cfg"
  607 +
  608 + if [ -e "$CFG_FILE_ALT" ]
  609 + then
  610 + TODOTXT_CFG_FILE="$CFG_FILE_ALT"
  611 + fi
  612 +}
  613 +
  614 +
  615 +if [ -z "$TODO_ACTIONS_DIR" -o ! -d "$TODO_ACTIONS_DIR" ]
  616 +then
  617 + TODO_ACTIONS_DIR="$HOME/.todo/actions"
  618 + export TODO_ACTIONS_DIR
  619 +fi
  620 +
  621 +[ -d "$TODO_ACTIONS_DIR" ] || {
  622 + TODO_ACTIONS_DIR_ALT="$HOME/.todo.actions.d"
  623 +
  624 + if [ -d "$TODO_ACTIONS_DIR_ALT" ]
  625 + then
  626 + TODO_ACTIONS_DIR="$TODO_ACTIONS_DIR_ALT"
  627 + fi
  628 +}
  629 +
  630 +# === SANITY CHECKS (thanks Karl!) ===
  631 +[ -r "$TODOTXT_CFG_FILE" ] || die "Fatal Error: Cannot read configuration file $TODOTXT_CFG_FILE"
  632 +
  633 +. "$TODOTXT_CFG_FILE"
  634 +
  635 +# === APPLY OVERRIDES
  636 +if [ -n "$OVR_TODOTXT_AUTO_ARCHIVE" ] ; then
  637 + TODOTXT_AUTO_ARCHIVE="$OVR_TODOTXT_AUTO_ARCHIVE"
  638 +fi
  639 +if [ -n "$OVR_TODOTXT_FORCE" ] ; then
  640 + TODOTXT_FORCE="$OVR_TODOTXT_FORCE"
  641 +fi
  642 +if [ -n "$OVR_TODOTXT_PRESERVE_LINE_NUMBERS" ] ; then
  643 + TODOTXT_PRESERVE_LINE_NUMBERS="$OVR_TODOTXT_PRESERVE_LINE_NUMBERS"
  644 +fi
  645 +if [ -n "$OVR_TODOTXT_PLAIN" ] ; then
  646 + TODOTXT_PLAIN="$OVR_TODOTXT_PLAIN"
  647 +fi
  648 +if [ -n "$OVR_TODOTXT_DATE_ON_ADD" ] ; then
  649 + TODOTXT_DATE_ON_ADD="$OVR_TODOTXT_DATE_ON_ADD"
  650 +fi
  651 +if [ -n "$OVR_TODOTXT_DISABLE_FILTER" ] ; then
  652 + TODOTXT_DISABLE_FILTER="$OVR_TODOTXT_DISABLE_FILTER"
  653 +fi
  654 +if [ -n "$OVR_TODOTXT_VERBOSE" ] ; then
  655 + TODOTXT_VERBOSE="$OVR_TODOTXT_VERBOSE"
  656 +fi
  657 +if [ -n "$OVR_TODOTXT_DEFAULT_ACTION" ] ; then
  658 + TODOTXT_DEFAULT_ACTION="$OVR_TODOTXT_DEFAULT_ACTION"
  659 +fi
  660 +if [ -n "$OVR_TODOTXT_SORT_COMMAND" ] ; then
  661 + TODOTXT_SORT_COMMAND="$OVR_TODOTXT_SORT_COMMAND"
  662 +fi
  663 +if [ -n "$OVR_TODOTXT_FINAL_FILTER" ] ; then
  664 + TODOTXT_FINAL_FILTER="$OVR_TODOTXT_FINAL_FILTER"
  665 +fi
  666 +
  667 +ACTION=${1:-$TODOTXT_DEFAULT_ACTION}
  668 +
  669 +[ -z "$ACTION" ] && usage
  670 +[ -d "$TODO_DIR" ] || die "Fatal Error: $TODO_DIR is not a directory"
  671 +( cd "$TODO_DIR" ) || die "Fatal Error: Unable to cd to $TODO_DIR"
  672 +
  673 +[ -f "$TODO_FILE" ] || cp /dev/null "$TODO_FILE"
  674 +[ -f "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
  675 +[ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
  676 +
  677 +if [ $TODOTXT_PLAIN = 1 ]; then
  678 + for clr in ${!PRI_@}; do
  679 + export $clr=$NONE
  680 + done
  681 + PRI_X=$NONE
  682 + DEFAULT=$NONE
  683 + COLOR_DONE=$NONE
  684 +fi
  685 +
  686 +_addto() {
  687 + file="$1"
  688 + input="$2"
  689 + cleaninput
  690 +
  691 + if [[ $TODOTXT_DATE_ON_ADD = 1 ]]; then
  692 + now=$(date '+%Y-%m-%d')
  693 + input=$(echo "$input" | sed -e 's/^\(([A-Z]) \)\{0,1\}/\1'"$now /")
  694 + fi
  695 + echo "$input" >> "$file"
  696 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  697 + TASKNUM=$(sed -n '$ =' "$file")
  698 + echo "$TASKNUM $input"
  699 + echo "$(getPrefix "$file"): $TASKNUM added."
  700 + fi
  701 +}
  702 +
  703 +shellquote()
  704 +{
  705 + typeset -r qq=\'; printf %s\\n "'${1//\'/${qq}\\${qq}${qq}}'";
  706 +}
  707 +
  708 +filtercommand()
  709 +{
  710 + filter=${1:-}
  711 + shift
  712 + post_filter=${1:-}
  713 + shift
  714 +
  715 + for search_term
  716 + do
  717 + ## See if the first character of $search_term is a dash
  718 + if [ "${search_term:0:1}" != '-' ]
  719 + then
  720 + ## First character isn't a dash: hide lines that don't match
  721 + ## this $search_term
  722 + filter="${filter:-}${filter:+ | }grep -i $(shellquote "$search_term")"
  723 + else
  724 + ## First character is a dash: hide lines that match this
  725 + ## $search_term
  726 + #
  727 + ## Remove the first character (-) before adding to our filter command
  728 + filter="${filter:-}${filter:+ | }grep -v -i $(shellquote "${search_term:1}")"
  729 + fi
  730 + done
  731 +
  732 + [ -n "$post_filter" ] && {
  733 + filter="${filter:-}${filter:+ | }${post_filter:-}"
  734 + }
  735 +
  736 + printf %s "$filter"
  737 +}
  738 +
  739 +_list() {
  740 + local FILE="$1"
  741 + ## If the file starts with a "/" use absolute path. Otherwise,
  742 + ## try to find it in either $TODO_DIR or using a relative path
  743 + if [ "${1:0:1}" == / ]; then
  744 + ## Absolute path
  745 + src="$FILE"
  746 + elif [ -f "$TODO_DIR/$FILE" ]; then
  747 + ## Path relative to todo.sh directory
  748 + src="$TODO_DIR/$FILE"
  749 + elif [ -f "$FILE" ]; then
  750 + ## Path relative to current working directory
  751 + src="$FILE"
  752 + elif [ -f "$TODO_DIR/${FILE}.txt" ]; then
  753 + ## Path relative to todo.sh directory, missing file extension
  754 + src="$TODO_DIR/${FILE}.txt"
  755 + else
  756 + die "TODO: File $FILE does not exist."
  757 + fi
  758 +
  759 + ## Get our search arguments, if any
  760 + shift ## was file name, new $1 is first search term
  761 +
  762 + _format "$src" '' "$@"
  763 +
  764 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  765 + echo "--"
  766 + echo "$(getPrefix "$src"): ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown"
  767 + fi
  768 +}
  769 +getPadding()
  770 +{
  771 + ## We need one level of padding for each power of 10 $LINES uses.
  772 + LINES=$(sed -n '$ =' "${1:-$TODO_FILE}")
  773 + printf %s ${#LINES}
  774 +}
  775 +_format()
  776 +{
  777 + # Parameters: $1: todo input file; when empty formats stdin
  778 + # $2: ITEM# number width; if empty auto-detects from $1 / $TODO_FILE.
  779 + # Precondition: None
  780 + # Postcondition: $NUMTASKS and $TOTALTASKS contain statistics (unless $TODOTXT_VERBOSE=0).
  781 +
  782 + FILE=$1
  783 + shift
  784 +
  785 + ## Figure out how much padding we need to use, unless this was passed to us.
  786 + PADDING=${1:-$(getPadding "$FILE")}
  787 + shift
  788 +
  789 + ## Number the file, then run the filter command,
  790 + ## then sort and mangle output some more
  791 + if [[ $TODOTXT_DISABLE_FILTER = 1 ]]; then
  792 + TODOTXT_FINAL_FILTER="cat"
  793 + fi
  794 + items=$(
  795 + if [ "$FILE" ]; then
  796 + sed = "$FILE"
  797 + else
  798 + sed =
  799 + fi \
  800 + | sed -e '''
  801 + N
  802 + s/^/ /
  803 + s/ *\([ 0-9]\{'"$PADDING"',\}\)\n/\1 /
  804 + /^[ 0-9]\{1,\} *$/d
  805 + '''
  806 + )
  807 +
  808 + ## Build and apply the filter.
  809 + filter_command=$(filtercommand "${pre_filter_command:-}" "${post_filter_command:-}" "$@")
  810 + if [ "${filter_command}" ]; then
  811 + filtered_items=$(echo -n "$items" | eval "${filter_command}")
  812 + else
  813 + filtered_items=$items
  814 + fi
  815 + filtered_items=$(
  816 + echo -n "$filtered_items" \
  817 + | sed '''
  818 + s/^ /00000/;
  819 + s/^ /0000/;
  820 + s/^ /000/;
  821 + s/^ /00/;
  822 + s/^ /0/;
  823 + ''' \
  824 + | eval ${TODOTXT_SORT_COMMAND} \
  825 + | awk '''
  826 + function highlight(colorVar, color) {
  827 + color = ENVIRON[colorVar]
  828 + gsub(/\\+033/, "\033", color)
  829 + return color
  830 + }
  831 + {
  832 + if (match($0, /^[0-9]+ x /)) {
  833 + print highlight("COLOR_DONE") $0 highlight("DEFAULT")
  834 + } else if (match($0, /^[0-9]+ \([A-Z]\) /)) {
  835 + clr = highlight("PRI_" substr($0, RSTART + RLENGTH - 3, 1))
  836 + print \
  837 + (clr ? clr : highlight("PRI_X")) \
  838 + (ENVIRON["HIDE_PRIORITY_SUBSTITUTION"] == "" ? $0 : substr($0, 1, RLENGTH - 4) substr($0, RSTART + RLENGTH)) \
  839 + highlight("DEFAULT")
  840 + } else { print }
  841 + }
  842 + ''' \
  843 + | sed '''
  844 + s/'"${HIDE_PROJECTS_SUBSTITUTION:-^}"'//g
  845 + s/'"${HIDE_CONTEXTS_SUBSTITUTION:-^}"'//g
  846 + s/'"${HIDE_CUSTOM_SUBSTITUTION:-^}"'//g
  847 + ''' \
  848 + | eval ${TODOTXT_FINAL_FILTER} \
  849 + )
  850 + [ "$filtered_items" ] && echo "$filtered_items"
  851 +
  852 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  853 + NUMTASKS=$( echo -n "$filtered_items" | sed -n '$ =' )
  854 + TOTALTASKS=$( echo -n "$items" | sed -n '$ =' )
  855 + fi
  856 + if [ $TODOTXT_VERBOSE -gt 1 ]; then
  857 + echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
  858 + fi
  859 +}
  860 +
  861 +export -f cleaninput getPrefix getTodo getNewtodo shellquote filtercommand _list getPadding _format die
  862 +
  863 +# == HANDLE ACTION ==
  864 +action=$( printf "%s\n" "$ACTION" | tr 'A-Z' 'a-z' )
  865 +
  866 +## If the first argument is "command", run the rest of the arguments
  867 +## using todo.sh builtins.
  868 +## Else, run a actions script with the name of the command if it exists
  869 +## or fallback to using a builtin
  870 +if [ "$action" == command ]
  871 +then
  872 + ## Get rid of "command" from arguments list
  873 + shift
  874 + ## Reset action to new first argument
  875 + action=$( printf "%s\n" "$1" | tr 'A-Z' 'a-z' )
  876 +elif [ -d "$TODO_ACTIONS_DIR" -a -x "$TODO_ACTIONS_DIR/$action" ]
  877 +then
  878 + "$TODO_ACTIONS_DIR/$action" "$@"
  879 + exit $?
  880 +fi
  881 +
  882 +## Only run if $action isn't found in .todo.actions.d
  883 +case $action in
  884 +"add" | "a")
  885 + if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
  886 + echo -n "Add: "
  887 + read input
  888 + else
  889 + [ -z "$2" ] && die "usage: $TODO_SH add \"TODO ITEM\""
  890 + shift
  891 + input=$*
  892 + fi
  893 + _addto "$TODO_FILE" "$input"
  894 + ;;
  895 +
  896 +"addm")
  897 + if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
  898 + echo -n "Add: "
  899 + read input
  900 + else
  901 + [ -z "$2" ] && die "usage: $TODO_SH addm \"TODO ITEM\""
  902 + shift
  903 + input=$*
  904 + fi
  905 +
  906 + # Set Internal Field Seperator as newline so we can
  907 + # loop across multiple lines
  908 + SAVEIFS=$IFS
  909 + IFS=$'\n'
  910 +
  911 + # Treat each line seperately
  912 + for line in $input ; do
  913 + _addto "$TODO_FILE" "$line"
  914 + done
  915 + IFS=$SAVEIFS
  916 + ;;
  917 +
  918 +"addto" )
  919 + [ -z "$2" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\""
  920 + dest="$TODO_DIR/$2"
  921 + [ -z "$3" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\""
  922 + shift
  923 + shift
  924 + input=$*
  925 +
  926 + if [ -f "$dest" ]; then
  927 + _addto "$dest" "$input"
  928 + else
  929 + die "TODO: Destination file $dest does not exist."
  930 + fi
  931 + ;;
  932 +
  933 +"append" | "app" )
  934 + errmsg="usage: $TODO_SH append ITEM# \"TEXT TO APPEND\""
  935 + shift; item=$1; shift
  936 + getTodo "$item"
  937 +
  938 + if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
  939 + echo -n "Append: "
  940 + read input
  941 + else
  942 + input=$*
  943 + fi
  944 + case "$input" in
  945 + [$SENTENCE_DELIMITERS]*) appendspace=;;
  946 + *) appendspace=" ";;
  947 + esac
  948 + cleaninput "for sed"
  949 +
  950 + if sed -i.bak $item" s|^.*|&${appendspace}${input}|" "$TODO_FILE"; then
  951 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  952 + getNewtodo "$item"
  953 + echo "$item $newtodo"
  954 + fi
  955 + else
  956 + die "TODO: Error appending task $item."
  957 + fi
  958 + ;;
  959 +
  960 +"archive" )
  961 + # defragment blank lines
  962 + sed -i.bak -e '/./!d' "$TODO_FILE"
  963 + [ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"
  964 + grep "^x " "$TODO_FILE" >> "$DONE_FILE"
  965 + sed -i.bak '/^x /d' "$TODO_FILE"
  966 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  967 + echo "TODO: $TODO_FILE archived."
  968 + fi
  969 + ;;
  970 +
  971 +"del" | "rm" )
  972 + # replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
  973 + errmsg="usage: $TODO_SH del ITEM# [TERM]"
  974 + item=$2
  975 + getTodo "$item"
  976 +
  977 + if [ -z "$3" ]; then
  978 + if [ $TODOTXT_FORCE = 0 ]; then
  979 + echo "Delete '$todo'? (y/n)"
  980 + read ANSWER
  981 + else
  982 + ANSWER="y"
  983 + fi
  984 + if [ "$ANSWER" = "y" ]; then
  985 + if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
  986 + # delete line (changes line numbers)
  987 + sed -i.bak -e $item"s/^.*//" -e '/./!d' "$TODO_FILE"
  988 + else
  989 + # leave blank line behind (preserves line numbers)
  990 + sed -i.bak -e $item"s/^.*//" "$TODO_FILE"
  991 + fi
  992 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  993 + echo "$item $todo"
  994 + echo "TODO: $item deleted."
  995 + fi
  996 + else
  997 + echo "TODO: No tasks were deleted."
  998 + fi
  999 + else
  1000 + sed -i.bak \
  1001 + -e $item"s/^\((.) \)\{0,1\} *$3 */\1/g" \
  1002 + -e $item"s/ *$3 *\$//g" \
  1003 + -e $item"s/ *$3 */ /g" \
  1004 + -e $item"s/ *$3 */ /g" \
  1005 + -e $item"s/$3//g" \
  1006 + "$TODO_FILE"
  1007 + getNewtodo "$item"
  1008 + if [ "$todo" = "$newtodo" ]; then
  1009 + [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item $todo"
  1010 + die "TODO: '$3' not found; no removal done."
  1011 + fi
  1012 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  1013 + echo "$item $todo"
  1014 + echo "TODO: Removed '$3' from task."
  1015 + echo "$item $newtodo"
  1016 + fi
  1017 + fi
  1018 + ;;
  1019 +
  1020 +"depri" | "dp" )
  1021 + errmsg="usage: $TODO_SH depri ITEM#[, ITEM#, ITEM#, ...]"
  1022 + shift;
  1023 + [ $# -eq 0 ] && die "$errmsg"
  1024 +
  1025 + # Split multiple depri's, if comma separated change to whitespace separated
  1026 + # Loop the 'depri' function for each item
  1027 + for item in ${*//,/ }; do
  1028 + getTodo "$item"
  1029 +
  1030 + if [[ "$todo" = \(?\)\ * ]]; then
  1031 + sed -i.bak -e $item"s/^(.) //" "$TODO_FILE"
  1032 + if [ $TODOTXT_VERBOSE -gt 0 ]; then
  1033 + getNewtodo "$item"
  1034 + echo "$item $newtodo"
  1035 + echo "TODO: $item deprioritized."
  1036 + fi
  1037 + else
  1038 + echo "TODO: $item is not prioritized."