We write two functions: one to count the number of DONE entries in the drawer and one to add the count as a property to the headline. Here's an example Org mode document delineating the basic assumptions:
* TODO foo
SCHEDULED: <2020-07-07 Tue +1d>
:PROPERTIES:
:LOG_INTO_DRAWER: logging
:VISIBILITY: folded
:LAST_REPEAT: [2020-07-01 Wed 16:03]
:END:
:logging:
- State "DONE" from "TODO" [2020-07-01 Wed 16:03]
- State "DONE" from "TODO" [2020-07-01 Wed 16:03]
- State "DONE" from "TODO" [2020-07-01 Wed 16:03]
- State "DONE" from "TODO" [2020-07-01 Wed 15:58]
- State "DONE" from "TODO" [2020-07-01 Wed 13:38]
- State "DONE" from "TODO" [2020-07-01 Wed 13:38]
:END:
We assume that there is a LOG_INTO_DRAWER
property naming the drawer where the logging is done (logging
in this case), and we have transitioned to DONE a number of times.
Here's the (decidedly less clunky, thanks to @erikstokes's comment) code:
(defun ndk/count-done ()
(interactive)
(save-excursion
;; we need to end up *before* the start of the drawer in order
;; to parse it correctly, so we back up one line from where org-log-beginning tells us.
(goto-char (org-log-beginning))
(forward-line -1)
(let ((contents (cadr (org-element-drawer-parser nil nil))))
(count-lines (plist-get contents :contents-begin)
(plist-get contents :contents-end)))))
(defun ndk/put-count ()
(interactive)
(let ((count (ndk/count-done)))
(org-entry-put (point) "done-count" (format "%d" count))))
We assume that point
is at the beginning of the headline. Both functions are declared interactive
so you can call them with M-x ndk/count-done
or M-x ndk/put-count
for debugging, but only the second really needs to be.
So if you place the cursor on the *
of the headline and say M-x ndk/count-done
you should get 6
as a result in the echo area. The function gets to the beginning of the log and then calls org-element-drawer-parser
to parse the drawer and extract the beginning and end of the contents of the drawer. It then passes those two to count-lines
and returns the result.
Doing M-x ndk/put-count
at the same point calls the previous function to count the number of DONE
lines and then inserts the count as a property done-count
in the property drawer.
Resetting the count after every N times that the task is done is just a matter of calculating a remainder when dividing the count by N. Here's an implementation where N is obtained as the value of the reset-count
property (but if there is no such property, we take N=10):
(defun ndk/count (count)
(let* ((reset-count-prop (org-entry-get (point) "reset-count"))
(reset-count (or (and reset-count-prop (string-to-number reset-count-prop))
10)))
(% count reset-count)))
(defun ndk/put-count ()
(interactive)
(let ((count (ndk/count-done)))
(org-entry-put (point) "done-count" (format "%d" (ndk/count count)))))