99 lines
3.3 KiB
Markdown
99 lines
3.3 KiB
Markdown
+++
|
|
title = "Neat trees from org-ql to org-mode"
|
|
lastmod = 2025-04-21T14:19:29+02:00
|
|
tags = ["orgmode", "elisp"]
|
|
categories = ["tech", "emacs"]
|
|
draft = false
|
|
meta = true
|
|
type = "list"
|
|
[menu]
|
|
[menu.posts]
|
|
weight = 3001
|
|
identifier = "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:
|
|
|
|
```elisp
|
|
(let ((org-ql-cache (make-hash-table)))
|
|
(org-ql-select "bajsicki.com.org"
|
|
'(tags-local "@emacs")
|
|
:action (lambda () (org-get-outline-path t))))
|
|
```
|
|
|
|
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.
|
|
|
|
1. Turn the list of lists we get from org-ql into a tree.
|
|
|
|
<!--listend-->
|
|
|
|
```elisp
|
|
(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))))
|
|
```
|
|
|
|
1. Format and output that tree into org-mode.
|
|
|
|
<!--listend-->
|
|
|
|
```elisp
|
|
(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:
|
|
|
|
```elisp
|
|
(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))
|
|
```
|
|
|
|
Pretty neat.
|
|
|
|
Now, this isn't ideal or even close to good. Here's known issues:
|
|
|
|
1. It's jank. I'm sure there's a cleaner way of doing this.
|
|
2. It gets tags. If you don't want them, replace the entire `dash.el` `-snoc` form with just `(org-get-outline-path t)`.
|
|
3. Sometimes `nil` would slip in, and so I'm just removing all nils to start with. This may cause unintended issues.
|
|
4. 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.
|