todo.sh 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. #! /bin/bash
  2. # NOTE: Todo.sh requires the .todo/config configuration file to run.
  3. # Place the .todo/config file in your home directory or use the -d option for a custom location.
  4. # Your todo/done/report.txt locations
  5. export TODO_DIR=$(pwd)
  6. export TODO_FILE="$TODO_DIR/todo.txt"
  7. export DONE_FILE="$TODO_DIR/done.txt"
  8. export REPORT_FILE="$TODO_DIR/report.txt"
  9. export TMP_FILE="$TODO_DIR/todo.tmp"
  10. [ -f VERSION-FILE ] && . VERSION-FILE || VERSION="@DEV_VERSION@"
  11. version() { sed -e 's/^ //' <<EndVersion
  12. TODO.TXT Command Line Interface v$VERSION
  13. First release: 5/11/2006
  14. Original conception by: Gina Trapani (http://ginatrapani.org)
  15. Contributors: http://github.com/ginatrapani/todo.txt-cli/network
  16. License: GPL, http://www.gnu.org/copyleft/gpl.html
  17. More information and mailing list at http://todotxt.com
  18. Code repository: http://github.com/ginatrapani/todo.txt-cli/tree/master
  19. EndVersion
  20. exit 1
  21. }
  22. # Set script name early.
  23. TODO_SH=$(basename "$0")
  24. export TODO_SH
  25. oneline_usage="$TODO_SH [-fhpantvV] [-d todo_config] action [task_number] [task_description]"
  26. usage()
  27. {
  28. sed -e 's/^ //' <<EndUsage
  29. Usage: $oneline_usage
  30. Try '$TODO_SH -h' for more information.
  31. EndUsage
  32. exit 1
  33. }
  34. shorthelp()
  35. {
  36. sed -e 's/^ //' <<EndHelp
  37. Usage: $oneline_usage
  38. Actions:
  39. add|a "THING I NEED TO DO +project @context"
  40. addto DEST "TEXT TO ADD"
  41. append|app NUMBER "TEXT TO APPEND"
  42. archive
  43. command [ACTIONS]
  44. del|rm NUMBER [TERM]
  45. dp|depri NUMBER
  46. do NUMBER
  47. help
  48. list|ls [TERM...]
  49. listall|lsa [TERM...]
  50. listcon|lsc
  51. listfile|lf SRC [TERM...]
  52. listpri|lsp [PRIORITY]
  53. listproj|lsprj
  54. move|mv NUMBER DEST [SRC]
  55. prepend|prep NUMBER "TEXT TO PREPEND"
  56. pri|p NUMBER PRIORITY
  57. replace NUMBER "UPDATED TODO"
  58. report
  59. See "help" for more details.
  60. EndHelp
  61. exit 0
  62. }
  63. help()
  64. {
  65. sed -e 's/^ //' <<EndHelp
  66. Usage: $oneline_usage
  67. Actions:
  68. add "THING I NEED TO DO +project @context"
  69. a "THING I NEED TO DO +project @context"
  70. Adds THING I NEED TO DO to your todo.txt file on its own line.
  71. Project and context notation optional.
  72. Quotes optional.
  73. addto DEST "TEXT TO ADD"
  74. Adds a line of text to any file located in the todo.txt directory.
  75. For example, addto inbox.txt "decide about vacation"
  76. append NUMBER "TEXT TO APPEND"
  77. app NUMBER "TEXT TO APPEND"
  78. Adds TEXT TO APPEND to the end of the todo on line NUMBER.
  79. Quotes optional.
  80. archive
  81. Moves done items from todo.txt to done.txt and removes blank lines.
  82. command [ACTIONS]
  83. Runs the remaining arguments using only todo.sh builtins.
  84. Will not call any .todo.actions.d scripts.
  85. del NUMBER [TERM]
  86. rm NUMBER [TERM]
  87. Deletes the item on line NUMBER in todo.txt.
  88. If term specified, deletes only the term from the line.
  89. depri NUMBER
  90. dp NUMBER
  91. Deprioritizes (removes the priority) from the item
  92. on line NUMBER in todo.txt.
  93. do NUMBER[, NUMBER, NUMBER, ...]
  94. Marks item(s) on line NUMBER as done in todo.txt.
  95. help
  96. Display this help message.
  97. list [TERM...]
  98. ls [TERM...]
  99. Displays all todo's that contain TERM(s) sorted by priority with line
  100. numbers. If no TERM specified, lists entire todo.txt.
  101. listall [TERM...]
  102. lsa [TERM...]
  103. Displays all the lines in todo.txt AND done.txt that contain TERM(s)
  104. sorted by priority with line numbers. If no TERM specified, lists
  105. entire todo.txt AND done.txt concatenated and sorted.
  106. listcon
  107. lsc
  108. Lists all the task contexts that start with the @ sign in todo.txt.
  109. listfile SRC [TERM...]
  110. lf SRC [TERM...]
  111. Displays all the lines in SRC file located in the todo.txt directory,
  112. sorted by priority with line numbers. If TERM specified, lists
  113. all lines that contain TERM in SRC file.
  114. listpri [PRIORITY]
  115. lsp [PRIORITY]
  116. Displays all items prioritized PRIORITY.
  117. If no PRIORITY specified, lists all prioritized items.
  118. listproj
  119. lsprj
  120. Lists all the projects that start with the + sign in todo.txt.
  121. move NUMBER DEST [SRC]
  122. mv NUMBER DEST [SRC]
  123. Moves a line from source text file (SRC) to destination text file (DEST).
  124. Both source and destination file must be located in the directory defined
  125. in the configuration directory. When SRC is not defined
  126. it's by default todo.txt.
  127. prepend NUMBER "TEXT TO PREPEND"
  128. prep NUMBER "TEXT TO PREPEND"
  129. Adds TEXT TO PREPEND to the beginning of the todo on line NUMBER.
  130. Quotes optional.
  131. pri NUMBER PRIORITY
  132. p NUMBER PRIORITY
  133. Adds PRIORITY to todo on line NUMBER. If the item is already
  134. prioritized, replaces current priority with new PRIORITY.
  135. PRIORITY must be an uppercase letter between A and Z.
  136. replace NUMBER "UPDATED TODO"
  137. Replaces todo on line NUMBER with UPDATED TODO.
  138. report
  139. Adds the number of open todo's and closed done's to report.txt.
  140. Options:
  141. -@
  142. Hide context names in list output. Use twice to show context
  143. names (default).
  144. -+
  145. Hide project names in list output. Use twice to show project
  146. names (default).
  147. -d CONFIG_FILE
  148. Use a configuration file other than the default ~/.todo/config
  149. -f
  150. Forces actions without confirmation or interactive input
  151. -h
  152. Display a short help message
  153. -p
  154. Plain mode turns off colors
  155. -P
  156. Hide priority labels in list output. Use twice to show
  157. priority labels (default).
  158. -a
  159. Don't auto-archive tasks automatically on completion
  160. -n
  161. Don't preserve line numbers; automatically remove blank lines
  162. on task deletion
  163. -t
  164. Prepend the current date to a task automatically
  165. when it's added.
  166. -v
  167. Verbose mode turns on confirmation messages
  168. -vv
  169. Extra verbose mode prints some debugging information
  170. -V
  171. Displays version, license and credits
  172. Environment variables:
  173. TODOTXT_AUTO_ARCHIVE=0 is same as option -a
  174. TODOTXT_CFG_FILE=CONFIG_FILE is same as option -d CONFIG_FILE
  175. TODOTXT_FORCE=1 is same as option -f
  176. TODOTXT_PRESERVE_LINE_NUMBERS=0 is same as option -n
  177. TODOTXT_PLAIN=1 is same as option -p
  178. TODOTXT_DATE_ON_ADD=1 is same as option -t
  179. TODOTXT_VERBOSE=1 is same as option -v
  180. TODOTXT_DEFAULT_ACTION="" run this when called with no arguments
  181. TODOTXT_SORT_COMMAND="sort ..." customize list output
  182. TODOTXT_FINAL_FILTER="sed ..." customize list after color, P@+ hiding
  183. EndHelp
  184. if [ -d "$TODO_ACTIONS_DIR" ]
  185. then
  186. echo ""
  187. for action in "$TODO_ACTIONS_DIR"/*
  188. do
  189. if [ -x "$action" ]
  190. then
  191. "$action" usage
  192. fi
  193. done
  194. echo ""
  195. fi
  196. exit 1
  197. }
  198. die()
  199. {
  200. echo "$*"
  201. exit 1
  202. }
  203. cleanup()
  204. {
  205. [ -f "$TMP_FILE" ] && rm "$TMP_FILE"
  206. exit 0
  207. }
  208. archive()
  209. {
  210. #defragment blank lines
  211. sed -i.bak -e '/./!d' "$TODO_FILE"
  212. [ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"
  213. grep "^x " "$TODO_FILE" >> "$DONE_FILE"
  214. sed -i.bak '/^x /d' "$TODO_FILE"
  215. cp "$TODO_FILE" "$TMP_FILE"
  216. sed -n 'G; s/\n/&&/; /^\([ ~-]*\n\).*\n\1/d; s/\n//; h; P' "$TMP_FILE" > "$TODO_FILE"
  217. #[[ $TODOTXT_VERBOSE -gt 0 ]] && echo "TODO: Duplicate tasks have been removed."
  218. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $TODO_FILE archived."
  219. cleanup
  220. }
  221. # == PROCESS OPTIONS ==
  222. while getopts ":fhpnatvV+@Pd:" Option
  223. do
  224. case $Option in
  225. '@' )
  226. ## HIDE_CONTEXT_NAMES starts at zero (false); increment it to one
  227. ## (true) the first time this flag is seen. Each time the flag
  228. ## is seen after that, increment it again so that an even
  229. ## number hides project names and an odd number shows project
  230. ## names.
  231. : $(( HIDE_CONTEXT_NAMES++ ))
  232. if [ $(( $HIDE_CONTEXT_NAMES % 2 )) -eq 0 ]
  233. then
  234. ## Zero or even value -- show context names
  235. unset HIDE_CONTEXTS_SUBSTITUTION
  236. else
  237. ## One or odd value -- hide context names
  238. export HIDE_CONTEXTS_SUBSTITUTION='[[:space:]]@[^[:space:]]\{1,\}'
  239. fi
  240. ;;
  241. '+' )
  242. ## HIDE_PROJECT_NAMES starts at zero (false); increment it to one
  243. ## (true) the first time this flag is seen. Each time the flag
  244. ## is seen after that, increment it again so that an even
  245. ## number hides project names and an odd number shows project
  246. ## names.
  247. : $(( HIDE_PROJECT_NAMES++ ))
  248. if [ $(( $HIDE_PROJECT_NAMES % 2 )) -eq 0 ]
  249. then
  250. ## Zero or even value -- show project names
  251. unset HIDE_PROJECTS_SUBSTITUTION
  252. else
  253. ## One or odd value -- hide project names
  254. export HIDE_PROJECTS_SUBSTITUTION='[[:space:]][+][^[:space:]]\{1,\}'
  255. fi
  256. ;;
  257. a )
  258. TODOTXT_AUTO_ARCHIVE=0
  259. ;;
  260. d )
  261. TODOTXT_CFG_FILE=$OPTARG
  262. ;;
  263. f )
  264. TODOTXT_FORCE=1
  265. ;;
  266. h )
  267. shorthelp
  268. ;;
  269. n )
  270. TODOTXT_PRESERVE_LINE_NUMBERS=0
  271. ;;
  272. p )
  273. TODOTXT_PLAIN=1
  274. ;;
  275. P )
  276. ## HIDE_PRIORITY_LABELS starts at zero (false); increment it to one
  277. ## (true) the first time this flag is seen. Each time the flag
  278. ## is seen after that, increment it again so that an even
  279. ## number hides project names and an odd number shows project
  280. ## names.
  281. : $(( HIDE_PRIORITY_LABELS++ ))
  282. if [ $(( $HIDE_PRIORITY_LABELS % 2 )) -eq 0 ]
  283. then
  284. ## Zero or even value -- show priority labels
  285. unset HIDE_PRIORITY_SUBSTITUTION
  286. else
  287. ## One or odd value -- hide priority labels
  288. export HIDE_PRIORITY_SUBSTITUTION="([A-Z])[[:space:]]"
  289. fi
  290. ;;
  291. t )
  292. TODOTXT_DATE_ON_ADD=1
  293. ;;
  294. v )
  295. : $(( TODOTXT_VERBOSE++ ))
  296. ;;
  297. V )
  298. version
  299. ;;
  300. esac
  301. done
  302. shift $(($OPTIND - 1))
  303. # defaults if not yet defined
  304. TODOTXT_VERBOSE=${TODOTXT_VERBOSE:-1}
  305. TODOTXT_PLAIN=${TODOTXT_PLAIN:-0}
  306. TODOTXT_CFG_FILE=${TODOTXT_CFG_FILE:-$HOME/.todo/config}
  307. TODOTXT_FORCE=${TODOTXT_FORCE:-0}
  308. TODOTXT_PRESERVE_LINE_NUMBERS=${TODOTXT_PRESERVE_LINE_NUMBERS:-1}
  309. TODOTXT_AUTO_ARCHIVE=${TODOTXT_AUTO_ARCHIVE:-1}
  310. TODOTXT_DATE_ON_ADD=${TODOTXT_DATE_ON_ADD:-0}
  311. TODOTXT_DEFAULT_ACTION=${TODOTXT_DEFAULT_ACTION:-}
  312. TODOTXT_SORT_COMMAND=${TODOTXT_SORT_COMMAND:-env LC_COLLATE=C sort -f -k2}
  313. TODOTXT_FINAL_FILTER=${TODOTXT_FINAL_FILTER:-cat}
  314. # Export all TODOTXT_* variables
  315. export ${!TODOTXT_@}
  316. # Default color map
  317. export NONE=''
  318. export BLACK='\\033[0;30m'
  319. export RED='\\033[0;31m'
  320. export GREEN='\\033[0;32m'
  321. export BROWN='\\033[0;33m'
  322. export BLUE='\\033[0;34m'
  323. export PURPLE='\\033[0;35m'
  324. export CYAN='\\033[0;36m'
  325. export LIGHT_GREY='\\033[0;37m'
  326. export DARK_GREY='\\033[1;30m'
  327. export LIGHT_RED='\\033[1;31m'
  328. export LIGHT_GREEN='\\033[1;32m'
  329. export YELLOW='\\033[1;33m'
  330. export LIGHT_BLUE='\\033[1;34m'
  331. export LIGHT_PURPLE='\\033[1;35m'
  332. export LIGHT_CYAN='\\033[1;36m'
  333. export WHITE='\\033[1;37m'
  334. export DEFAULT='\\033[0m'
  335. # Default priority->color map.
  336. export PRI_A=$YELLOW # color for A priority
  337. export PRI_B=$GREEN # color for B priority
  338. export PRI_C=$LIGHT_BLUE # color for C priority
  339. export PRI_X=$WHITE # color for rest of them
  340. [ -e "$TODOTXT_CFG_FILE" ] || {
  341. CFG_FILE_ALT="$HOME/todo.cfg"
  342. if [ -e "$CFG_FILE_ALT" ]
  343. then
  344. TODOTXT_CFG_FILE="$CFG_FILE_ALT"
  345. fi
  346. }
  347. [ -e "$TODOTXT_CFG_FILE" ] || {
  348. CFG_FILE_ALT="$HOME/.todo.cfg"
  349. if [ -e "$CFG_FILE_ALT" ]
  350. then
  351. TODOTXT_CFG_FILE="$CFG_FILE_ALT"
  352. fi
  353. }
  354. if [ -z "$TODO_ACTIONS_DIR" -o ! -d "$TODO_ACTIONS_DIR" ]
  355. then
  356. TODO_ACTIONS_DIR="$HOME/.todo/actions"
  357. export TODO_ACTIONS_DIR
  358. fi
  359. [ -d "$TODO_ACTIONS_DIR" ] || {
  360. TODO_ACTIONS_DIR_ALT="$HOME/.todo.actions.d"
  361. if [ -d "$TODO_ACTIONS_DIR_ALT" ]
  362. then
  363. TODO_ACTIONS_DIR="$TODO_ACTIONS_DIR_ALT"
  364. fi
  365. }
  366. # === SANITY CHECKS (thanks Karl!) ===
  367. #[ -r "$TODOTXT_CFG_FILE" ] || die "Fatal error: Cannot read configuration file $TODOTXT_CFG_FILE"
  368. #. "$TODOTXT_CFG_FILE"
  369. ACTION=${1:-$TODOTXT_DEFAULT_ACTION}
  370. [ -z "$ACTION" ] && usage
  371. [ -d "$TODO_DIR" ] || die "Fatal Error: $TODO_DIR is not a directory"
  372. ( cd "$TODO_DIR" ) || die "Fatal Error: Unable to cd to $TODO_DIR"
  373. [ -w "$TMP_FILE" ] || echo -n > "$TMP_FILE" || die "Fatal Error: Unable to write to $TMP_FILE"
  374. [ -f "$TODO_FILE" ] || cp /dev/null "$TODO_FILE"
  375. [ -f "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
  376. [ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
  377. if [ $TODOTXT_PLAIN = 1 ]; then
  378. PRI_A=$NONE
  379. PRI_B=$NONE
  380. PRI_C=$NONE
  381. PRI_X=$NONE
  382. DEFAULT=$NONE
  383. fi
  384. # === HEAVY LIFTING ===
  385. shopt -s extglob
  386. _list() {
  387. local FILE="$1"
  388. ## If the file starts with a "/" use absolute path. Otherwise,
  389. ## try to find it in either $TODO_DIR or using a relative path
  390. if [ "${1:0:1}" == / ]
  391. then
  392. ## Absolute path
  393. src="$FILE"
  394. elif [ -f "$TODO_DIR/$FILE" ]
  395. then
  396. ## Path relative to todo.sh directory
  397. src="$TODO_DIR/$1"
  398. elif [ -f "$FILE" ]
  399. then
  400. ## Path relative to current working directory
  401. src="$FILE"
  402. else
  403. echo "TODO: File $FILE does not exist."
  404. exit 1
  405. fi
  406. ## Get our search arguments, if any
  407. shift ## was file name, new $1 is first search term
  408. ## Prefix the filter_command with the pre_filter_command
  409. filter_command="${pre_filter_command:-}"
  410. for search_term in "$@"
  411. do
  412. ## See if the first character of $search_term is a dash
  413. if [ ${search_term:0:1} != '-' ]
  414. then
  415. ## First character isn't a dash: hide lines that don't match
  416. ## this $search_term
  417. filter_command="${filter_command:-} ${filter_command:+|} \
  418. grep -i \"$search_term\" "
  419. else
  420. ## First character is a dash: hide lines that match this
  421. ## $search_term
  422. #
  423. ## Remove the first character (-) before adding to our filter command
  424. filter_command="${filter_command:-} ${filter_command:+|} \
  425. grep -v -i \"${search_term:1}\" "
  426. fi
  427. done
  428. ## If post_filter_command is set, append it to the filter_command
  429. [ -n "$post_filter_command" ] && {
  430. filter_command="${filter_command:-}${filter_command:+ | }${post_filter_command:-}"
  431. }
  432. ## Figure out how much padding we need to use
  433. ## We need one level of padding for each power of 10 $LINES uses
  434. LINES=$( sed -n '$ =' "$src" )
  435. PADDING=${#LINES}
  436. ## Number the file, then run the filter command,
  437. ## then sort and mangle output some more
  438. items=$(
  439. sed = "$src" \
  440. | sed "N; s/^/ /; s/ *\(.\{$PADDING,\}\)\n/\1 /" \
  441. | grep -v "^[0-9]\+ *$"
  442. )
  443. if [ "${filter_command}" ]; then
  444. filtered_items=$(echo -ne "$items" | eval ${filter_command})
  445. else
  446. filtered_items=$items
  447. fi
  448. filtered_items=$(
  449. echo -ne "$filtered_items" \
  450. | sed '''
  451. s/^ /00000/;
  452. s/^ /0000/;
  453. s/^ /000/;
  454. s/^ /00/;
  455. s/^ /0/;
  456. ''' \
  457. | eval ${TODOTXT_SORT_COMMAND} \
  458. | sed '''
  459. /^[0-9]\{'$PADDING'\} x /! {
  460. s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g;
  461. s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g;
  462. s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g;
  463. s/\(.*([D-Z]).*\)/'$PRI_X'\1'$DEFAULT'/g;
  464. }
  465. ''' \
  466. | sed '''
  467. s/'${HIDE_PRIORITY_SUBSTITUTION:-^}'//g
  468. s/'${HIDE_PROJECTS_SUBSTITUTION:-^}'//g
  469. s/'${HIDE_CONTEXTS_SUBSTITUTION:-^}'//g
  470. ''' \
  471. | eval ${TODOTXT_FINAL_FILTER} \
  472. )
  473. echo -ne "$filtered_items${filtered_items:+\n}"
  474. if [ $TODOTXT_VERBOSE -gt 0 ]; then
  475. NUMTASKS=$( echo -ne "$filtered_items" | sed -n '$ =' )
  476. TOTALTASKS=$( echo -ne "$items" | sed -n '$ =' )
  477. echo "--"
  478. echo "TODO: ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown from $FILE"
  479. fi
  480. if [ $TODOTXT_VERBOSE -gt 1 ]
  481. then
  482. echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
  483. fi
  484. }
  485. export -f _list
  486. # == HANDLE ACTION ==
  487. action=$( printf "%s\n" "$ACTION" | tr 'A-Z' 'a-z' )
  488. ## If the first argument is "command", run the rest of the arguments
  489. ## using todo.sh builtins.
  490. ## Else, run a actions script with the name of the command if it exists
  491. ## or fallback to using a builtin
  492. if [ "$action" == command ]
  493. then
  494. ## Get rid of "command" from arguments list
  495. shift
  496. ## Reset action to new first argument
  497. action=$( printf "%s\n" "$1" | tr 'A-Z' 'a-z' )
  498. elif [ -d "$TODO_ACTIONS_DIR" -a -x "$TODO_ACTIONS_DIR/$action" ]
  499. then
  500. "$TODO_ACTIONS_DIR/$action" "$@"
  501. cleanup
  502. fi
  503. ## Only run if $action isn't found in .todo.actions.d
  504. case $action in
  505. "add" | "a")
  506. if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
  507. echo -n "Add: "
  508. read input
  509. else
  510. [ -z "$2" ] && die "usage: $TODO_SH add \"TODO ITEM\""
  511. shift
  512. input=$*
  513. fi
  514. if [[ $TODOTXT_DATE_ON_ADD = 1 ]]; then
  515. now=`date '+%Y-%m-%d'`
  516. input="$now $input"
  517. fi
  518. echo "$input" >> "$TODO_FILE"
  519. TASKNUM=$(sed -n '$ =' "$TODO_FILE")
  520. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$input' added on line $TASKNUM."
  521. cleanup;;
  522. "addto" )
  523. [ -z "$2" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\""
  524. dest="$TODO_DIR/$2"
  525. [ -z "$3" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\""
  526. shift
  527. shift
  528. input=$*
  529. if [ -f "$dest" ]; then
  530. echo "$input" >> "$dest"
  531. TASKNUM=$(sed -n '$ =' "$dest")
  532. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$input' added to $dest on line $TASKNUM."
  533. else
  534. echo "TODO: Destination file $dest does not exist."
  535. fi
  536. cleanup;;
  537. "append" | "app" )
  538. errmsg="usage: $TODO_SH append ITEM# \"TEXT TO APPEND\""
  539. shift; item=$1; shift
  540. [ -z "$item" ] && die "$errmsg"
  541. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  542. todo=$(sed "$item!d" "$TODO_FILE")
  543. [ -z "$todo" ] && die "$item: No such todo."
  544. if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
  545. echo -n "Append: "
  546. read input
  547. else
  548. input=$*
  549. fi
  550. if sed -i.bak $item" s|^.*|& $input|" "$TODO_FILE"; then
  551. newtodo=$(sed "$item!d" "$TODO_FILE")
  552. [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
  553. else
  554. echo "TODO: Error appending task $item."
  555. fi
  556. cleanup;;
  557. "archive" )
  558. archive;;
  559. "del" | "rm" )
  560. # replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
  561. errmsg="usage: $TODO_SH del ITEM#"
  562. item=$2
  563. [ -z "$item" ] && die "$errmsg"
  564. if [ -z "$3" ]; then
  565. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  566. if sed -ne "$item p" "$TODO_FILE" | grep "^."; then
  567. DELETEME=$(sed "$item!d" "$TODO_FILE")
  568. if [ $TODOTXT_FORCE = 0 ]; then
  569. echo "Delete '$DELETEME'? (y/n)"
  570. read ANSWER
  571. else
  572. ANSWER="y"
  573. fi
  574. if [ "$ANSWER" = "y" ]; then
  575. if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
  576. # delete line (changes line numbers)
  577. sed -i.bak -e $item"s/^.*//" -e '/./!d' "$TODO_FILE"
  578. else
  579. # leave blank line behind (preserves line numbers)
  580. sed -i.bak -e $item"s/^.*//" "$TODO_FILE"
  581. fi
  582. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$DELETEME' deleted."
  583. cleanup
  584. else
  585. echo "TODO: No tasks were deleted."
  586. fi
  587. else
  588. echo "$item: No such todo."
  589. fi
  590. else
  591. sed -i.bak -e $item"s/$3/ /g" "$TODO_FILE"
  592. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $3 removed from $item."
  593. fi ;;
  594. "depri" | "dp" )
  595. item=$2
  596. errmsg="usage: $TODO_SH depri ITEM#"
  597. todo=$(sed "$item!d" "$TODO_FILE")
  598. [ -z "$todo" ] && die "$item: No such todo."
  599. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  600. sed -e $item"s/^(.) //" "$TODO_FILE" > /dev/null 2>&1
  601. if [ "$?" -eq 0 ]; then
  602. #it's all good, continue
  603. sed -i.bak -e $item"s/^(.) //" "$TODO_FILE"
  604. NEWTODO=$(sed "$item!d" "$TODO_FILE")
  605. [ $TODOTXT_VERBOSE -gt 0 ] && echo -e "`echo "$item: $NEWTODO"`"
  606. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item deprioritized."
  607. cleanup
  608. else
  609. die "$errmsg"
  610. fi;;
  611. "do" )
  612. errmsg="usage: $TODO_SH do ITEM#"
  613. # shift so we get arguments to the do request
  614. shift;
  615. # Split multiple do's, if comma seperated change to whitespace sepereated
  616. # Loop the 'do' function for each item
  617. for item in `echo $* | tr ',' ' '`; do
  618. [ -z "$item" ] && die "$errmsg"
  619. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  620. todo=$(sed "$item!d" "$TODO_FILE")
  621. [ -z "$todo" ] && die "$item: No such todo."
  622. now=`date '+%Y-%m-%d'`
  623. # remove priority once item is done
  624. sed -i.bak $item"s/^(.) //" "$TODO_FILE"
  625. sed -i.bak $item"s|^|&x $now |" "$TODO_FILE"
  626. newtodo=$(sed "$item!d" "$TODO_FILE")
  627. [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
  628. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item marked as done."
  629. done
  630. if [ $TODOTXT_AUTO_ARCHIVE = 1 ]; then
  631. archive
  632. fi
  633. cleanup ;;
  634. "help" )
  635. help
  636. ;;
  637. "list" | "ls" )
  638. shift ## Was ls; new $1 is first search term
  639. _list "$TODO_FILE" "$@"
  640. cleanup
  641. ;;
  642. "listall" | "lsa" )
  643. shift ## Was lsa; new $1 is first search term
  644. cat "$TODO_FILE" "$DONE_FILE" > "$TMP_FILE"
  645. _list "$TMP_FILE" "$@"
  646. cleanup
  647. ;;
  648. "listfile" | "lf" )
  649. shift ## Was listfile, next $1 is file name
  650. FILE="$1"
  651. shift ## Was filename; next $1 is first search term
  652. _list "$FILE" "$@"
  653. cleanup
  654. ;;
  655. "listcon" | "lsc" )
  656. grep -o '[^ ]*@[^ ]\+' "$TODO_FILE" | grep '^@' | sort -u
  657. cleanup ;;
  658. "listproj" | "lsprj" )
  659. grep -o '[^ ]*+[^ ]\+' "$TODO_FILE" | grep '^+' | sort -u
  660. cleanup ;;
  661. "listpri" | "lsp" )
  662. shift ## was "listpri", new $1 is priority to list
  663. if [ "${1:-}" ]
  664. then
  665. ## A priority was specified
  666. pri=$( printf "%s\n" "$1" | tr 'a-z' 'A-Z' | grep '^[A-Z]$' ) || {
  667. die "usage: $TODO_SH listpri PRIORITY
  668. note: PRIORITY must a single letter from A to Z."
  669. }
  670. else
  671. ## No priority specified; show all priority tasks
  672. pri="[[:upper:]]"
  673. fi
  674. pri="($pri)"
  675. _list "$TODO_FILE" "$pri"
  676. ;;
  677. "move" | "mv" )
  678. # replace moved line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
  679. errmsg="usage: $TODO_SH mv ITEM# DEST [SRC]"
  680. item=$2
  681. dest="$TODO_DIR/$3"
  682. src="$TODO_DIR/$4"
  683. [ -z "$item" ] && die "$errmsg"
  684. [ -z "$4" ] && src="$TODO_FILE"
  685. [ -z "$dest" ] && die "$errmsg"
  686. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  687. if [ -f "$src" ]; then
  688. if [ -f "$dest" ]; then
  689. if sed -ne "$item p" "$src" | grep "^."; then
  690. MOVEME=$(sed "$item!d" "$src")
  691. if [ $TODOTXT_FORCE = 0 ]; then
  692. echo "Move '$MOVEME' from $src to $dest? (y/n)"
  693. read ANSWER
  694. else
  695. ANSWER="y"
  696. fi
  697. if [ "$ANSWER" = "y" ]; then
  698. if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
  699. # delete line (changes line numbers)
  700. sed -i.bak -e $item"s/^.*//" -e '/./!d' "$src"
  701. else
  702. # leave blank line behind (preserves line numbers)
  703. sed -i.bak -e $item"s/^.*//" "$src"
  704. fi
  705. echo "$MOVEME" >> "$dest"
  706. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$MOVEME' moved from '$src' to '$dest'."
  707. cleanup
  708. else
  709. echo "TODO: No tasks moved."
  710. fi
  711. else
  712. echo "$item: No such item in $src."
  713. fi
  714. else
  715. echo "TODO: Destination file $dest does not exist."
  716. fi
  717. else
  718. echo "TODO: Source file $src does not exist."
  719. fi
  720. cleanup;;
  721. "prepend" | "prep" )
  722. errmsg="usage: $TODO_SH prepend ITEM# \"TEXT TO PREPEND\""
  723. shift; item=$1; shift
  724. [ -z "$item" ] && die "$errmsg"
  725. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  726. todo=$(sed "$item!d" "$TODO_FILE")
  727. [ -z "$todo" ] && die "$item: No such todo."
  728. if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
  729. echo -n "Prepend: "
  730. read input
  731. else
  732. input=$*
  733. fi
  734. # Test for then set priority
  735. if [ `sed "$item!d" "$TODO_FILE"|grep -c "^(\\w)"` -eq 1 ]; then
  736. priority=$(sed "$item!d" "$TODO_FILE" | awk -F '\\(|\\)' '{print $2}')
  737. fi
  738. # If priority isn't set prepend
  739. if [ -z $priority ]; then
  740. if sed -i.bak $item" s|^.*|$input &|" "$TODO_FILE"; then
  741. newtodo=$(sed "$item!d" "$TODO_FILE")
  742. [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
  743. else
  744. echo "TODO: Error prepending task $item."
  745. fi
  746. # If priority is set, remove priority, prepend and add back priority
  747. else
  748. if sed -i.bak -e "$item s/^(.) //" -e "$item s|^.*|\($priority\) $1 &|" "$TODO_FILE"; then
  749. newtodo=$(sed "$item!d" "$TODO_FILE")
  750. [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
  751. else
  752. echo "TODO: Error prepending task $item."
  753. fi
  754. fi
  755. cleanup;;
  756. "pri" | "p" )
  757. item=$2
  758. newpri=$( printf "%s\n" "$3" | tr 'a-z' 'A-Z' )
  759. errmsg="usage: $TODO_SH pri ITEM# PRIORITY
  760. note: PRIORITY must be anywhere from A to Z."
  761. [ "$#" -ne 3 ] && die "$errmsg"
  762. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  763. [[ "$newpri" = @([A-Z]) ]] || die "$errmsg"
  764. sed -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE" > /dev/null 2>&1
  765. if [ "$?" -eq 0 ]; then
  766. #it's all good, continue
  767. sed -i.bak -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE"
  768. NEWTODO=$(sed "$item!d" "$TODO_FILE")
  769. [ $TODOTXT_VERBOSE -gt 0 ] && echo -e "`echo "$item: $NEWTODO"`"
  770. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item prioritized ($newpri)."
  771. cleanup
  772. else
  773. die "$errmsg"
  774. fi;;
  775. "replace" )
  776. errmsg="usage: $TODO_SH replace ITEM# \"UPDATED ITEM\""
  777. shift; item=$1; shift
  778. [ -z "$item" ] && die "$errmsg"
  779. [[ "$item" = +([0-9]) ]] || die "$errmsg"
  780. todo=$(sed "$item!d" "$TODO_FILE")
  781. [ -z "$todo" ] && die "$item: No such todo."
  782. # Test for then set priority
  783. if [ `sed "$item!d" "$TODO_FILE"|grep -c "^(\\w)"` -eq 1 ]; then
  784. priority=$(sed "$item!d" "$TODO_FILE" | awk -F '\\(|\\)' '{print $2}')
  785. fi
  786. if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
  787. echo -n "Replacement: "
  788. read input
  789. else
  790. input=$*
  791. fi
  792. # If priority isn't set replace, if it is remove priority, replace then add priority again
  793. if [ -z $priority ]; then
  794. sed -i.bak $item" s|^.*|$input|" "$TODO_FILE"
  795. else
  796. sed -i.bak -e "$item s/^(.) //" -e "$item s|^.*|\($priority\) $1|" "$TODO_FILE"
  797. fi
  798. [ $TODOTXT_VERBOSE -gt 0 ] && NEWTODO=$(head -$item "$TODO_FILE" | tail -1)
  799. [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $todo"
  800. [ $TODOTXT_VERBOSE -gt 0 ] && echo "replaced with"
  801. [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $NEWTODO"
  802. cleanup;;
  803. "report" )
  804. #archive first
  805. sed '/^x /!d' "$TODO_FILE" >> "$DONE_FILE"
  806. sed -i.bak '/^x /d' "$TODO_FILE"
  807. NUMLINES=$( sed -n '$ =' "$TODO_FILE" )
  808. if [ ${NUMLINES:-0} = "0" ]; then
  809. echo "datetime todos dones" >> "$REPORT_FILE"
  810. fi
  811. #now report
  812. TOTAL=$( sed -n '$ =' "$TODO_FILE" )
  813. TDONE=$( sed -n '$ =' "$DONE_FILE" )
  814. TECHO=$(echo $(date +%Y-%m-%d-%T); echo ' '; echo ${TOTAL:-0}; echo ' ';
  815. echo ${TDONE:-0})
  816. echo $TECHO >> "$REPORT_FILE"
  817. [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file updated."
  818. cat "$REPORT_FILE"
  819. cleanup;;
  820. * )
  821. usage
  822. ;;
  823. esac