I've tried (and failed) to use many different pieces of software designed to manage todo lists. The main reasons I've failed is because the software either has a high learning curve, bad documentation, or it is cumbersome to use.

I've finally struck gold with devtodo. Out of the box, it is almost perfect, but there are a few little issues:

  • It expects .todo in the current directory
  • It has no ability to track what you are working on.

I've managed to work around both of those with some extra functions in my .bash_profile.

Where is my .todo?

I wanted a git-like approach to my todo list. If there is not one in the current directory, check the parent, the grandparent, etc. Eventually, fall-back to 'Global' todo list in ${HOME}.

Note: This depends on the rfind function in my previous post.

First, create a function to set the TODO_FILE variable. We're going to set TASK_FILE here as well, this is referenced further down the page.

set_nearest_todo_file()
{   
    ntdf_file=$( rfind -name .todo -type f )
    if <span class="createlink"> -z &#34;&#36;&#123;ntdf file&#125;&#34; </span>; then
        ntdf_file="${HOME}/.todo"
    fi  
    TODO_FILE="${ntdf_file}"
}

set_nearest_task_file()
{
    TASK_FILE="${TODO_FILE}-current"
}

Now, we need to call these on cd, pushd, and popd, as well as when the shell is sourced.

cd()
{
  if builtin cd "$@"; then
    set_nearest_todo_file
    set_nearest_task_file
  fi  
}
pushd()
{
  if builtin pushd "$@"; then
    set_nearest_todo_file
    set_nearest_task_file
  fi
}
popd()
{
  if builtin popd "$@"; then
    set_nearest_todo_file
    set_nearest_task_file
  fi
}

set_nearest_todo_file
set_nearest_task_file

Next, lets make an alias for td to call devtodo with our desired todo_file

td()
{
    # Don't print DB notice for .todo in current dir
    if <span class="createlink">.todo&#34; </span>; then
        # Output DB notice to stderr.
        # so we don't mess with output parsers
        echo "Using database ${TODO_FILE}" 1>&2
    fi
    # Specify found DB
    devtodo --database ${TODO_FILE} $@
}

Now, when we call td, we will be referencing the nearest TODO_FILE, either in the current directory, one of it's parents, or falling back to ${HOME}.

What am I working on?

This was simple enough. devtodo has aliases for todo (devtodo), tda (todo --add) and tdd (todo --done). We also added td above. I'm going to hijack tdd, and add a new 't' command to set a current task.

We already added the neccessary parts to get TASK_FILE above. Now we're just going to create a function to set a task in that file.

t()
{
    if <span class="createlink"> &#34;&#36;&#42;&#34; &#61;&#61; &#34;&#34; </span> ; then
        if <span class="createlink"> -f &#34;&#36;&#123;TASK FILE&#125;&#34; </span>; then
            cat ${TASK_FILE}
        fi
    else
        td $@ | tee ${TASK_FILE}
    fi
}

You'll notice that if we call t with a parameter, such as t 6, it run td 6, instruct td to show task 6, and copy that output to your TASK_FILE.

If we simply call t, it will cat out our TASK_FILE to the screen.

I've also hijacked tdd, the devtodo 'done' alias, to clear my TASK_FILE as well.

tdd()
{
    /usr/bin/tdd $@
    rm ${TASK_FILE}
}

My workflow

I find this very easy to manage. td to review my list, t # to select my active task, and t while I'm working to be reminded what I'm doing. The task number is still in the output of t. When I'm done, tdd # marks that task as complete, removes TASK_FILE, and I'm free to start another task.