Skip to content

Instantly share code, notes, and snippets.

@weakish
Last active April 16, 2016 14:21
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save weakish/827296 to your computer and use it in GitHub Desktop.
every #todo is a file #shell #sh
#!/bin/sh
# Author: Jakukyo Friel <weakish@gmail,com>
# License: GPL v2
# ftm -- an 'every todo is a file' todo manager
# =========================================================
# Description
# -------------------------------------------------------
#
# Ftm is a simple todo manager using a 'one todo item per file' approach.
#
# Every todo item is a file.
# Ftm stores todo items in a directory.
# Ftm uses the file name to record the todo item.
# If you want to add some notes, you simply write them down in the file.
# You can access your todo items via ftm's interface, or directly
# using commands like
# `mv`, `touch`, `rm`, etc.
# Installation
# -------------------------------------------------------
#
# Ftm is written in one single shell script.
# You may simply put it in your `PATH`, for example, `~/bin`.
# Upgrade from old version
# ----------------------------
# If you use a todo directory other than the default one,
# you need to run `ftm --init yourdir` again after installing
# the new version.
# Versions 0.0.x uses ~/.config/ftm as the default directory.
# Ftm defaults to ~/.ftm now.
# Thus if you upgrade from 0.0.x and uses the default directory,
# you need to run `ftm --init ~/.config/ftm` (if you want to
# continue to use it) or `mv ~/.config/ftm ~/.ftm`.
# Quickstart
# -------------------------------------------------------
# Run `ftm init`.
# Use `ftm -a` to add a todo, do it, and `ftm -d`
# to mark it done.
# Use `ftm -l` to list todos.
# Usage
# -----------------
help() {
cat <<ENDHELP
ftm -- an 'every todo is a file' todo manager
list of options:
-a, --add add a new todo
-c, --cat [todo] show todo note
-e, --edit [todo] edit todos
-d, --done [todo] mark todos as completed
-h, --help print this help page
--init [dir] init todo directory
-l, --list [word] list todos
-n, --next show next action
-p, --postpone postpone next action
-s, --search ERE search your todos
-V, --version show version
ENDHELP
}
# Similar projects
# -------------------------------------------------------
#
# Here are some other command line todo managers:
#
# - [todo.txt](http://ginatrapani.github.com/todo.txt-cli/):
# written in Bash, featureful.
# - [TaskWarrior](http://taskwarrior.org)
# written in C, featureful. The superest CLI todo manager I've encountered.
# - [t](http://stevelosh.com/projects/t/)
# written in Python, for people that want to finish tasks, not organize them.
# - [todo](https://github.com/juanibiapina/todo)
# minimalist per directory TODO list manager written in Bash.
# Releases
# -------------------------------------------------------
readonly semver='0.1.0' # released on 2011-04
# Next action is the earliest created/modified action.
# You can postpone next action now.
# Default $ftm_repo to ~/.ftm.
# `ftm -c [todo]` will echo todo name.
# BUGFIX: `ftm -c` and `ftm -e` will not open all todos.
# readonly semver='0.0.1' # released on 2011-02
# BUGFIX: Run `ftm` without options will prompot you to type `ftm -h` now.
# BUGFIX: Fix wrong help info.
# readonly semver='0.0.0' # released on 2011-02
# Usage
# -------------------------------------------------------
#
# First use `ftm --init dir` to specify the directory to store your todos.
# If you omit the `dir` parameter, ftm will use `~/.ftm`.
# Note ftm will refuse to work if you use interesting characters
# such as space and dot in directory name.
readonly ftm_repo=$HOME/.ftm
init_ftm() {
readonly dir=$1
# Set todo repo path by *modifying source*.
# So we don't need an additional config file.
set_repo_bang() {
readonly var_pattern="^(readonly $1=).*"
readonly new_var_pattern=$2
sed -i -r -e "s:$var_pattern:\1$new_var_pattern:" $0
}
[ -n "$dir" ] && set_repo_bang 'ftm_repo' $dir
# We cannot simply use $ftm_repo here.
# Remember set_repo_bang changed the source, *not* the variable.
mkdir -p ${dir:-$ftm_repo}/.done
}
# Add todos:
#
# ftm -a I've some thing to do
#
# Ftm will convert whitespace and punctions to '-'.
# For example, the previous todo will be stored as `I-ve-some-thing-to-do`.
add_todo() {
# convert special characters
nice_filename() {
readonly raw_text="$*"
echo $raw_text | tr [:punct:] '-' | tr ' \t' '-'
}
readonly todo="$*"
touch $ftm_repo/$(nice_filename $todo)
}
# You can check next action with: `ftm -n`.
# Reversely sorted by modification time.
# It mainly meant for scripting usage.
# I find myself almost always use `ftm -c` instead.
next_action() {
ls -t $ftm_repo | tail -1
}
# You can postpone next action, i.e. put it to the tail of the queue.
postpone_action() {
touch $ftm_repo/$(next_action)
}
# For every todo, you can add notes to it, look at its notes,
# and mark it as done.
#
# ftm --<action> [todo]
#
# Available actions:
#
# * `-e|--edit`
# * `-c|--cat`
# * `-d|--done`
#
# You specify todo item by their name, but you can be lazy by
# only typing the beginnig part of the name as long as it will produce
# a *unique* match.
# (If not unique, the behavior is undefined.
# In fact, `-c` and `-e` may support edit/view multiple todo items,
# but `-d` will certainly fail.)
# If you do not provide a todo name, ftm will operate on the next action.
act_on_todo() {
readonly todo_action=$1
readonly todo=$2
[ $todo ] && $todo_action $ftm_repo/$todo* || $todo_action $ftm_repo/$(next_action)
}
# For example, add/edit notes todo with your prefered editor.
# (The one your `editor` points to.)
#
# ftm -e I-ve-some-thing-to-do
#
edit_todo() {
readonly to_edit_todo=$1
act_on_todo 'editor' $to_edit_todo
}
# Look at the note in a lazy way:
#
# ftm -c I-ve
# It simply prints the note to standard output.
# I've considered using `less` when it detects the note is very long.
# But I dropped this approach for simplicity.
# After all, I gusee todo notes tend to be short.
# And if you do have a long note, you can always pipe the standard
# output to a pager, or scroll back.
cat_todo() {
show_todo() {
readonly to_show_todo=$1
echo $(basename $to_show_todo)
echo # print a new line
cat $to_show_todo
}
readonly to_cat_todo=$1
act_on_todo show_todo $to_cat_todo
}
# When you mark something done, ftm moves it into a subdirectory `.done`,
# with the time you marked it done appenden to its name.
# For example:
#
# my-todo -> .done/my-todo/2000-01-01T13-01-01TZ
#
# You may use other software to generating reports from them.
finish_todo() {
readonly to_mark_done_todo=$1
mark_done() {
readonly done_todo=$1
mv $done_todo $ftm_repo/.done/$(basename $done_todo)_$(date -u +%Y-%m-%dT%H-%M-%STZ)
}
act_on_todo mark_done $to_mark_done_todo
}
# `ftm -l` will list all todos.
# `ftm -l word` will list any todo containing the word.
list_todos() {
readonly todo_word=$1
# `$ftm_repo/*$1*` will print full path. Thus we change directory.
cd $ftm_repo
ls *$todo_word*
cd $OLDPWD
}
# Ftm also supports full text search:
#
# `ftm -s regexp`
#
# We uses ERE. Ftm will not search todos marked as done.
search_todos() {
readonly ERE_pattern="$*"
grep --colour -C3 -EHne $ERE_pattern $ftm_repo/*
}
main() {
case $1 in
-a|--add) shift 1 && add_todo "$@";;
-c|--cat) cat_todo $2;;
-e|--edit) edit_todo $2;;
-d|--done) finish_todo $2;;
-h|--help) help;;
--init) init_ftm $2;;
-l|--list) list_todos $2;;
-n|--next) next_action;;
-p|--postpone) postpone_action;;
-s|--search) shift 1 && search_todos "$@";;
-V|--version) echo ftm version $semver;;
*) echo 'Type ftm -h for help.'; exit 1;;
esac
}
main "$@"
# Examples
# ------------------------------
#
# Ftm tries hard to keep things simple.
# But due to its 'every todo is a file' design,
# you can use it in an 'advanced' way.
#
# Use number
# prefix for priority.
# For example:
#
# - 0-TAKE-OVER-THE-WORLD
# - 1-fly-to-the-moon
# - 2-sleep
# - 21-eat
# - 3-who-cares
#
# And so on.
# With 10 numbers, you can build a very sophisticated priority system.
# And uses the filter `ftm -l`:
#
# Check what's urgent?
#
# ftm -l 0
#
# Check all todos with a priority higher than 3:
#
# ftm -l [0-3]
#
# Besides priority, it's also possible to build a context system, for example:
#
# - 0-TAKE-OVER-THE-WORLD-HOME
# - 1-fly-to-the-moon-ERRAND
#
# I will go out:
#
# ftm -l ERRAND
#
# I'm at home, what's the next action at home:
#
# ftm -l HOME | head -1
#
# I'm in office, and check the most important tasks today:
#
# ftm -l OFFICE | grep [0-3]
#
# There are many other possibilities, be creative!
# But always remember, focus on doing, rather than organizing todos
# or writing todo managers. ;-)
# BUGS
# ----
#
# - If you mark two identical notes in the same day, the latter will
# overwrite the former silently.
# Since this is unlikely to happen, I've no plan to fix it yet.
#
#
# Bugs in earlier version:
#
# <0.1.0:
# - Keep todos in ~/.config/ftm is against XDG.
# Should use $XDG_DATA_HOME/ftm instead.
# (Reported by astolia)
#
# - Neither `ftm -c` or `ftm -e` works as expected.
# It should show/edit the note of next action, but it actually
# opens notes of all todos.
#
# 0.0.0:
# - Help says typing `ftm` without options will change directory
# to $ftm_repo.
# Since it's in a subshell, this changing is nonsense.
# - Help gives wrong information on option 'add'. The parametre
# is not optional.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment