Blog

Last edit

Summary: bez kalendarza

Deleted:

< month:+0


For the English part of the blog, see Content AND Presentation.

2025-10-27 Org mode burst timer

Org mode has two built-in timers – the “relative timer” (which is basically a stopwatch – it starts with 0 and counts up), useful for taking meeting minutes with an indication of when things were discussed, and a “countdown timer” (which is, well, a countdown timer – you tell it a duration, it counts down from it, and rings a bell when it gets to zero).

What I sometimes miss is the following feature. I’d like to have some kind of timer which would tell me when a given amount of time passed, but then it would keep running. For instance, let’s assume that I want to write this blog post for at least 25 minutes, but also measure the time I spend doing it even if I keep writing for longer.

Of course, this is more or less what my tomato.el did from early on (and what I partially recreated with ketchup.el). Interestingly, Org mode has a very similar feature built-in. When you set an effort estimate on a task and clock it, you’ll get notified after reaching your estimate. However, this is not exactly what I need – I’d like to have a similar feature but also for “bursts of work”. I have some long-running tasks for which the effort estimate doesn’t make sense – for example, when I read a book, I often track the time I spend on it. On the other hand, I usually want to read said book for at least N minutes per day (of course, those who read my blog regularly will guess that this is tied to Beeminder), and it’s best to spend that time in one session.

So, let’s get working on this. I’m going to write some code which will be tied to the Org mode clocking mechanism, and if a clock is started on a task which has a burst property, it will start a timer to notify me that many minutes later. (I don’t need my feature to display how much time has passed since I started the clock, since I have org-clock-mode-line-total set to current.) This could be a global minor mode, but since I do not envision turning this feature off, and it will only influence anything when a very specific property is set, I’m fine with just having it on all the time.

(defvar burst--timer nil
  "Timer for bursts of work.")

(defun burst-run-timer ()
  "Run the burst timer."
  (when-let* ((burst-string (org-entry-get (point) "burst" t))
              (burst (string-to-number burst-string)))
    (setq burst--timer
          (run-with-timer (* burst 60)
                          nil
                          #'org-notify
                          (format "%s minutes have passed"
                                  burst)
                          org-clock-sound))))

(defun burst-cancel-timer ()
  "Cancel the burst timer."
  (when (timerp burst--timer)
    (cancel-timer burst--timer)))

(add-hook 'org-clock-in-hook #'burst-run-timer)
(add-hook 'org-clock-out-hook #'burst-cancel-timer)
(add-hook 'org-clock-cancel-hook #'burst-cancel-timer)

In fact, the code is very similar to the one I wrote for my ketchup feature. The difference is in the condition used to start the timer. I am wondering if this means that an abstraction for “run a timer when clocking in, cancel it when clocking out or canceling the clock” could be useful.

Anyway, that’s it for today – although while writing this, I had an even better idea, so expect a follow-up to this post soon!

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

2025-10-20 A class register in Org Mode

My 9-year-old son loves playing school. He’s got a whiteboard, Mom, Sister and Dad are his pupils and he teaches them various real or made-up things. At the beginning he checks attendance, using a long list of made-up pupils’ names. Some time ago he decided that he’d like to keep the attendance data in a digital form. Mom helped him make a MS Word document (I know, I know…), where he puts little minuses for absentees and pluses for the attendees.

Some time later he approached me asking if I could make the computer sum up the pluses and minuses. Well sure I could! For obvious reasons I didn’t want to do that in Word. Since I may have accidentally;-) introduced him to a certain superior tool for text editing a few months ago, I suggested he used it, and decided to code this in Org mode. I imagined a table like this:

| Name       | Attendance |
|------------+------------|
| Bill Musk  | -+-        |
| Jeff Gates | +-+        |
| Elon Bezos | -++        |
|------------+------------|
| Total      | ???        |

and set out to find a way to count the pluses in the last position of these strings using the Org mode spreadsheet feature.

It turns out that it’s even much simpler than I thought. Org has a special syntax for Elisp formulae, and it correctly interpolates cell ranges as multiple arguments. The only thing that remained was to write a function accepting an arbitrary number of string arguments and counting all its arguments as well as the ones ending with the plus sign:

(defun get-recent-attendance (&rest attendance-strings)
  "Count all ATTENDANCE-STRINGS and how many end with a `+'."
  (let ((all (length attendance-strings))
        (pluses (length (seq-filter (lambda (string)
                                      (string-match "\\+$" string))
                                    attendance-strings))))
    (format "%s/%s" pluses all)))

Now the only thing I needed to do was to add this:

#+TBLFM: @5$2='(get-recent-attendance @I..@II)

As usual, Emacs and Org mode turned out to be excellent at building little pieces of code to solve little problems. Not that I’m surprised;-)!

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

2025-10-13 Grouping rows by values in a column

A few days ago I needed to analyze some tabular data where some rows where grouped in a natural way. Imagine for example a database table which stores events, and every event is related to a user. For example, it could have a user_id column, a timestamp column and an event_type column. I want to see all the events, but events related to different users should be visually spearated (as they are completely unrelated). A natural way would be to sort the data by user_id first and by timestamp next. Ideally, groups of events related to different users would be separated by e.g. an empty row.

It seems that this is possible to achieve in PostgreSQL by some clever use of the lag window function, but I thought that maybe there’s an easier way. I could export the results of my query in the csv format and apply some clever Miller code. Trouble is, I don’t know Miller’s DSL.

After poking a bit on the Internet, I discovered that there’s no need to dabble in Miller’s DSL to do that – it has a separate verb just to do exactly what I want!

Given the following csv data

user_id,timestamp,event_type
1,2025-10-07T05:31:00Z,log_in
1,2025-10-07T05:32:00Z,edit_profile
1,2025-10-07T05:33:00Z,log_out
2,2025-10-07T05:30:00Z,log_in
2,2025-10-07T05:34:00Z,log_out

and applying mlr --csv gap -g user_id, I get

user_id,timestamp,event_type
1,2025-10-07T05:31:00Z,log_in
1,2025-10-07T05:32:00Z,edit_profile
1,2025-10-07T05:33:00Z,log_out
,,
2,2025-10-07T05:30:00Z,log_in
2,2025-10-07T05:34:00Z,log_out

which is great!

One problem I had with this is that on some machines I needed to do this, I only had access to an older version of Miller (v6.0.0) which did not add the commas in the empty rows – it just produced an empty line, which it then considers a “schema change” and outputs a new header whenever that happens. The solution was to say mlr --csv gap -g user_id then unsparsify.

Now, what I needed next was to plug this into psql. This is easy enough thanks to psql’s abilities – csv formatting and piping. While the most common way to tell psql “I’m done writing the query, execute it” is to type a semicolon at the end, you can also use the \g command which can take some parameters – for example, \g (format=csv) produces the results in csv format. You can also say \g |command to pipe the results into command – and you can obviously use both these features at once, too, so that generating a csv and piping it into Miller is easy. Of course, I don’t like to have to view the raw csv file – but I don’t have to. One option is to use mlr --icsv --opprint so that Miller’s output is in a nice, tabular format. Another option I prefer is to pipe Miller’s output to pspg, which can act as an insanely cool csv viewer. (Even if you don’t use any advanced features of pspg, seeing the header all the time even when you scroll through the table is very useful!)

select user_id, timestamp, event_type from event
order by user_id, timestamp
\g (format=csv) | mlr --csv gap -g user_id | pspg --csv

That’s it for today, see you next week!

CategoryEnglish, CategoryBlog, CategoryPostgreSQL

Comments on this page

More...