"Porting" of kapow to Plan 9

I have mentioned that applications duplicate operating system's functionality. How severe issue is it and what kind of consequences there are?

Kapow punch clock tracks hours and constructs reports. Its author has done well in maintaining complexity. The whole program is only 4171 lines (+594 lines of copyright headers), with total 146kB of source code. This post presents a program for Plan 9 that serves the same purpose. Finally lets do some comparison.

kapow screenshot

The source code presented is attached: task.zip

task entries

The clock has this kind of a nice directory -tab. I have seen this tool being used. The tabs were used to track customer_project_day instead of projects. The dialog looks like a file browser.

The 'task' program treats each task as a file. For instance here's the '0.kapow.task'.

Punch clock port experiment
start: Sat Nov 21 11:00:06 GMT 2020

This is a running task. When I'm done, I append in the stop: -line.

The time stamps aren't using Finnish abbreviations for weekdays or months and it is bit inconvenient for me. Though they are slightly more convenient than unix timestamps because I can type the hour there if I forget to mark it.

The system benefits from not needing a locale to parse dates. If you don't like english dates, you can use ISO-8601 timestamps, those should work as well and look like this:


task/start and task/stop

To start a task you write a timestamp into a file and mark it to start the task. Although it'd be easy to do it manually, I add few convenience scripts so you don't lose an entry just because you write > instead of >>. Here's task/start and task/stop. They are used to mark a task completed or start a task.

rfork e
for (taskname in $*) { echo start: `{date} >> $taskname }

rfork e
for (taskname in $*) { echo stop: `{date} >> $taskname }

You can consider these are up to you to configure, they're for your convenience.


Next you need a tool to know task status. Here's the 'task/span':

if (~ $#* 1) {
  if (test -f $1) {
    rfork e
    start=`{sed -n 's/^start: (.*)$/\1/p' <$taskname | xargs seconds}
    stop=`{sed -n 's/^stop: (.*)$/\1/p' <$taskname | xargs seconds}
    if (~ $stop '') stop=`{date | xargs seconds}

    dt=`{echo print $stop(1) - $start(1) | bc}
    s=`{echo print $dt % 60 | bc}
    m=`{echo print $dt / 60 % 60 | bc}
    h=`{echo print $dt / 3600 | bc}
    echo span: $h hours $m minutes $s seconds
  if not { echo not a file: $1>[1=2]; exit usage }
if not { echo usage: $0 taskfile >[1=2]; exit usage }

It presents how many hours, minutes, seconds have been spent in the task. If the task is still running, it uses the current time as range.


The 'task/report' sorts the tasks and builds a report out of them.

rfork e
tasks=`{for (t in $*) {
  p=`{sed -n 's/^start: (.*)$/\1/p' $t | xargs seconds}
  echo $p $t } | sort -n | awk '{ print $2 }'}

for(taskname in $tasks) {
  span=`{task/span $taskname | awk '{ print $2,$3,$4,$5 }'}
  start=`{sed -n 's/^start: (.*)$/(\1)/p' $taskname}
  stop=`{sed -n 's/^stop: (.*)$/(\1)/p' $taskname}
  if (~ $stop '') { echo $span $start '*' }
  if not { echo $span $start }
  sed '/^start:|^stop:/d' $taskname | sed 's/^/  /g'

for(taskname in $tasks) {
  d=`{task/span $taskname | awk '{ print $2,$4 }'}
  total=`{echo print $total + ($d(1) '*' 60) + $d(2) | bc}
echo total: `{echo print $total / 60 | bc} hours `{echo print $total % 60 | bc} minutes

When a task is ongoing, this utility shows a star on it's entry. This script could be customized for different uses. Right now it's a plaintext report.


Some people like to have a display that tracks what is going on. The 'task/track' -script executes a command periodically every minute and the window where it runs is cleaned every time it repeats.

task/report `{rc -c $"*} >/dev/text
while (sleep 60) task/report `{rc -c $"*} > /dev/text

It again is merely a convenience. All of this functionality composes with rest of what the system has to offer. For instance if you want to see all tasks in some directory:

while (sleep 1) {
    echo -n >/dev/text
    for (x in *.task) {
        echo -n $x: ' '
        task/span $x }}

Also here's a cool thing, if you want completed task list to appear when you log into your terminal, you can add this into the rio start script:

window -r 2338 15 2853 345 task/track 'echo *.task'

Ok, we're done now. Here's how the finished program looks like:

task screenshot


The whole program suite is 48 lines and 1.5kB.

cpu% wc bin/rc/task/*
 21     119     703 bin/rc/task/report
 18     100     578 bin/rc/task/span
  3      11      73 bin/rc/task/start
  3      11      72 bin/rc/task/stop
  3      15     100 bin/rc/task/track
 48     256    1526 total

This is about 85 times less than kapow punch clock.

apples and oranges?

It may seem the comparison is unfair. Are the programs equivalent in functionality? The answer is no. The 'task' implements much less functionality than the kapow punch clock. It's barely considered bunch of scripts. However the tasks are merely files and you edit them with existing tools, this thing introduces very little to learn. The manual is 1kB and I think I made it verbose. Yet despite that:

I do wonder what the take-away from here is. Despite this all, kapow punch clock, it's fine software. I don't mind that it's been written.


I did this to try off this 'rc' and try to write a manual page. I'm surprised how usable the 'troff' is once you don't assume it should be something familiar. The 'rc' is equally incredible by having an actual structure that you can rely on. Plan 9 is such an incredibly good operating system, I constantly find things that surprise in a good way.