org-ql
org-ql copied to clipboard
Passing arguments to :action
It would be nice to be able to pass arguments to the function given as :action
in org-ql-select
. Right now I'm using a lambda with a closure but I think it would cleaner and more convenient if you could do something like ('myfunc arg1 arg2...)
.
Please see the org-ql-select
docstring:
ACTION is a function which is called on each matching entry with
point at the beginning of its heading. It may be:
- `element' or nil: Equivalent to `org-element-headline-parser'.
- `element-with-markers': Equivalent to calling
`org-element-headline-parser', with markers added using
`org-ql--add-markers'. Suitable for formatting with
`org-ql-agenda--format-element', allowing insertion into an Org
Agenda-like buffer.
- A sexp, which will be byte-compiled into a lambda function.
- A function symbol.
I'm not sure any of these work for what I want to do. My function takes an argument which is a plist that it modifies in-place. I'm not super experienced with Elisp but I don't think byte-compiling a sexp would work here?
If the argument to your function is static, you can either pass a sexp containing the argument, or pass a lambda which contains it. Of course, in that case, it's not so much an argument as a piece of data embedded in the function.
If the argument to your function is not static, I don't understand what you're trying to do. Where would the argument come from?
You should be able to do anything you need by either passing a lambda or mapping across the results returned by org-ql-select
. I don't think it's necessary to add a way to pass arguments to the action function. But if you want to show me exactly what you're trying to do, maybe there's something I'm missing.
I'm trying to modify org-ql-select
slightly so that it collects not just matching headlines but also their ancestors. To avoid parsing common ancestors multiple times I put all headlines in a flat list and store their indices in a hash table by buffer and position. The action callback parses the current headline if it hasn't already, adds it to the list and hash, recurses to the parent, and returns the index of the current headline. But to do that it needs access to some common state across calls.
Here's the basic version of my code with some made up function names:
(defun -select-with-parents-action (data)
"Parse current headline and add to list+hash if not already a member. Return index in list."
(let ((headlines-list (plist-get data :headlines-list))
(headlines-hash (plist-get data :headlines-hash))
(key (the-current-buffer-and-position))
(hashed-index (gethash key headlines-hash)))
(or hashed-index
(let ((headline (org-element-headline-parser))
(parent-index
(save-excursion-and-go-up-a-headline
(-select-with-parents-action data)))
(this-index (length headlines-list))
(org-element-set-property headline :parent-index parent-index)
(puthash key this-index headlines-hash)
(append-to-list headlines-list headline)
this-index))))
(defun select-with-parents (files query)
"Get headlines matching query and their parents/ancestors."
(let ((data (list :headlines-list nil :headlines-hash (make-hash-table))))
(action (lambda () (-select-with-parents-action data)))
(match-indices (org-ql-select files query :action action)))
(cons (plist-get data :headlines-list) match-indices)))
Now that I think about it, this is probably an edge case. It's likely that most users would only use actions whose arguments are constant for the given query.
That's very cool! You may be interested in this WIP branch, which implements a parent selector: https://github.com/alphapapa/org-ql/tree/wip/parent-selector It hasn't been optimized, so it's not very fast, but it works.
I think it needs a bit of refactoring to support a kind of "predicate query", which would return nil or non-nil rather than a list of matching headings, acting like cl-some
, that way it wouldn't continue searching after finding a match. If it also had some caching like you've implemented, that could probably make it fast.
Also, see this WIP recursive query function, which returns a parent heading and child headings which match a "sub query": https://github.com/alphapapa/org-ql/blob/master/notes.org#b-recursive-queries You might be able to implement your code in terms of these "recursive queries", although I don't know how performance would compare. I posted an example of using that code here: https://www.reddit.com/r/orgmode/comments/cwuh8k/providing_more_context_of_subtasks_for_tasks_in/eyg6jow/
There are a lot of interesting possibilities to explore here. :) Thanks for your input.
FWIW, I think this use case can be solved easily enough by using lexical binding and closures. That is, let
-bind the list and hash table around the call to org-ql-select
, with the action function being a closure around the list and table variables. For example, see the implementation in org-ql-completing-read
: https://github.com/alphapapa/org-ql/blob/4c1a4b169f54d37ce541902c0ae5043759ef9d9b/org-ql-completing-read.el#L107