I use org-mode for many things, especially tracking all the time I work on projects. To keep tracking as easy as possible, I use a table for each project. Each table consists of the fields: day, duration and comments. For me, there is no need to track certain tasks on a project, only the duration matters. Most of the projects have a lifetime of one year. I have to sync my tracking with our time-tracking-system each month, so I need an overview over all entries of a month ordered by the projects. At the moment I work on seven projects in parallel. You can imagine that the more all the tables grow, the more the org-file becomes unhandy. I don’t want to split up the file into several others to keep things simple. So I decided to write a lisp function that creates a new buffer and lists the entries of a given month of all tables. So let’s dive into some lisp code 😉
The tables in my org file almost look like this
#+NAME: <PROJECT> | | Date | Duration | Comment | |---+------------+----------------+---------| | | 2017-02-28 | 08:00 | | |---+------------+----------------+---------| | _ | | tot<IDOFTABLE> | | | # | Sum | 08:00 | |
There is a report-table in the same file. tot<IDOFTBALE> is the reference to the sum, and I use it to build the report-table. In further iterations, I will refactor the report so that this tag won’t be necessary for the future.
I want to exclude some tables from processing, so we store the names of those in a global variable.
(defvar psp-tables-to-ignore '(template report-summary-year))
Furthermore, we want to exclude some rows from processing, especially hlines, headers and summary lines. The following helper function tests if a given row should be excluded and returns true or false.
(defun psp-ignore-row-p (row) "Should the given row be ignored?" (or (equalp 'hline row) ; ignore hlines (equalp "Date" (cadr row)) ; ignore header row (equalp "_" (car row)) ; ignore reference row (equalp "#" (car row)) ; ignore summary row (equalp (cadr row) ""))) ; ignore empty rows
Next, we create our function body.
(defun psp-calculate-month (month) "PSP calculator - shows all tracked times of a given month." (interactive "MMonth") )
The function is declared as interactive so that we can set the month as a parameter in the minibuffer. The “M” in front of “Month” means, take an arbitrary text from the minibuffers input and return it as a string.
We can use the function org-table-map-tables to iterate over all tables in an org file. org-table-map-tables takes a function and invokes that on each table of a buffer.
(org-table-map-tables <FUNCTION>)
org-table-map-tables operates on org-table structures. In my opinion, it is easier to work with Lisp lists. The function org-table-to-lisp converts the current org-table into a Lisp list structure. Keep in mind that it converts all rows including headers, hlines etc.
The function mapcar iterates over each line of a table and invokes a given function on each row. In Lisp it looks like that.
(mapcar #'<FUNCTION> (org-table-to-lisp))
Let’s build the things together, that we have so far.
(defun <FUNCTION_NAME> (<OPTIONS>) "<DESCRIPTION>" (interactive "") (org-table-map-tables (lambda () (mapcar #'<FUNCTION> (org-table-to-lisp)))))
The structure above is all you need to iterate over all the tables and their rows. We will extend this structure to a full working example in the rest of this article, so you have something more realistic to play with.
First, we create our result buffer. Emacs Lisp provides the function get-buffer-create for this. get-buffer-create takes a string with the name of a buffer and returns that buffer, or it creates a new buffer with that name if it does not exist yet. We save the buffer handle a function local variable for future usage.
(let ((RES_BUFFER (get-buffer-create "RESULT"))) <BODY>)
Each table in the result buffer should have the same name like in the origin org file. We can use org-element-property to get the name of the table.
(let ((table_name (org-element-property :name (org-element-at-point)))) <BODY>)
org-element-at-point returns the org-element at cursors current position. We need not move the cursor ourselves, org-table-map-tables already does this job for us. In the next step we test if the table_name is in our ignore list, for that we have to convert table_name into a symbol first, the lisp function intern does the job.
(if (not (member (intern table_name) psp-tables-to-ignore)) <BODY>)
To save the current tables name in our result buffer, we use the emacs lisp functions save-current-buffer and insert.
(save-current-buffer (set-buffer PSPLIST) ; open destination buffer (insert (format "/n*%s" table_name))) ; leave context and return to old buffer is handled by save-current-buffer
We use the same technique to copy relevant rows from our origin table into the destination buffer.
(if (not(psp-ignore-row-p row)) (if (equalp (subseq (cadr row) 5 7) month) (save-current-buffer (set-buffer PSPLIST) (insert (format "|%s|%s|\n" (cadr row) (nth 2 row))))))
At the end, we open the new buffer in org-mode.
(switch-to-buffer PSPLIST) (with-current-buffer PSPLIST (funcall 'org-mode) (outline-show-all))
Here you find the complete code. Hopefully, I didn’t forget to copy something 😉
(require 'org-table) (defvar psp-tables-to-ignore '(template report-summary-year)) (defun psp-ignore-row-p (row) "Should the given row be ignored?" (or (equalp 'hline row) ; ignore hlines (equalp "Date" (cadr row)) ; ignore header row (equalp "_" (car row)) ; ignore reference row (equalp "#" (car row)) ; ignore summary row (equalp (cadr row) ""))) ; ignore empty rows (defun psp-calculate-month (month) "PSP calculator - shows all tracked times of a given month" (interactive "MMonth") (let ((cur_buf (current-buffer)) (PSPLIST (get-buffer-create "PSP-LISTE"))) (org-table-map-tables (lambda () (let ((table_name (org-element-property :name (org-element-at-point)))) (if (not (member (intern table_name) psp-tables-to-ignore)) (progn (save-current-buffer (set-buffer PSPLIST) (insert (format "/n*%s" table_name))) (mapcar #'(lambda (row) (if (not(psp-ignore-row-p row)) (if (equalp (subseq (cadr row) 5 7) month) (save-current-buffer (set-buffer PSPLIST) (insert (format "|%s|%s|\n" (cadr row) (nth 2 row))))))) (org-table-to-lisp))))))) (switch-to-buffer PSPLIST) (with-current-buffer PSPLIST (funcall 'org-mode) (outline-show-all)))) (provide 'psp-calc)
Let me know if this article helped you, or what things can be improved.