Add/Change: (org-ql-select, org-ql-query) Add/rename function

Also improve docstrings.
This commit is contained in:
Adam Porter 2019-07-24 01:29:33 -05:00
parent 3b9c474193
commit 7c4297b667
3 changed files with 163 additions and 83 deletions

View file

@ -59,12 +59,12 @@ More examples are available in [[examples.org]].
The functionality provided may be grouped by: The functionality provided may be grouped by:
+ Interactive commands :: ~org-ql-search~ + Interactive commands :: ~org-ql-search~
+ Non-interactive functions and macros :: ~org-ql~ (macro), ~org-ql-query~ (function), and ~org-ql-agenda~ (macro) + Non-interactive functions and macros :: ~org-ql~ (macro), ~org-ql-select~ (function), ~org-ql-query~ (function), and ~org-ql-agenda~ (macro)
Alternatively, they may be grouped by: Alternatively, they may be grouped by:
+ Showing an agenda-like view :: ~org-ql-search~ (command), and ~org-ql-agenda~ (macro) + Showing an agenda-like view :: ~org-ql-search~ (command), and ~org-ql-agenda~ (macro)
+ Returning a list of matches or acting on them :: ~org-ql~ (macro), and ~org-ql-query~ (function) + Returning a list of matches or acting on them :: ~org-ql~ (macro), ~org-ql-select~ (function), and ~org-ql-query~ (function)
Feedback on these APIs is welcome. Eventually, after being tested and polished, they will be considered stable. Feedback on these APIs is welcome. Eventually, after being tested and polished, they will be considered stable.
@ -198,41 +198,105 @@ Here are some other examples:
*** Listing / acting-on results *** Listing / acting-on results
**** Function: ~org-ql-query~ **** Function: ~org-ql-select~
/Arguments:/ ~(buffers-or-files query &key action narrow sort)~ /Arguments:/ ~(buffers-or-files query &key action narrow sort)~
Return items matching ~QUERY~ in ~BUFFERS-OR-FILES~. Return items matching ~QUERY~ in ~BUFFERS-OR-FILES~.
~BUFFERS-OR-FILES~ is a one (or a list of) file(s) or buffer(s). ~BUFFERS-OR-FILES~ is a one or a list of files and/or buffers.
~QUERY~ is an ~org-ql~ query sexp (quoted, since this is a function). ~QUERY~ is an ~org-ql~ query sexp (quoted, since this is a function).
~ACTION~ is a function which is called on each matching entry, with point at the beginning of its heading. For example, ~org-element-headline-parser~ may be used to parse an entry into an Org element (note that it must be called with a limit argument, so a lambda must be used to do so). Also see ~org-ql--add-markers~, which may be used to add markers compatible with Org Agenda code. ~ACTION~ is a function which is called on each matching entry with point at the beginning of its heading. It may be:
If ~NARROW~ is non-nil, buffers are not widened. - ~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.
If ~NARROW~ is non-nil, buffers are not widened (the default is to widen and search the entire buffer).
~SORT~ is either nil, in which case items are not sorted; or one or a list of defined ~org-ql~ sorting methods (~date~, ~deadline~, ~scheduled~, ~todo~, or ~priority~); or a user-defined comparator function that accepts two items as arguments and returns nil or non-nil. ~SORT~ is either nil, in which case items are not sorted; or one or a list of defined ~org-ql~ sorting methods (~date~, ~deadline~, ~scheduled~, ~todo~, or ~priority~); or a user-defined comparator function that accepts two items as arguments and returns nil or non-nil.
Examples:
#+BEGIN_SRC elisp
;; Return list of to-do headings in inbox file with tags and to-do keywords:
(org-ql-select "~/org/inbox.org"
'(todo)
:action #'org-get-heading)
;; => ("TODO Practice leaping tall buildings in a single bound :personal:" ...)
;; Without tags and to-do keywords:
(org-ql-select "~/org/inbox.org"
'(todo)
:action '(org-get-heading t t))
;; => ("Practice leaping tall buildings in a single bound" ...)
;; Return WAITING heading elements in agenda files:
(org-ql-select (org-agenda-files)
'(todo "WAITING")
:action 'element)
;; => ((headline (:raw-value "Visit the moon" ...) ...) ...)
;; Since `element' is the default for ACTION, it may be omitted:
(org-ql-select (org-agenda-files)
'(todo "WAITING"))
;; => ((headline (:raw-value "Visit the moon" ...) ...) ...)
#+END_SRC
**** Function: ~org-ql-query~
/Arguments:/ ~(&key (select 'element-with-markers) from where)~
Like ~org-ql-select~, but arguments are named more like a ~SQL~ query.
~SELECT~ corresponds to the ~org-ql-select~ argument ~ACTION~.
~FROM~ corresponds to the ~org-ql-select~ argument ~BUFFERS-OR-FILES~.
~WHERE~ corresponds to the ~org-ql-select~ argument ~QUERY~.
Examples:
#+BEGIN_SRC elisp
;; Return list of to-do headings in inbox file with tags and to-do keywords:
(org-ql-query
:select #'org-get-heading
:from "~/org/inbox.org"
:where '(todo))
;; => ("TODO Practice leaping tall buildings in a single bound :personal:" ...)
;; Without tags and to-do keywords:
(org-ql-query
:select '(org-get-heading t t)
:from "~/org/inbox.org"
:where '(todo))
;; => ("Practice leaping tall buildings in a single bound" ...)
;; Return WAITING heading elements in agenda files:
(org-ql-query
:select 'element
:from (org-agenda-files)
:where '(todo "WAITING"))
;; => ((headline (:raw-value "Visit the moon" ...) ...) ...)
;; Since `element' is the default for SELECT, it may be omitted:
(org-ql-query
:from (org-agenda-files)
:where '(todo "WAITING"))
;; => ((headline (:raw-value "Visit the moon" ...) ...) ...)
#+END_SRC
**** Macro: ~org-ql~ **** Macro: ~org-ql~
/Arguments:/ ~(buffers-or-files query &key sort narrow markers action)~ /Arguments:/ ~(buffers-or-files query &key sort narrow markers action)~
Find entries in ~BUFFERS-OR-FILES~ that match ~QUERY~, and return the results of running ~ACTION-FN~ on each matching entry. Expands into a call to ~org-ql-select~ with the same arguments. For convenience, arguments should be unquoted.
Unlike the corresponding function ~org-ql-query~, arguments to this macro should not be quoted.
~BUFFERS-OR-FILES~ is a form which should evaluate to one (or a list of) file(s) or buffer(s).
~QUERY~ is an ~org-ql~ query sexp, unquoted.
~ACTION~ is a sexp which will be evaluated at each matching entry with point at the beginning of its heading. It is passed to ~org-ql-query~ as a lambda. By default, ~org-element-headline-parser~ is called to return an Org element.
~SORT~ is a user defined sorting function, or an unquoted list of one or more sorting methods, including: ~date~, ~deadline~, ~scheduled~, ~todo~, and ~priority~.
If ~NARROW~ is non-nil, query will run without widening the buffer (the default is to widen and search the entire buffer).
If ~MARKERS~ is non-nil, ~org-agenda-ng--add-markers~ is used to add markers to each item, pointing to the item in its source buffer. In this case, ~ACTION~ should return an Org element.
* Changelog * Changelog
:PROPERTIES: :PROPERTIES:
@ -243,7 +307,12 @@ If ~MARKERS~ is non-nil, ~org-agenda-ng--add-markers~ is used to add markers to
** 0.2-pre ** 0.2-pre
*Added*
+ Function ~org-ql-query~, like ~org-ql-select~ but with arguments named more like a SQL query.
*Changed* *Changed*
+ Function ~org-ql-query~ renamed to ~org-ql-select~.
+ Macro ~org-ql~ no longer accepts a ~:markers~ argument. Instead, use argument ~:action element-with-markers~. See function ~org-ql-select~, which ~org-ql~ calls.
+ ~(regexp)~ selector accepts multiple regexps to test. + ~(regexp)~ selector accepts multiple regexps to test.
+ The ~:sort~ argument to ~org-ql~, ~org-ql-query~, etc. now also accepts a comparator function by which to sort items. + The ~:sort~ argument to ~org-ql~, ~org-ql-query~, etc. now also accepts a comparator function by which to sort items.

View file

@ -207,7 +207,7 @@ SORT: One or a list of `org-ql' sorting functions, like `date' or
(when (and super-groups (not org-super-agenda-mode)) (when (and super-groups (not org-super-agenda-mode))
(user-error "`org-super-agenda-mode' must be activated to use grouping")) (user-error "`org-super-agenda-mode' must be activated to use grouping"))
(let* ((org-super-agenda-groups super-groups) (let* ((org-super-agenda-groups super-groups)
(entries (--> (org-ql-query buffers-files (entries (--> (org-ql-select buffers-files
query query
:sort sort :sort sort
:narrow narrow :narrow narrow

133
org-ql.el
View file

@ -91,48 +91,13 @@ match."
(push (list :name ',pred-name :fn ',fn-name :docstring ,docstring :args ',args) org-ql-predicates) (push (list :name ',pred-name :fn ',fn-name :docstring ,docstring :args ',args) org-ql-predicates)
(cl-defun ,fn-name ,args ,docstring ,@body)))) (cl-defun ,fn-name ,args ,docstring ,@body))))
(cl-defmacro org-ql (buffers-or-files query &key sort narrow markers (cl-defmacro org-ql (buffers-or-files query &key sort narrow action)
(action '(org-element-headline-parser (line-end-position)))) "Expands into a call to `org-ql-select' with the same arguments.
"Find entries in BUFFERS-OR-FILES that match QUERY, and return the results of running ACTION-FN on each matching entry. For convenience, arguments should be unquoted."
Unlike the corresponding function `org-ql-query', arguments to
this macro should not be quoted.
BUFFERS-OR-FILES is a form which should evaluate to one (or a
list of) file(s) or buffer(s).
QUERY is an `org-ql' query sexp, unquoted.
ACTION is a sexp which will be evaluated at each matching entry
with point at the beginning of its heading. It is passed to
`org-ql-query' as a lambda. By default, `org-element-headline-parser'
is called to return an Org element.
SORT is either nil, in which case items are not sorted; or one or
a list of predefined `org-ql' sorting methods (`date', `deadline',
`scheduled', `todo', or `priority'; or a user-defined comparator
function that accepts two items as arguments and returns nil or
non-nil.
If NARROW is non-nil, query will run without widening the
buffer (the default is to widen and search the entire buffer).
If MARKERS is non-nil, `org-ql--add-markers' is used to
add markers to each item, pointing to the item in its source
buffer. In this case, ACTION should return an Org element."
(declare (indent defun)) (declare (indent defun))
(setq action (pcase markers `(org-ql-select ,buffers-or-files
('nil `(lambda ()
,action))
(_ `(lambda ()
;; FIXME: Document that, when markers is t, `action' should return an Org
;; headline element, which --add-markers works with. On the other hand,
;; maybe this should be on the agenda-ng side.
(->> ,action
org-ql--add-markers)))))
`(org-ql-query ,buffers-or-files
',query ',query
:action ,action :action ',action
:narrow ,narrow :narrow ,narrow
:sort ',sort)) :sort ',sort))
@ -141,23 +106,31 @@ buffer. In this case, ACTION should return an Org element."
(define-hash-table-test 'org-ql-hash-test #'equal (lambda (args) (define-hash-table-test 'org-ql-hash-test #'equal (lambda (args)
(sxhash-equal (prin1-to-string args)))) (sxhash-equal (prin1-to-string args))))
(cl-defun org-ql-query (buffers-or-files query &key action narrow sort) (cl-defun org-ql-select (buffers-or-files query &key action narrow sort)
"Return items matching QUERY in BUFFERS-OR-FILES. "Return items matching QUERY in BUFFERS-OR-FILES.
BUFFERS-OR-FILES is a one (or a list of) file(s) or buffer(s). BUFFERS-OR-FILES is a one or a list of files and/or buffers.
QUERY is an `org-ql' query sexp (quoted, since this is a QUERY is an `org-ql' query sexp (quoted, since this is a
function). function).
ACTION is a function which is called on each matching entry, with ACTION is a function which is called on each matching entry with
point at the beginning of its heading. For example, point at the beginning of its heading. It may be:
`org-element-headline-parser' may be used to parse an entry into
an Org element (note that it must be called with a limit
argument, so a lambda must be used to do so). Also see
`org-ql--add-markers', which may be used to add markers
compatible with Org Agenda code.
If NARROW is non-nil, buffers are not widened. - `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.
If NARROW is non-nil, buffers are not widened (the default is to
widen and search the entire buffer).
SORT is either nil, in which case items are not sorted; or one or SORT is either nil, in which case items are not sorted; or one or
a list of defined `org-ql' sorting methods (`date', `deadline', a list of defined `org-ql' sorting methods (`date', `deadline',
@ -179,13 +152,23 @@ non-nil."
(user-error "Can't open file: %s" it))))))) (user-error "Can't open file: %s" it)))))))
((query preamble-re) (org-ql--query-preamble query)) ((query preamble-re) (org-ql--query-preamble query))
(predicate (org-ql--query-predicate query)) (predicate (org-ql--query-predicate query))
(action (cl-etypecase action (action (pcase action
(symbol (unless (functionp action) ;; NOTE: These two lambdas are backquoted to prevent "unused lexical
(user-error "Action not a function: %s" action)) ;; variable" warnings from byte-compilation, because they don't use
action) ;; all of the variables from their enclosing scope.
(function (byte-compile action)) ('element-with-markers (byte-compile
(list (byte-compile `(lambda (&rest _ignore) `(lambda (&rest _ignore)
,action))))) (org-ql--add-markers
(org-element-headline-parser (line-end-position))))))
((or 'nil 'element) (byte-compile
`(lambda (&rest _ignore)
(org-element-headline-parser (line-end-position)))))
((pred functionp) action)
((and (pred listp) (guard (functionp (car action))))
(byte-compile
`(lambda (&rest _ignore)
,action)))
(_ (user-error "Invalid action form: %s" action))))
;; TODO: Figure out how to use or reimplement the org-scanner-tags feature. ;; TODO: Figure out how to use or reimplement the org-scanner-tags feature.
;; (org-use-tag-inheritance t) ;; (org-use-tag-inheritance t)
;; (org-trust-scanner-tags t) ;; (org-trust-scanner-tags t)
@ -210,6 +193,34 @@ non-nil."
((pred functionp) (sort items sort)) ((pred functionp) (sort items sort))
(_ (user-error "SORT must be either nil, or one or a list of the defined sorting methods (see documentation)"))))) (_ (user-error "SORT must be either nil, or one or a list of the defined sorting methods (see documentation)")))))
(cl-defun org-ql-query (&key (select 'element-with-markers) from where)
"Like `org-ql-select', but arguments are named more like a SQL query.
SELECT corresponds to the `org-ql-select' argument ACTION. It is
the function called on matching headings, the results of which
are returned by this function. It may be:
- `element' or nil: Equivalent to `org-element-headline-parser'.
- `element-with-markers': Equivalent to
`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.
FROM corresponds to the `org-ql-select' argument BUFFERS-OR-FILES.
It may be one or a list of file paths and/or buffers.
WHERE corresponds to the `org-ql-select' argument QUERY. It
should be an `org-ql' query sexp."
(declare (indent defun))
(org-ql-select from where
:action select))
(defun org-ql--query-predicate (query) (defun org-ql--query-predicate (query)
"Return predicate function for QUERY." "Return predicate function for QUERY."
(byte-compile `(lambda () (byte-compile `(lambda ()
@ -460,7 +471,7 @@ Note: Clock entries are expected to be clocked out. Currently
clocked entries (i.e. with unclosed timestamp ranges) are clocked entries (i.e. with unclosed timestamp ranges) are
ignored." ignored."
;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written ;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written
;; for end users, for which the arguments are pre-processed by `org-ql-query'. ;; for end users, for which the arguments are pre-processed by `org-ql-select'.
;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled. ;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled.
(cl-macrolet ((next-timestamp () (cl-macrolet ((next-timestamp ()
`(when (re-search-forward org-clock-line-re end-pos t) `(when (re-search-forward org-clock-line-re end-pos t)
@ -626,7 +637,7 @@ FROM, TO, and ON should be strings parseable by
`parse-time-string' but may omit the time value." `parse-time-string' but may omit the time value."
;; TODO: DRY this with the clocked predicate. ;; TODO: DRY this with the clocked predicate.
;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written ;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written
;; for end users, for which the arguments are pre-processed by `org-ql-query'. ;; for end users, for which the arguments are pre-processed by `org-ql-select'.
;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled. ;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled.
(cl-macrolet ((next-timestamp () (cl-macrolet ((next-timestamp ()
`(when (re-search-forward org-element--timestamp-regexp end-pos t) `(when (re-search-forward org-element--timestamp-regexp end-pos t)
@ -668,7 +679,7 @@ FROM, TO, and ON should be strings parseable by
`parse-time-string' but may omit the time value." `parse-time-string' but may omit the time value."
;; TODO: DRY this with the clocked predicate. ;; TODO: DRY this with the clocked predicate.
;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written ;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written
;; for end users, for which the arguments are pre-processed by `org-ql-query'. ;; for end users, for which the arguments are pre-processed by `org-ql-select'.
;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled. ;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled.
(cl-macrolet ((next-timestamp () (cl-macrolet ((next-timestamp ()
`(when (re-search-forward org-element--timestamp-regexp end-pos t) `(when (re-search-forward org-element--timestamp-regexp end-pos t)
@ -711,7 +722,7 @@ FROM, TO, and ON should be strings parseable by
`parse-time-string' but may omit the time value." `parse-time-string' but may omit the time value."
;; TODO: DRY this with the clocked predicate. ;; TODO: DRY this with the clocked predicate.
;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written ;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written
;; for end users, for which the arguments are pre-processed by `org-ql-query'. ;; for end users, for which the arguments are pre-processed by `org-ql-select'.
;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled. ;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled.
(cl-macrolet ((next-timestamp () (cl-macrolet ((next-timestamp ()
`(when (re-search-forward org-element--timestamp-regexp end-pos t) `(when (re-search-forward org-element--timestamp-regexp end-pos t)