Using devtodo with multiple projects

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 [[ -z "${ntdf_file}" ]]; 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 [[ "${TODO_FILE}" != "./.todo" ]]; 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 [[ "$*" == "" ]] ; then
        if [[ -f "${TASK_FILE}" ]]; 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.