phil@bajsicki:~$
Neat trees from org-ql to org-mode
Over the past few weeks I’ve been working on a rather large inventory of items stored and held in org-mode.
For the purposes of this example, let’s use the current buffer, bajsicki.com.org
I started using org-ql to grab the data I need, in the form of:
(let ((org-ql-cache (make-hash-table)))
(org-ql-select "bajsicki.com.org"
'(tags-local "@emacs")
:action (lambda () (org-get-outline-path t))))
RESULTS:
(("Blog" "Tech" "Neat trees from org-ql to org-mode") ("Blog" "Tech" "I really, really like Emacs") ("Blog" "Tech" "A new look: ox-tufte") ("Blog" "Tech" "Moving to Hugo") ("Blog" "Tech" "Some improvements for my ox-hugo set-up"))
So, this is neat. But notice how it’s a list of lists, and there’s a lot of repetition.
Suppose you wanted to see this in a more pleasant form, maybe even one that you can interact with in org-mode directly.
To that end, I ended up writing two functions.
- Turn the list of lists we get from org-ql into a tree.
(defun phil/make-tree-from-nested-lists (lists)
(if (not lists) ;; been an issue, isn't any more
nil
(let ((grouped-lists
(seq-group-by #'car
(cl-remove-if nil lists))))
;;would occasionally return (nil nil) conses, so we filter them out
(mapcar (lambda (group)
(let ((key (car group))
(sublists (mapcar #'cdr (cdr group))))
(cons key (if (not (every #'null sublists))
(phil/make-tree-from-nested-lists sublists)
sublists))))
grouped-lists))))
- Format and output that tree into org-mode.
(defun phil/org-list-from-tree (tree &optional indent)
(let ((indent (or indent "")))
(mapconcat (lambda (item) ;; actual org-mode indentation on output? In MY Emacs?
(unless (eq item (cons nil nil))
(when (consp item)
(format "%s- %s\n%s" indent (car item)
(phil/org-list-from-tree (cdr item) (concat indent " ")))
(format "%s- %s\n" indent item))))
tree "")))
Example:
(let ((tree (phil/make-tree-from-nested-lists
(let ((org-ql-cache (make-hash-table)))
(org-ql-select (get-buffer "bajsicki.com.org") ;;the file I write my blog in
'(and (tags-local "@emacs"))
:action (lambda ()
(-snoc ;;this gets the tag list as well, on top of the heading text itself
(org-get-outline-path)
(replace-regexp-in-string
"^*+\s" "" ;; remove initial asterisks, since we're already indenting
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))))))))
(phil/org-list-from-tree tree))
RESULTS:
- Blog
- Tech
- Neat trees from org-ql to org-mode :@emacs:orgmode:elisp:
- I really, really like Emacs :@emacs:orgmode:@tech:
- A new look: ox-tufte :@emacs:orgmode:web:css:tufte:@tech:
- Moving to Hugo :hugo:web:orgmode:css:tufte:@emacs:
- Some improvements for my ox-hugo set-up :hugo:web:orgmode:@emacs:
- Tech
Pretty neat.
Now, this isn’t ideal or even close to good. Here’s known issues:
- It’s jank. I’m sure there’s a cleaner way of doing this.
- It gets tags. If you don’t want them, replace the entire
dash.el
-snoc
form with just(org-get-outline-path t)
. - Sometimes
nil
would slip in, and so I’m just removing all nils to start with. This may cause unintended issues. - This hasn’t been extensively tested, and I only tested it with the
tags-local
org-ql predicate.
So yeah. There. I may end up wrapping these in a function of some sort, but for the time being this is entirely sufficient for my purposes. We’ll see.