Karl Hans Janke Kollaborativ
Heute die Welt, morgen das Sonnensystem!

jul 2010

Keeping a personal journal on plain old Unix

The other day, somebody presented this privacy nightmare on Hacker News. Nevertheless, I thought the idea was cute. Encourage people to write a journal entry every day, combined with a random peek at one of their old entries. Then make it really easy for them to drop the new one.

Here is a pair of shell scripts towards the same end. Entries are kept in ~/journal, one plain text file for each year. Each entry is automatically marked with the date and time of its submission. The format is trivial, give it a try or read the code.

To add a new entry, call the journal script. I alias this to the shortcut j.

#!/bin/sh
# keeping a personal journal, pesco 2010
[ -z "$EDITOR" ] && EDITOR=vi
[ -d "$HOME/journal" ] || mkdir "$HOME/journal"

t=`mktemp -t journal`
$EDITOR $t
if cmp -s $t /dev/null; then
	echo "empty file, nothing to do"
else
	f="$HOME/journal/`date +%Y`"
	( date
	  cat $t | sed -e 's/^/> /'
	  echo
	) >> $f
	echo "got it. thanks for saving this!"
fi

It drops you into an editor on an empty file. Just type anything on your mind, save, quit, done. And you even get a nice thank you for it.

To see a random entry, call the past script. This one is a little longer because of the whole business of getting random numbers, selecting a file, and picking one specific entry out of it. Yet it uses only standard Unix tools. Have a look:

#!/bin/sh
# retrieving a random journal entry from the past, pesco 2010

rand() {
	od -N 2 -t u2 /dev/urandom | sed -e 's/^[^ ]*//'
}

# select a journal file
r=`rand`
n=`ls $HOME/journal | wc -l`               # number of files
i=`expr $r % $n + 1`                       # random index, 1-based
f=`ls $HOME/journal | sed -n -e "${i}p"`   # that file basename
f="$HOME/journal/$f"                       # absolute path

# select a journal entry
r=`rand`
n=`grep '^ *$' $f | wc -l`                 # number of entries
i=`expr $r % $n`                           # random index, 0-based

# print journal entry
(
	# skip until the i'th empty line
	j=0
	while [ $j -lt $i ]; do
		read l
		if [ -z "$l" ]; then
			j=`expr $j + 1`
		fi
	done

	# echo until the next empty line
	read l
	while [ -n "$l" ]; do
		echo "$l"
		read l
	done
) < $f

According to the HISTORY sections of my corresponding man pages, this should run on Version 1 AT&T Unix and later! Note the use of expr for arithmetic operations. You can run this on your mainframe! :)

I swear, somewhere in the multiverse there is an alternate timeline forking off in the 70s mainframe era and it's totally awesome.

Have fun!

PS. Right, this doesn't remind you to submit your daily report. I actually don't like the part about friendly reminder emails piling up in my inbox that much.

Command line flags for Haskell scripts, Really Cheap & Simple (TM)

How often do you find yourself in the following situation?

Having written a nice little program, you just want to add one little command line option. Typical example: -dprint debugging output. So all you want to say (in code) is something like this:

If we were given the '-d' flag on the command line,
... [print/enable debug output]

But what you actually have to do is usually this:

Seem a bit much? Not that there is anything wrong with the above in principle. These libraries are very useful for writing compilers and faithful reproductions of historic UNIX utilities. But for your everyday script? I think it's annoying.

So, speaking of returns to simplicity. These are for pasting into your next Haskell script:

import System.Environment

-- pesco's really cheap and simple flags and options (tm)
clparts = getArgs >>= return . (\(a,b) -> (a,drop 1 b)) . break (=="--")
getargs = clparts >>= \(a,b)-> return ([h:t| h:t<-a, h/='-' || null t] ++ b)
getflags = clparts >>= \(a,_)-> return (concat [t| '-':t <- a])
getflag x = getflags >>= return . elem x
getenv f v x = catch (getEnv v >>= return . f) (\_ -> return x)

Here, have some type signatures, too:

getargs  :: IO [String]
getflags :: IO [Char]
getflag  :: Char -> IO Bool
getenv   :: (String -> a) -> String -> a -> IO a

Usage of the functions is pretty apparent from the types. Don't look anything up. Please!

From five lines you get:

You buy simplicity at the expense of comprehensiveness. What you don't get:

PS. I did make an elaborate command line parsing library a few years ago. Suitable for building big compilers and stuff! You might still find it in the caches of the intarweb if you're interested. Reading the original abstract, the goal was similar then: Make it really easy to get command line options into programs. Alas, using that library still suffered from problems outlined at the top of this post: Find it, look through the docs, add a big bunch of code to your project, use an elaborate API. I think it was a very good replacement for the standard GetOpt; considerably nicer, with many fancy features. But probably overkill for everyday use in scripts or small programs.

A command-line organizer for getting things done

In case anyone hasn't noticed: I have a thing for simple solutions.

So here is the third iteration of my personal to-do tracker. Previous versions held each task in a separate file, possibly along with a verbose description and several metadata fields.

This one is a return to simplicity. As a special note, I am particularly proud of defaulting to the venerable .plan file to hold the task list. I've always wished I had a proper use for it!

From the README:

basic operation

  • keep a list of tasks in ~/.plan, one per line. example:
     do something
     do something else
    
  • annotate them with due dates as appropriate
    12.7.2010! do something
    do something else
    
  • arrange roughly in order of priority
  • group tasks by context, i.e. what could be done together
    do laundry
    scrub floor
    
    visit parents
    do something on way to parents house
    do something at parents' house
    
  • mark current tasks / next actions
    > laundromat
    scrub floor
    > read chapter on poly types
    
  • stall tasks until a certain date
    2010-08-20> bday present for dad
    2010-08-25> bday present for sis
    
  • use hashtags to name projects
    code up first prototype of new #watnu
    blog about #watnu on #khjk
    
  • watnu will warn when a project has no tasks active or scheduled.
  • use generic tasks to keep projects on plan:
    keep blogging on #khjk
    keep coding: #watnu #bitlbeeotr #noooo
    #home #friends #school
    
  • multiple date formats allowed. examples (all 1st of may):
    2010-05-01! iso date
    1.5.2010!   german
    5/1/2010!   us
    
    1.5.! german w/o year
    5/1!  us w/o year
    
  • delegate tasks to other people, schedule activation as reminder
    10.7.> [mom] do laundry
    [timmy] scrub floor
    
  • call watnu to get your current todo list. order and grouping will be preserved from input.
  • set PLAN environment variable to use a different input file
  • set PLAN="-" to read from stdin

The concepts are my take on ideas from David Allen's GTD. These are some data points on the design:

The program is just short of 180 lines of Haskell code. See the README file for build instructions.

NB. I have found it a surprisingly nice routine to print a fresh todo list on a PocketMod each morning, so I'm throwing in a dirty shell script as a free bonus.

Get it here: http://code.khjk.org/watnu/