Add: Convert bare strings to (regexp) predicates
This commit is contained in:
parent
7c4297b667
commit
70176d9c5f
3 changed files with 60 additions and 3 deletions
|
|
@ -103,6 +103,10 @@ Here's an example of using it to generate an agenda-like view for certain files
|
||||||
|
|
||||||
A query is a lisp form which may contain arbitrary lisp forms, as well as certain built-in predicates. It is byte-compiled into a predicate function which is tested with point on each heading in an Org buffer; when it returns non-nil, the heading matches the query.
|
A query is a lisp form which may contain arbitrary lisp forms, as well as certain built-in predicates. It is byte-compiled into a predicate function which is tested with point on each heading in an Org buffer; when it returns non-nil, the heading matches the query.
|
||||||
|
|
||||||
|
*Notes:*
|
||||||
|
+ Bare strings like ~"string"~ are automatically converted to ~(regexp "string")~ predicates.
|
||||||
|
+ Standard numeric comparator function symbols (~<~, ~<=~, ~>~, ~>=~, ~=~ ) need not be quoted when passed as an argument to these predicates. The resemblance to infix notation is coincidental. See examples in documentation.
|
||||||
|
|
||||||
*** Predicates
|
*** Predicates
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:TOC: ignore
|
:TOC: ignore
|
||||||
|
|
@ -110,8 +114,6 @@ A query is a lisp form which may contain arbitrary lisp forms, as well as certai
|
||||||
|
|
||||||
Arguments are listed next to predicate names, where applicable.
|
Arguments are listed next to predicate names, where applicable.
|
||||||
|
|
||||||
Note that, for convenience, standard numeric comparator function symbols (~<~, ~=~, etc.) do not need to be quoted when passed as an argument to these predicates. The resemblance to infix notation is coincidental. See examples in documentation.
|
|
||||||
|
|
||||||
+ ~category (&optional categories)~ :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings).
|
+ ~category (&optional categories)~ :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings).
|
||||||
+ ~clocked (&key from to on)~ :: Return non-nil if current entry was clocked in given period. If no arguments are specified, return non-nil if entry was clocked at any time. If ~FROM~, return non-nil if entry was clocked on or after ~FROM~. If ~TO~, return non-nil if entry was clocked on or before ~TO~. If ~ON~, return non-nil if entry was clocked on date ~ON~. ~FROM~, ~TO~, and ~ON~ should be strings parseable by ~parse-time-string~ but may omit the time value. Note: Clock entries are expected to be clocked out. Currently clocked entries (i.e. with unclosed timestamp ranges) are ignored.
|
+ ~clocked (&key from to on)~ :: Return non-nil if current entry was clocked in given period. If no arguments are specified, return non-nil if entry was clocked at any time. If ~FROM~, return non-nil if entry was clocked on or after ~FROM~. If ~TO~, return non-nil if entry was clocked on or before ~TO~. If ~ON~, return non-nil if entry was clocked on date ~ON~. ~FROM~, ~TO~, and ~ON~ should be strings parseable by ~parse-time-string~ but may omit the time value. Note: Clock entries are expected to be clocked out. Currently clocked entries (i.e. with unclosed timestamp ranges) are ignored.
|
||||||
+ ~closed (&optional comparator target-date)~ :: Return non-nil if entry's closed date compares with ~TARGET-DATE~ using ~COMPARATOR~. ~TARGET-DATE~ should be a string parseable by ~date-to-day~. ~COMPARATOR~ should be a function (like ~<=~).
|
+ ~closed (&optional comparator target-date)~ :: Return non-nil if entry's closed date compares with ~TARGET-DATE~ using ~COMPARATOR~. ~TARGET-DATE~ should be a string parseable by ~date-to-day~. ~COMPARATOR~ should be a function (like ~<=~).
|
||||||
|
|
@ -309,6 +311,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience
|
||||||
|
|
||||||
*Added*
|
*Added*
|
||||||
+ Function ~org-ql-query~, like ~org-ql-select~ but with arguments named more like a SQL query.
|
+ Function ~org-ql-query~, like ~org-ql-select~ but with arguments named more like a SQL query.
|
||||||
|
+ Bare strings like ~"string"~ can be used in queries, which are converted to ~(regexp "string")~ automatically.
|
||||||
|
|
||||||
*Changed*
|
*Changed*
|
||||||
+ Function ~org-ql-query~ renamed to ~org-ql-select~.
|
+ Function ~org-ql-query~ renamed to ~org-ql-select~.
|
||||||
|
|
|
||||||
18
org-ql.el
18
org-ql.el
|
|
@ -150,6 +150,7 @@ non-nil."
|
||||||
;; a buffer if the filename doesn't exist.
|
;; a buffer if the filename doesn't exist.
|
||||||
(find-file-noselect it))
|
(find-file-noselect it))
|
||||||
(user-error "Can't open file: %s" it)))))))
|
(user-error "Can't open file: %s" it)))))))
|
||||||
|
(query (org-ql--pre-process-query query))
|
||||||
((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 (pcase action
|
(action (pcase action
|
||||||
|
|
@ -221,6 +222,23 @@ should be an `org-ql' query sexp."
|
||||||
(org-ql-select from where
|
(org-ql-select from where
|
||||||
:action select))
|
:action select))
|
||||||
|
|
||||||
|
(defun org-ql--pre-process-query (query)
|
||||||
|
"Return QUERY having been pre-processed.
|
||||||
|
Replaces bare strings with (regexp) selectors."
|
||||||
|
;; This is unsophisticated, but it works.
|
||||||
|
(cl-labels ((rec (element)
|
||||||
|
(pcase element
|
||||||
|
(`(or . ,clauses) `(or ,@(mapcar #'rec clauses)))
|
||||||
|
(`(and . ,clauses) `(and ,@(mapcar #'rec clauses)))
|
||||||
|
(`(when ,condition . ,clauses) `(when ,(rec condition)
|
||||||
|
,@(mapcar #'rec clauses)))
|
||||||
|
(`(unless ,condition . ,clauses) `(unless ,(rec condition)
|
||||||
|
,@(mapcar #'rec clauses)))
|
||||||
|
;; TODO: Combine (regexp) when appropriate (i.e. inside an OR, not an AND).
|
||||||
|
((pred stringp) `(regexp ,element))
|
||||||
|
(_ element))))
|
||||||
|
(rec query)))
|
||||||
|
|
||||||
(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 ()
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,12 @@
|
||||||
(correct-sexp-p (pcase (car sexp)
|
(correct-sexp-p (pcase (car sexp)
|
||||||
('org-ql 'org-ql)
|
('org-ql 'org-ql)
|
||||||
('org-ql--query-preamble 'query-preamble)
|
('org-ql--query-preamble 'query-preamble)
|
||||||
|
('org-ql--pre-process-query t)
|
||||||
(_ nil)))
|
(_ nil)))
|
||||||
(result (pcase (car sexp)
|
(result (pcase (car sexp)
|
||||||
('org-ql (org-ql-test--format-result--ql sexp))
|
('org-ql (org-ql-test--format-result--ql sexp))
|
||||||
('org-ql--query-preamble (org-ql-test--format-result--query-preamble sexp))
|
('org-ql--query-preamble (org-ql-test--format-result--query-preamble sexp))
|
||||||
|
('org-ql--pre-process-query (format "'%S" (eval sexp)))
|
||||||
(_ nil))))
|
(_ nil))))
|
||||||
(insert " :to-equal " result)
|
(insert " :to-equal " result)
|
||||||
(user-error "Point must be after an `org-ql' form")))
|
(user-error "Point must be after an `org-ql' form")))
|
||||||
|
|
@ -107,6 +109,27 @@ Based on Buttercup macro `it'."
|
||||||
(cl-loop while (re-search-forward org-heading-regexp nil t)
|
(cl-loop while (re-search-forward org-heading-regexp nil t)
|
||||||
sum 1)))))
|
sum 1)))))
|
||||||
|
|
||||||
|
(it "Query pre-processing"
|
||||||
|
(expect (org-ql--pre-process-query '(and "string1" "string2"))
|
||||||
|
:to-equal '(and (regexp "string1") (regexp "string2")))
|
||||||
|
(expect (org-ql--pre-process-query '(or "string1" "string2"))
|
||||||
|
:to-equal '(or (regexp "string1") (regexp "string2")))
|
||||||
|
(expect (org-ql--pre-process-query '(and (todo "TODO")
|
||||||
|
(or "string1" "string2")))
|
||||||
|
:to-equal '(and (todo "TODO") (or (regexp "string1") (regexp "string2"))))
|
||||||
|
(expect (org-ql--pre-process-query '(when (todo "TODO")
|
||||||
|
(or "string1" "string2")))
|
||||||
|
:to-equal '(when (todo "TODO") (or (regexp "string1") (regexp "string2"))))
|
||||||
|
(expect (org-ql--pre-process-query '(when "string-cond1"
|
||||||
|
(or "string1" "string2")))
|
||||||
|
:to-equal '(when (regexp "string-cond1") (or (regexp "string1") (regexp "string2"))))
|
||||||
|
(expect (org-ql--pre-process-query '(when (and "string-cond1" "string-cond2")
|
||||||
|
(or "string1" "string2")))
|
||||||
|
:to-equal '(when (and (regexp "string-cond1") (regexp "string-cond2")) (or (regexp "string1") (regexp "string2"))))
|
||||||
|
(expect (org-ql--pre-process-query '(unless (and "stringcondition1" "stringcond2")
|
||||||
|
(or "string1" "string2")))
|
||||||
|
:to-equal '(unless (and (regexp "stringcondition1") (regexp "stringcond2")) (or (regexp "string1") (regexp "string2")))))
|
||||||
|
|
||||||
(describe "Query compiling"
|
(describe "Query compiling"
|
||||||
;; Okay, so it's not really "compiling," but it sounds fancy. :)
|
;; Okay, so it's not really "compiling," but it sounds fancy. :)
|
||||||
|
|
||||||
|
|
@ -337,7 +360,20 @@ Based on Buttercup macro `it'."
|
||||||
(expect (org-ql test-buffer
|
(expect (org-ql test-buffer
|
||||||
(regexp "Take over" "pizza")
|
(regexp "Take over" "pizza")
|
||||||
:sort todo
|
:sort todo
|
||||||
:action (org-ql-test-org-get-heading)) :to-equal '("Take over the universe" "Take over the world" "Take over Mars" "Take over the moon" "Order a pizza" "Get haircut"))))
|
:action (org-ql-test-org-get-heading))
|
||||||
|
:to-equal '("Take over the universe" "Take over the world" "Take over Mars" "Take over the moon" "Order a pizza" "Get haircut")))
|
||||||
|
(org-ql-it "with a plain string"
|
||||||
|
(expect (org-ql test-buffer
|
||||||
|
"Take over"
|
||||||
|
:sort todo
|
||||||
|
:action (org-ql-test-org-get-heading))
|
||||||
|
:to-equal '("Take over the universe" "Take over the world" "Take over Mars" "Take over the moon" "Get haircut")))
|
||||||
|
(org-ql-it "with two plain strings"
|
||||||
|
(expect (org-ql test-buffer
|
||||||
|
(or "Take over" "pizza")
|
||||||
|
:sort todo
|
||||||
|
:action (org-ql-test-org-get-heading))
|
||||||
|
:to-equal '("Take over the universe" "Take over the world" "Take over Mars" "Take over the moon" "Order a pizza" "Get haircut"))))
|
||||||
|
|
||||||
(describe "(todo)"
|
(describe "(todo)"
|
||||||
(it "without arguments"
|
(it "without arguments"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue