Library-provided `org-agenda-skip-function` builder?
The org-ql is great for filtering the agenda, but as far as I can see it does not provide "out of the box" method to build filters suitable to be used in org-agenda-skip-function.
So I suggest to add one:
(defmacro org-ql-agenda-skip-if (query)
(cl-labels (
;; recurses into list form and adds prefix
;; "org-ql--predicate-" for all functions except of "and", "or" and "not"
(replace-predicate-names (form)
(if (listp form)
(let ((head (car form))
(tail (cdr form)))
(cons
(cl-case head
;; don't replace boolean expressions
((and or not)
head)
;; for other symbols add "org-ql--predicate-" prefix
(t (intern (concat "org-ql--predicate-" (symbol-name head)))))
;; also replace for all args
(mapcar #'replace-predicate-names tail))
)
form)))
`(lambda ()
(save-excursion
;; org-ql predicates work best on the start of heading
(outline-back-to-heading)
(flet ((next-headline () (save-excursion
(or (outline-next-heading)
(point-max)))))
;; if condition matched, go to next headline
(when ,(replace-predicate-names query)
(next-headline)))))))
This macro takes form, and adds "org-ql--predicate-" prefix to any symbol in function position except of "and", "or" and "not".
The usage is pretty straighforward:
(let ((org-agenda-custom-commands
'(
("t" "test"
agenda ""
(
(org-agenda-files '("/home/elk/test-for-org-ql.org"))
;; it can be just used inplace to build filter
(org-agenda-skip-function
(org-ql-agenda-skip-if (and (tags "noise")
(not (tags "important"))))))))))
(org-agenda nil "t")
)
test file:
* heading one :test:noise:
<2023-10-03 Tue>
*** heading two :shmest:
<2023-10-03 Tue>
*** heading three :shmest:extra:important:
<2023-10-03 Tue>
*** heading four :shmest:
<2023-10-03 Tue>
"E.L.K." @.***> writes:
The
org-qlis great for filtering the agenda, but as far as I can see it does not provide "out of the box" method to build filters suitable to be used inorg-agenda-skip-function.So I suggest to add one: ...
Another approach that also utilizes cache is https://github.com/yantar92/emacs-config/blob/master/config.org#archiving
The
org-qlis great for filtering the agenda,
org-ql is more intended as an alternative to org-agenda, i.e. org-ql-search provides a similar view with similar features (the buffer is actually in org-agenda-mode) and better performance.
but as far as I can see it does not provide "out of the box" method to build filters suitable to be used in
org-agenda-skip-function.
That's because it's designed to replace org-agenda's backend, which is very slow by comparison. The notion of a "skip function" is obsoleted by org-ql's query functionality.
Nevertheless, org-agenda provides some features which aren't implemented in org-ql, so we provide some ways to integrate with it, like org-ql-block.
So I'm not opposed to offering a feature like this, for circumstances in which org-agenda is still necessary.
An important caveat to note is that using org-ql's predicates in an agenda skip function will skip the optimizations that org-ql performs when running a query, so most of the performance benefits will be discarded. So it would generally be better to use org-ql-search directly.
yantar92's code example is an interesting way to use a cache to avoid re-running the same skip function more than necessary; I'd probably decline to add that to org-ql as it seems confusing to me, but it's a neat hack.
I'd also recommend writing this feature with a function rather than a macro, as generally there's no need for it to be one.
Finally, the function that rewrites the query's symbols should take the org-ql-predicates variable into account rather than doing it on all unrecognized symbols. (org-ql used to work that way, early in its development, but having org-ql-predicate-- prefixing all of the query symbols was unpleasant to look at. Now and then, however, I consider whether it should work that way internally, rewriting the symbols before executing the query rather than using fset around the running of each query.)
@alphapapa
Please take a look:
It's now a function, not a macro (now it requires lexical bindings). Also it now converts by referring org-ql-predicates.
(defun org-ql-agenda-skip-if (query)
(cl-labels (
;; recurses into list form and replaces all predicates which are
;; registered in org-ql-predicates
(replace-predicate-names (form)
(if (listp form)
(let* ((fn (car form))
(as-predicate (alist-get fn org-ql-predicates))
(rest (cdr form)))
(cons
(if as-predicate
(plist-get as-predicate :fn)
fn)
;; also replace for all args
(mapcar #'replace-predicate-names rest))
)
form))
(next-headline () (save-excursion
(or (outline-next-heading)
(point-max)))))
(let ((updated-query (replace-predicate-names query)))
(lambda ()
(save-excursion
;; org-ql predicates work best on the start of heading
(outline-back-to-heading)
;; if condition matched, go to next headline
(when (eval updated-query)
(next-headline)))))))
@alphapapa
An important caveat to note is that using org-ql's predicates in an agenda skip function will skip the optimizations that org-ql performs when running a query, so most of the performance benefits will be discarded
For my case, using provided function speeds up generating (actually _re_generating, first generating still rather slow) big weekly agenda 5-7 times, so maybe some optimizations are lost, but still this is much faster than what I had before. And also org-ql provides much more predicates.