jul 2010
30.7.2010 0:51, pesco
tags: code shell
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.
25.7.2010, pesco
tags: code haskell getflag cmdline watnu
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:
-d —
print 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:
- Get a hold of your favourite
getopt
library.
If it's not a standard library,
make sure to note that dependency somewhere!
- Find the documentation and look up basic usage.
- Specify to the library exactly which options you want to accept,
whether they take any arguments,
and provide a usage note for the autogenerated help text.
- Make a loop in your main routine for
parsing all options
.
- Finally, in that loop, state that -d enables debugging output.
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:
- Boolean flags: Anywhere in your (IO) code, ask whether a certain flag
has been specified:
d <- getflag 'd'
when d $ putStrLn "debug on!"
Alternatively, get the list of flags once and pass it around.
flags <- getflags
when all (`elem` flags) "nlp" $ putStrLn "my favourite options!"
- Condensed flags: Throw any number of flag characters behind a single
dash
-
:
$ myps -aux
- Options with defaults: This might feel strange, but seriously:
Use environment variables.
To query, complete with reader/parser and default value:
n <- getenv read "n" 10 -- think "tail -n"
c <- getenv read "C" 0 -- think "grep -C"
-- default comes in from another IO action
today <- getenv parsedate "today" =<< getcurdate
-- cascading defaults
plan <- getenv id "PLAN"
=<< getenv (++ "/.plan") "HOME"
=<< return "-"
BTW, don't think that setting an env var is more work than a command
line argument! Compare:
$ today=2010-07-26 watnu
vs.
$ watnu --today=2010-07-26
The so-called keyword parameter
syntax of the former has been
supported since the original Bourne Shell.
- Retrieve any non-flag arguments via the getargs routine.
- Arguments that begin with a dash:
Anything after an argument of
--
is not a flag.
A single dash -
is also not a flag.
You buy simplicity at the expense of comprehensiveness.
What you don't get:
- Long flags (e.g.
--debug
).
You have 52 latin letters (upper- and lower-case) available.
Plus any digits and special characters you can sensibly make use of.
When you need more, use a real command line option parser.
You're writing a goddamn compiler or something.
- Complaints about unrecognized flags or options.
Defining the following function is left as the proverbial
exercise to the reader:
allowflags :: [Char] -> IO ()
- Command line options with arguments.
Use environment variables.
- Autogenerated help and usage messages.
Type them yourself,
they're going to be so much prettier.
Come on, add some ASCII art.
When it really is too bothersome,
feel free to define the following function:
helpscreen :: String -- textual command description
-> [(Char, String)] -- recognized flags with desc.
-> [(String, String)] -- recognized env vars with desc.
-> String
- Elaborate error messages.
Obviously there's nothing like
command line option --foo requires Frob argument, but got Twizzle
in those five lines.
Then again, nothing is to stop you from putting arbitrarily complicated
error checking in the reader you pass to getenv or
validate the order and number of things in the result of getflags,
etc..
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.
6.7.2010 23:36, pesco
tags: haskell code watnu gtd
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:
- Contexts never really worked out for me.
Instead of ordering tasks by priority,
GTD advocates grouping them by what can be done together.
While I agree with that,
trying a priori to name a good set of contexts
was annoyingly cumbersome.
- It was hard to keep an overview when tasks were scattered around their
separate files. This made the weekly review unpleasant.
- Long task descriptions are superfluous.
Sometimes there would be a note,
e.g. opening hours of a store,
but the vast majority of tasks are one-liners.
Tack-on notes can be added as a separate feature later.
- Only some things have hard due dates.
In general, it is more important when I plan to start something
rather than until when I must have completed it.
- Tasks of the
someday/maybe
category need not be tracked.
Keep them in a different file.
- There are always tasks that are definately
to do
but should not appear
on my radar, yet.
They might be scheduled for later or simply buried by
more pressing matters.
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/