0

What I'm trying to do:

  1. Load a named org-mode file in the background
  2. Find a named heading in that file
  3. Locate the first list under that heading
  4. Get the names (as they appear in Emacs when I write them) of all items from that list as a ELisp list
  5. Return one of those randomly.

Basically, I'm trying to get a random list entry from a named heading in a named file.

My input file:

* Heading1

- Item1
- Item2
- Item3

* Heading2

From this, I want to be able to call a function with the name of the above file, a heading name ("Heading1") and get the following back:

("Item1" "Item2" "Item3")

Then, I'd easily be able to select one of those randomly (but I haven't gotten that far yet).

How I'm trying to do it

I have some ELisp experience, but not much. So as an experiment, I tried to get ChatGPT 4 to help me write this. I asked it to do it in chunks, which I have then put together.

I'm parsing the input file into an AST.

Anyway, here's the code I have so far:

(defun urpg-load-ast (filename)
  "Load an org-file in the background and return its AST."
  (with-temp-buffer
    (insert-file-contents filename)
    (org-element-parse-buffer)))

(defun urpg-find-first-list-under-heading (ast target-heading)
  "Find the first list under a named heading in an AST and return it."
  (let (found-heading)
    (org-element-map ast 'headline
      (lambda (headline)
        (when (and (not found-heading)
                   (string= (org-element-property :raw-value headline)
                            target-heading))
          (setq found-heading t)
          (org-element-map headline 'plain-list
            (lambda (plain-list)
              plain-list)))))))

(setq ast (urpg-load-ast "~/test-file.org"))
(setq items (urpg-find-first-list-under-heading ast "Heading1"))
items

The output I get from running this

(((plain-list (:type unordered :begin 13 :end 37 :contents-begin 13 :contents-end 37 :structure ... ...) (item ... ...) (item ... ...) (item ... ...))))

The problem I'm facing

This gets as far as returning some sort of AST entry for the list I'm looking for in the input file. But my problem is I have no idea how to handle that AST entry - as you can see above, it has a bunch of data (all of which is not displayed), and I'm not sure how to extract the data I want - the names of the items. As I said above, I basically want this:

("Item1" "Item2" "Item3")
6
  • 1
    emacs.stackexchange.com/tags/elisp/info
    – Drew
    Mar 22, 2023 at 16:50
  • 1
    One,specific question per post, please. Thx.
    – Drew
    Mar 22, 2023 at 16:50
  • 1
    Are you assuming there are no nested lists? If not, what do you want to happen with nested lists?
    – NickD
    Mar 22, 2023 at 17:54
  • 1
    There are no nested lists. I will be writing the input files, so I can be certain of that. For those familiar with roleplaying games - the lists are random tables; lists of random encounters, etc.
    – Enfors
    Mar 22, 2023 at 18:48
  • @Drew, I've edited out there extra question, sorry about that.
    – Enfors
    Mar 22, 2023 at 18:51

1 Answer 1

2

To understand how the AST works, you can read the initial comment in org-element.el (use M-x find-library). To learn about the org-element-api you can read its documentation and the docstring of the API functions.

Finally, if you read all that, then you can slowly try to understand the following code that achieves what you ask for:

(defun org-get-heading-random-list-elements (heading-title)
  (let* ((heading (car (org-element-map (org-element-parse-buffer)
                           'headline
                         (lambda (h)
                           (when (string= (org-element-property :raw-value h) heading-title)
                             h)))))
         (heading-contents (org-element-contents heading))
         (first-plain-list (caar (org-element-map heading-contents
                                     'section
                                   (lambda (s)
                                     (org-element-map s 'plain-list #'identity)))))
         (item-contents (org-element-map (org-element-contents first-plain-list)
                            'item
                          #'org-element-contents))
         (paragraph-contents (mapcar (lambda (ic)
                                       (org-element-map
                                           ic
                                           'paragraph
                                         #'org-element-contents))
                                     item-contents))
         (list-items (mapcar #'caar paragraph-contents)))
    (string-trim-right (substring-no-properties (nth (random (length list-items)) list-items)))))

You can use it on your input example file(-buffer) with M-: (org-get-heading-random-list-elements "Heading1")

1
  • 1
    This does indeed work, thank you very much!
    – Enfors
    Mar 22, 2023 at 21:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.