diff --git a/README.org b/README.org index 0235712..73c038e 100644 --- a/README.org +++ b/README.org @@ -153,19 +153,20 @@ An =org-ql= query is a lisp form which may contain arbitrary lisp forms, as well The command =org-ql-search= also accepts, and the command =helm-org-ql= only accepts, an alternative, non-sexp query syntax. The syntax is simple, and a few examples of queries in both syntaxes should suffice. By default, when multiple predicates are used, they are combined with boolean =and=. -| Sexp syntax | Non-sexp syntax | -|-------------------------------------------------+----------------------------------------------| -| ~(todo)~ | ~todo:~ | -| ~(todo "SOMEDAY")~ | ~todo:SOMEDAY~ | -| ~(todo "SOMEDAY" "WAITING")~ | ~todo:SOMEDAY,WAITING~ | -| ~(ts :on today)~ | ~ts:on=today~ | -| ~(ts-active :from "2017-01-01" :to "2018-01-01")~ | ~ts-active:from=2017-01-01,to=2018-01-01~ | -| ~(clocked :on -1)~ | ~clocked:on=-1~ | -| ~(heading "quoted phrase" "word")~ | ~heading:"quoted phrase",word~ | -| ~(and (tags "book" "books") (priority "A"))~ | ~tags:book,books priority:A~ | +| Sexp syntax | Non-sexp syntax | +|---------------------------------------------------+--------------------------------------------------| +| ~(todo)~ | ~todo:~ | +| ~(todo "SOMEDAY")~ | ~todo:SOMEDAY~ | +| ~(todo "SOMEDAY" "WAITING")~ | ~todo:SOMEDAY,WAITING~ | +| ~(ts :on today)~ | ~ts:on=today~ | +| ~(ts-active :from "2017-01-01" :to "2018-01-01")~ | ~ts-active:from=2017-01-01,to=2018-01-01~ | +| ~(clocked :on -1)~ | ~clocked:on=-1~ | +| ~(heading "quoted phrase" "word")~ | ~heading:"quoted phrase",word~ | +| ~(heading-regexp "quoted phrase" "[Ww]ord")~ | ~heading-regexp:"quoted phrase",[Ww]ord~ | +| ~(and (tags "book" "books") (priority "A"))~ | ~tags:book,books priority:A~ | | ~(src :lang "elisp" :regexps ("defun"))~ | ~src:defun,lang=elisp~ or ~src:lang=elisp,defun~ | -| ~(and (tags "space") (not (regexp "moon")))~ | ~tags:space !moon~ | -| ~(priority >= B)~ | ~priority:A,B~ | +| ~(and (tags "space") (not (regexp "moon")))~ | ~tags:space !moon~ | +| ~(priority >= B)~ | ~priority:A,B~ | Note that the =priority= predicate does not support comparators in the non-sexp syntax, so multiple priorities should be passed instead, as seen in the last example. @@ -183,6 +184,7 @@ Arguments are listed next to predicate names, where applicable. + =habit= :: Return non-nil if entry is a habit. + =heading (&rest regexps)= :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings). - Aliases: =h=. ++ =heading-regexp (&rest regexps)= :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings). + =level (level-or-comparator &optional level)= :: Return non-nil if current heading's outline level matches arguments. The following forms are accepted: ~(level NUMBER)~: Matches if heading level is ~NUMBER~. ~(level NUMBER NUMBER)~: Matches if heading level is equal to or between NUMBERs. ~(level COMPARATOR NUMBER)~: Matches if heading level compares to ~NUMBER~ with ~COMPARATOR~. ~COMPARATOR~ may be ~<~, ~<=~, ~>~, or ~>=~. + =outline-path (&rest strings)= :: Return non-nil if current node's outline path matches all of ~STRINGS~. Each string may appear as a substring in any part of the node's outline path. For example, the path =Food/Fruits/Grapes= would match ~(olp "Fruit" "Grape")~. - Aliases: ~olp~. @@ -385,6 +387,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience + *Queries* - Negation of terms in plain queries using ~!~. For example, ~tags:space !moon~ to exclude entries which contain ~moon~. - Predicates =outline-path= (alias =olp=) and =outline-path-segment= (alias =olps=). + - Predicate ~heading-regexp~, which matchs heading with regexps. - Predicate ~src~, which matches Org Babel source blocks. - Alias =h= for =heading= predicate. - Alias =r= for =regexp= predicate. (Thanks to [[https://github.com/tumashu][Feng Shu]].) diff --git a/org-ql.el b/org-ql.el index 1f39d3f..7e8859c 100644 --- a/org-ql.el +++ b/org-ql.el @@ -696,6 +696,23 @@ replace the clause with a preamble." 'no-group)) element) + ;; Heading regexp. + ;; MAYBE: Adjust regexp to avoid matching in tag list. + (`(heading-regexp ,regexp) + ;; Only one regexp: match with preamble, then let predicate confirm (because + ;; the match could be in e.g. the tags rather than the heading text). + (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) + (regexp ,regexp)) + 'no-group)) + element) + (`(heading-regexp . ,regexps) + ;; Multiple regexps: use preamble to match against first + ;; regexp, then let the predicate match the rest. + (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) + (regexp ,(car regexps))) + 'no-group)) + element) + ;; Heading levels. (`(level ,comparator-or-num ,num) (let ((repeat (pcase comparator-or-num @@ -1149,6 +1166,12 @@ priority B)." (let ((heading (org-get-heading 'no-tags 'no-todo))) (--all? (string-match it heading) regexps))) +(org-ql--defpred heading-regexp (&rest regexps) + "Return non-nil if current entry's heading matches all REGEXPS (regexp strings)." + ;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments. + (let ((heading (org-get-heading 'no-tags 'no-todo))) + (--all? (string-match it heading) regexps))) + (org-ql--defpred property (property &optional value) "Return non-nil if current entry has PROPERTY (a string), and optionally VALUE (a string)." (pcase property diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index ea6ab8a..8cfbe9e 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -513,6 +513,16 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((heading "Take over" "world")) '("Take over the world")))) + (describe "(heading-regexp)" + (org-ql-it "with one argument" + (org-ql-expect ((heading-regexp "w[ol]r[lm]d")) + ;; NOTE: This--correctly--does not match the "Skype with president of + ;; Antarctica" heading, which has the tag ":world:" in its heading line. + '("Take over the world"))) + (org-ql-it "with two arguments" + (org-ql-expect ((heading-regexp "Take over" "w[ol]r[lm]d")) + '("Take over the world")))) + (describe "(outline-path)" (org-ql-it "with one argument" (org-ql-expect ((outline-path "symphony"))