Blog

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

2023-01-23 TODO stats table with parameters

Last time I wrote about my TODO stats table I promised to make it more configurable. And here I am.

Let’s start with deciding what parameters I want. Looking at the section about clock tables in the Org mode manual, I can see three parameters I could use here, too:

  • :maxlevel
  • :scope
  • :match

Here is how you can implement them. Probably the easiest one is :maxlevel – you can call (org-reduced-level (org-current-level)) on a headline to get its level. (Here, “reduced” means “taking into account the fact that someone might use org-odd-levels-only.) Note that org-current-level returns nil when the point is before the first headline, so I’ll take that into account, too.

It is also not difficult to implement :scope – it is, after all, one of the parameters of the org-map-entries function. Beware, though – the possible scopes for e.g. the clock table and that function are slightly different! The first difference is that clock table’s :scope allows a function which would return the list of files. The second, more important difference is that in the clock table’s :scope, subtree means “current subtree”, treeN means “the surrounding level N tree” and tree means tree1. In the case of org-map-entries​’ scope parameter, tree means “the current subtree” and there are no subtree nor treeN options. So, in order to define a clock-table-like :scope parameter, we need to pass it to org-map-entries unless it is one of subtree, tree, treeN, or a function. In order to do that, we can copy some code from the org-dblock-write:clocktable function.

The situation is the easiest with :match – you can simply pass it to org-map-entries, and that’s it. The only improvement I can envision here is that I could create another, Boolean parameter to turn on property inheritance just for the one stats table being constructed. This could be achieved by adding the (special) variable org-use-property-inheritance to the let clause surrounding the call to org-map-entries. I leave it as an exercise for the reader;-).

So, let’s put all this knowledge into practice (and code). A bit surprisingly, I couldn’t find an Elisp function to go up the Org file structure until a certain level, so I wrote one. The code should be pretty self-explanatory. And now I have a way to see the status of all my TODO items in a neat table. Even better, this is now a part of the dynamic block system, so e.g. C-u C-c C-x C-u automatically recomputes all such tables along the clock tables. (Now that I learned how to code a custom dynamic block, I am thinking about other possibilities like this…)

(defun org-up-to-level (&optional level)
  "Go up to a headline at LEVEL (default 1)."
  (interactive "p")
  (setq level (or level 1))
  (while (> (org-reduced-level (or (org-current-level) 0))
            level)
    (org-up-heading-safe)))

(defun org-subtree-todo-stats (maxlevel match scope)
  "Get stats about todo keywords.
 MAXLEVEL, MATCH and SCOPE are like in clock table."
  (save-excursion
    (let ((stats (mapcar (lambda (keyword) (cons keyword 0))
                         org-todo-keywords-1))
          (ome-scope (cond
                      ((eq scope 'subtree) 'tree)
                      ((eq scope 'tree) (progn (org-up-to-level) 'tree))
                      ((string-match "\\`tree\\([0-9]+\\)\\'"
                                     (symbol-name scope))
                       (org-up-to-level
                        (string-to-number
                         (match-string 1
                                       (symbol-name scope))))
                       'tree)
                      ((functionp scope) (funcall scope))
                      (t scope))))
      (org-map-entries
       (lambda ()
         (when (or (null maxlevel)
                   (<= (org-reduced-level (org-current-level))
                       maxlevel))
           (let ((keyword (org-element-property
                           :todo-keyword
                           (org-element-at-point))))
             (when keyword
               (cl-incf (alist-get keyword stats nil nil #'string=))))))
       match
       ome-scope 'archive 'comment)
      stats)))

(defun org-dblock-write:todotable (params)
  "Write a todotable."
  (insert "| Keyword | Count |\n")
  (insert "|-\n")
  (mapc (lambda (keyword-count)
          (insert (format
                   "| %s | %s |\n"
                   (car keyword-count)
                   (cdr keyword-count))))
        (org-subtree-todo-stats (plist-get params :maxlevel)
                                (plist-get params :match)
                                (plist-get params :scope)))
  (org-table-align))

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

2023-01-14 My plans for 2023

It is the first time I write a post about my plans for the upcoming year. This is because I keep changing my productivity habits, and the change I’ve made recently was introducing some planning. This includes doing the famous “weekly reviews”, together with quarterly and yearly ones. For now, I’m very bad at it, but I keep going in the hope of getting better. And it means that I want to plan my activities (in various areas of life) for the next several months.

Of course, the easiest one of them is my day job – no need to do much planning here, I just keep doing what I’m assigned and that’s more or less it. In other words, planning is not my job here. The tricky part is my side projects (also, of course, more personal stuff like what is going to happen with my family – but this is very personal and not subject of this blog post!).

Last year was a kind of disappointment for me, really. While 2021 was great – I finally finished not one, but two big projects, the Elisp book and another thing I still haven’t written about (but I’ll get to it eventually!) – there was no such achievements in 2022. That’s ok, it’s not a disaster, but not what I was hoping for, either. So I want to change it in 2023.

So, here is my side-project-plan for 2023. It’s going to be a writing year! First of all, I want to finish my booklet about accounting. It’s nothing big, but I think it’s pretty nice and it bothers me that I didn’t finish it (even though the core, that is the explanation of the main tenets of accounting and how the fundamental types of accounts work, is done). So, this is one thing I plan to finish, preferably even in January.

Another thing I’m going to spend considerable time on is polishing and expanding the Elisp book. I have some ideas here (including places I really want to improve), and I’d love to put them out this year. It’s going to take some time – this is not going to be my top priority – but something I want to spend some regular time every week, so that it gets done this year (although I don’t know when exactly – probably the second half of it).

There is also a secret (well, semi-secret, since I did allude to it several times) project I started in late 2020. It is a really big one (and still ongoing!) – I’ve already spent more than 340 hours on it, with at least another 150 or so ahead of me (though after 2+ years of pretty intensive work for a side project this long I decided to slow down considerably). This will be blog-worthy, and it’s different enough from what I write about here that it’s possible I’ll start a brand new blog about it somewhere in 2023.

And finally the most exciting of my non-secret plans for 2023 is this: two new books! I’ve been thinking about a certain experiment, and I finally decided that the time for it is very soon now. In January, I’m going to start learning something new in order to write a textbook about it! Much like the book on Elisp, it is going to be a book about using a computer and some programming skills to make your life easier. Since the only software other than Emacs I spend considerable time using is (obviously) a web browser, I’m going to learn to write browser extensions and share my newly found knowledge in a book.

This – as I said – is one big experiment. It may turn out that the existing resources are good enough that there is no need for a book by a humble mathematician turned programmer. But another part of the experiment is that I’m going to learn in public. The plan is to document my learning process in a booklet – a kind of programmer’s diary about what it’s like to learn something new.

Is it going to be interesting? I don’t know. Is it going to be long? I don’t know. Is it going to be fun to read? I hope so. Is it going to be worth anything to anyone? I really don’t know. Is it going to be even finished? Well… yes.

I plan to start learning in January and commit to document the process of going through the (substantial part or all of) the resources I decide to use. Honestly, I consider it also a way for people who find my writing worth anything (and come on, I know you’re out there, even if it’s only two of you;-)!) to support me financially, by buying the booklet I’m going to write this way.

And when I’m done (probably in late spring or early summer), I will decide if it makes sense to convert my newly found knowledge into a regular textbook. And I’m really excited by this. I consider many, many websites and web apps to have inferior UI and capabilities, and I hope to learn that browser extensions can help with that. It’s exactly the same idea with Emacs: you learn Elisp to mold Emacs into something more useful to you, only now it’s about JavaScript and a web browser.

So, that’s about it as far as my (public) plans for 2023 go. There are also backup plans. I have three other book ideas (on various levels of vagueness, but one of them is pretty concrete). I also plan to do some little programming side projects. But the core is what I told above.

Does that sound interesting? Does it make sense? Do you have any ideas about how to improve my plans for 2023? Or do you find them completely ridiculous and a waste of time? Let me know, and thanks for reading this far!

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2023-01-09 TODO stats table

Last week I showed how to compute a summary of all TODO keywords in an Org mode subtree. Today we’ll find out how to insert those data in an easy and readable way in the buffer.

What I want to achieve is something akin to clock tables. I want to be able to insert a block like this:

#+BEGIN: todotable
#+END:

and press C-c C-c, and get the stats in an Org table. (In the future, I might add support for parameters like :scope. For now, let’s keep it simple.)

The first idea I had was to add an entry to org-ctrl-c-ctrl-c-hook or org-ctrl-c-ctrl-c-final-hook (which see). During experimenting, however, I found something else. It turns out that the org-ctrl-c-ctrl-c command is basically a giant cond clause deciding what to do, and in the case of a dynamic block it calls the aptly named org-update-dblock function. This one in turn has this:

(let* ((cmd (intern (concat "org-dblock-write:" name))))
  (funcall cmd params))

(of course this is just an excerpt). This means that I just need to define an org-dblock-write:todotable function, accepting a single parameter – the plist of parameters given by the user in the #+BEGIN line (combined with some other stuff, like the previous content of the block). What’s even better, the only thing this function needs to do is to insert the necessary stuff – the previous content is emptied by the org-prepare-dblock function. (By the way, this is explained in the docstring of the org-update-dblock function, but somewhat unexpected when reading the code alone – who would guess that this part

(let* ((params (org-prepare-dblock))
       ;; ...
       )
  ;; ...
  )

would actually perform the emptying?)

So here is the first attempt at inserting the todotable block (ignoring any parameters and inserting just the data for the current subtree). I poked around in some Org mode source files and it seems there are no functions to insert a table row and realign the table – you just insert stuff like | and |-+-| manually. On the other hand, this is admittedly a good thing, since you may just insert what you need in an unaligned fashion and call org-table-align once at the end, thus not wasting cycles.

So, without further ado, here is my function.

(defun org-dblock-write:todotable (params)
  "Write a todotable."
  (insert "| Keyword | Count |\n")
  (insert "|-\n")
  (mapc (lambda (keyword-count)
          (insert (format
                   "| %s | %s |\n"
                   (car keyword-count)
                   (cdr keyword-count))))
        (org-subtree-todo-stats))
  (org-table-align))

In the future we’ll add some parameter parsing to this. Until then!

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

More...