justdoit.sh - justdoit - Simpler (but with not all the features) reimplementation todo.txt CLI
 (HTM) hg clone https://bitbucket.org/iamleot/justdoit
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
       justdoit.sh
       ---
            1 #!/bin/sh
            2 
            3 #
            4 # Copyright (c) 2018 Leonardo Taccari
            5 # All rights reserved.
            6 # 
            7 # Redistribution and use in source and binary forms, with or without
            8 # modification, are permitted provided that the following conditions
            9 # are met:
           10 # 
           11 # 1. Redistributions of source code must retain the above copyright
           12 #    notice, this list of conditions and the following disclaimer.
           13 # 2. Redistributions in binary form must reproduce the above copyright
           14 #    notice, this list of conditions and the following disclaimer in the
           15 #    documentation and/or other materials provided with the distribution.
           16 # 
           17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
           18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
           19 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
           20 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
           21 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
           22 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
           23 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
           24 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
           25 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
           26 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
           27 # POSSIBILITY OF SUCH DAMAGE.
           28 #
           29 
           30 config_file=${JUSTDOIT_CONFIG_FILE:=${HOME}/.config/justdoit/t.conf}
           31 
           32 if [ -f "${config_file}" ]; then
           33         . "${config_file}"
           34 fi
           35 
           36 todo_file=${JUSTDOIT_TODO_FILE:=${HOME}/.config/justdoit/todo.txt}
           37 done_file=${JUSTDOIT_DONE_FILE:=${HOME}/.config/justdoit/done.txt}
           38 
           39 
           40 #
           41 # Print usage message and exit
           42 #
           43 usage()
           44 {
           45 
           46         echo "usage: t add|done|due|edit|help|list|listall"
           47 
           48         exit 1
           49 }
           50 
           51 
           52 #
           53 # Print help message and exit
           54 #
           55 help()
           56 {
           57 
           58         echo "usage: justdoit add|done|edit|help|list|listall"
           59         echo
           60         echo "a|add entry"
           61         echo "Add the entry \`entry' at the end of the todo.txt file."
           62         echo
           63         echo "d|do|done item"
           64         echo "Mark an item \`item' of todo.txt file as done, moving it in done.txt"
           65         echo "and removing it from todo.txt"
           66         echo
           67         echo "due days [pattern]"
           68         echo "List all task due next number of days - and all the expired"
           69         echo "ones - that matches pattern (or all of them if no pattern is"
           70         echo "provided)"
           71         echo
           72         echo "e|ed|edit"
           73         echo "Open the todo.txt with the \${EDITOR}"
           74         echo
           75         echo "h|help"
           76         echo "Show an help message"
           77         echo
           78         echo "l|ls|list [pattern]"
           79         echo "List all todo.txt entries that matches pattern (or all of them"
           80         echo "if no pattern is provided)"
           81         echo
           82         echo "la|lsa|listall [pattern]"
           83         echo "List all done.txt and todo.txt entries that matches pattern (or"
           84         echo "all of them if no pattern is provided)"
           85         echo
           86 
           87         exit 1
           88 }
           89 
           90 
           91 #
           92 # Add an entry at the end of a todo.txt file
           93 #
           94 add()
           95 {
           96         file=$1
           97         entry=$2
           98 
           99         echo "${entry}" >> "${file}"
          100 }
          101 
          102 
          103 #
          104 # Mark an entry item as done, adding it to the done.txt file and removing it
          105 # from the todo.txt file.
          106 #
          107 mark_done()
          108 {
          109         tfile=$1
          110         dfile=$2
          111         item=$(($3))
          112 
          113         if [ ${item} -ge 1 ] && [ ${item} -le $(wc -l < "${tfile}") ]; then
          114                 {
          115                         printf "x %s " "$(date +%Y-%m-%d)";
          116                         sed -n "${item}p" "${tfile}";
          117                 } >> "${dfile}"
          118                 sed -i.bak "${item}d" "${tfile}"
          119         fi
          120 }
          121 
          122 
          123 #
          124 # List, show the contents of a todo.txt file
          125 #
          126 list()
          127 {
          128         file=$1
          129         search=$2
          130 
          131         if [ "${search}" ]; then
          132                 filter() grep -F -- "${search}"
          133         else
          134                 filter() cat
          135         fi
          136 
          137         cat "${file}" | prettify | filter
          138 }
          139 
          140 
          141 #
          142 # Prettify the output of a todo.txt
          143 #
          144 prettify()
          145 {
          146 
          147         awk '
          148         BEGIN {
          149                 FS = " "
          150         }
          151 
          152         function bold(s) {
          153                 return sprintf("\033[1m%s\033[0m", s);
          154         }
          155 
          156         function underline(s) {
          157                 return sprintf("\033[4m%s\033[0m", s);
          158         }
          159 
          160         #
          161         # Parse the entry and populate all optional and non-optional fields
          162         #
          163         # Every line of todo.txt file should have the following format:
          164         #
          165         #  +------------+------------+-----------------+---------------+-------------+
          166         #  | completion |  priority  | completion date | creation date | description |
          167         #  | (optional) | (optional) |   (optional)    |  (optional)   |             |
          168         #  +------------+------------+-----------------+---------------+-------------+
          169         #
          170         # where:
          171         #  - completion: just a lower-case "x" to mark that a task is done
          172         #  - priority: a upper-case letter under parethenses (e.g. "(A)")
          173         #  - completion date: date of completion of the task in the %Y-%m-%d
          174         #                     format (e.g. "2018-08-18")
          175         #  - creation date: date of creation of the task in the %Y-%m-%d
          176         #                   format (e.g. "2018-08-18")
          177         #  - description: text of the task; can contain multiple +project_tag,
          178         #                 @context_tag, special key/value tag (key:value)
          179         #
          180         {
          181                 completion = ""
          182                 priority = ""
          183                 completion_date = ""
          184                 creation_date = ""
          185                 description = ""
          186 
          187                 i = 1
          188 
          189                 if ($i == "x") {
          190                         completion = "x"
          191                         i++
          192                 }
          193                 if (match($i, /\([A-Z]\)/)) {
          194                         priority = $i
          195                         i++
          196                 }
          197                 if (completion && match($i, /[0-9]+-[0-9]+-[0-9]+/)) {
          198                         completion_date = $i
          199                         i++
          200                 }
          201                 if (match($i, /[0-9]+-[0-9]+-[0-9]+/)) {
          202                         creation_date = $i
          203                         i++
          204                 }
          205 
          206                 # Remove any non-description fields and possible leading spaces
          207                 for (j = 1; j < i; j++) {
          208                         $j = ""
          209                 }
          210                 sub(/^ +/, "")
          211                 description = $0
          212         }
          213 
          214         {
          215                 # bold all the tags
          216                 gsub(/@[^ ]+/, "\033[1m&\033[0m", description)
          217 
          218                 if (completion) {
          219                         printf("%12s %s\n", underline(completion), description)
          220                 } else {
          221                         n++
          222                         printf("%12s %s\n", underline(n), description)
          223                 }
          224         }
          225         '
          226 }
          227 
          228 
          229 #
          230 # Filter all due: key-values task of the next n. days and all "expired" ones.
          231 #
          232 due()
          233 {
          234         days="$1"
          235 
          236         awk -v days="${days}" '
          237         function date_to_seconds(date) {
          238                 cmd = "date -d " date " +%s"
          239                 cmd | getline
          240                 close(cmd)
          241                 return $0
          242         }
          243 
          244         function seconds_to_date(seconds) {
          245                 cmd = "date -r " seconds " +%Y%m%d"
          246                 cmd | getline
          247                 close(cmd)
          248                 return $0
          249         }
          250 
          251         BEGIN {
          252                 n_days = int(days)
          253                 n_seconds = date_to_seconds(strftime("%Y%m%d")) + \
          254                     (n_days * 60 * 60 * 24)
          255         }
          256 
          257         match($0, /due:[0-9]+-[0-9]+-[0-9]+/) {
          258                 # XXX: We mess up with getline in date_to_seconds()
          259                 # XXX: so backup the current line.
          260                 s = $0
          261 
          262                 # Delete "due:" and convert the date in %Y%m%d format
          263                 due_date = substr(s, RSTART + 4, RLENGTH - 4)
          264                 gsub(/\-/, "", due_date)
          265 
          266                 if (n_seconds >= date_to_seconds(due_date)) {
          267                         print s
          268                 }
          269         }
          270         '
          271 }
          272 
          273 
          274 #
          275 # justdoit, a todo.sh clone in POSIX shell script
          276 #
          277 main()
          278 {
          279         subcommand=$1
          280 
          281         case $subcommand in
          282         a|add)
          283                 entry=$2
          284                 add "${todo_file}" "${entry}"
          285                 ;;
          286         d|do|done)
          287                 item=$2
          288                 mark_done "${todo_file}" "${done_file}" "${item}"
          289                 ;;
          290         due)
          291                 days=$2
          292                 search=$3
          293                 list "${todo_file}" "${search}" | due "${days}"
          294                 ;;
          295         e|ed|edit)
          296                 ${EDITOR} "${todo_file}"
          297                 ;;
          298         h|help)
          299                 help
          300                 ;;
          301         l|ls|list)
          302                 search=$2
          303                 list "${todo_file}" "${search}"
          304                 ;;
          305         la|lsa|listall)
          306                 search=$2
          307                 list "${done_file}" "${search}"
          308                 list "${todo_file}" "${search}"
          309                 ;;
          310         *)
          311                 usage
          312                 ;;
          313         esac
          314 
          315         exit 0
          316 }
          317 
          318 main "$@"