Add/Change: (heading-regexp), (heading) predicates

Closes #64.  Closes #68.

Thanks to Feng Shu (@tumashu) for suggestions.
This commit is contained in:
Adam Porter 2020-12-09 21:28:55 -06:00
parent 31efd5a26d
commit 2dfd378d5f
4 changed files with 95 additions and 45 deletions

View file

@ -207,8 +207,10 @@ Arguments are listed next to predicate names, where applicable.
+ =category (&optional categories)= :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings).
+ =done= :: Return non-nil if entry's ~TODO~ keyword is in ~org-done-keywords~.
+ =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).
+ =heading (&rest strings)= :: Return non-nil if current entry's heading matches all ~STRINGS~. Matching is done case-insensitively.
- Aliases: =h=.
+ ~heading-regexp (&rest regexps)~ :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings). Matching is done case-insensitively.
- Aliases: ~h*~.
+ =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 ~>=~.
+ =link (&optional description-or-target &key description target regexp-p)= :: Return non-nil if current heading contains a link matching arguments. ~DESCRIPTION-OR-TARGET~ is matched against the link's description and target. Alternatively, one or both of ~DESCRIPTION~ and ~TARGET~ may be matched separately. Without arguments, return non-nil if any link is found.
+ =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")~.
@ -518,9 +520,11 @@ Simple links may also be written manually in either sexp or non-sexp form, like:
*Added*
+ Macro =org-ql-defpred=, used to define search predicates. (See [[file:examples/defpred.org][tutorial]].)
+ Predicate ~heading-regexp~, which matches regular expressions against heading text (alias: ~h*~).
*Changed*
+ Helm support (including the command =helm-org-ql=) has been moved to a separate package, =helm-org-ql=.
+ Predicate ~heading~ now matches plain strings instead of regular expressions.
*Internal*
+ Predicates are now defined more cleanly with a macro (=org-ql-defpred=) that consolidates functionality related to each predicate. This will also allow users to more easily define custom predicates.

View file

@ -1027,28 +1027,56 @@ predicates."
(list :regexp (rx bol (0+ space) ":STYLE:" (1+ space) "habit" (0+ space) eol))))
:body (org-is-habit-p))
(org-ql-defpred (heading h) (&rest regexps)
"Return non-nil if current entry's heading matches all REGEXPS (regexp strings)."
(org-ql-defpred (heading h) (&rest strings)
"Return non-nil if current entry's heading matches all STRINGS.
Matching is done case-insensitively."
:normalizers ((`(,predicate-names . ,args)
;; "h" alias.
`(heading ,@args)))
;; TODO: Adjust regexp to avoid matching in tag list.
:preambles ((`(,predicate-names ,string)
;; Only one string: match with preamble, then let predicate confirm (because
;; the match could be in e.g. the tags rather than the heading text).
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
,string)
'no-group)
:case-fold t :query query))
(`(,predicate-names . ,strings)
;; Multiple strings: use preamble to match against first
;; string, then let the predicate match the rest.
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
,(car strings))
'no-group)
:case-fold t :query query)))
;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments.
:body (let ((heading (org-get-heading 'no-tags 'no-todo))
(case-fold-search t))
(--all? (string-match it heading) strings)))
(org-ql-defpred (heading-regexp h*) (&rest regexps)
"Return non-nil if current entry's heading matches all REGEXPS (regexp strings).
Matching is done case-insensitively."
:normalizers ((`(,predicate-names . ,args)
;; "h" alias.
`(heading-regexp ,@args)))
;; MAYBE: Adjust regexp to avoid matching in tag list.
:preambles ((`(,predicate-names ,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).
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
,regexp)
(regexp ,regexp))
'no-group)
:query query))
:case-fold t :query query))
(`(,predicate-names . ,regexps)
;; Multiple regexps: use preamble to match against first
;; regexp, then let the predicate match the rest.
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
,(car regexps))
(regexp ,(car regexps)))
'no-group)
:query query)))
:case-fold t :query query)))
;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments.
:body (let ((heading (org-get-heading 'no-tags 'no-todo)))
:body (let ((heading (org-get-heading 'no-tags 'no-todo))
(case-fold-search t))
(--all? (string-match it heading) regexps)))
(org-ql-defpred level (level-or-comparator &optional level)

View file

@ -401,10 +401,14 @@ Arguments are listed next to predicate names, where applicable.
Return non-nil if entrys TODO keyword is in org-done-keywords.
habit
Return non-nil if entry is a habit.
heading (&rest regexps)
Return non-nil if current entrys heading matches all REGEXPS
(regexp strings).
heading (&rest strings)
Return non-nil if current entrys heading matches all STRINGS.
Matching is done case-insensitively.
• Aliases: h.
heading-regexp (&rest regexps)
Return non-nil if current entrys heading matches all REGEXPS
(regexp strings). Matching is done case-insensitively.
• Aliases: h*.
level (level-or-comparator &optional level)
Return non-nil if current headings outline level matches
arguments. The following forms are accepted: (level NUMBER):
@ -962,10 +966,14 @@ File: README.info, Node: 06-pre, Next: 05, Up: Changelog
*Added*
• Macro org-ql-defpred, used to define search predicates. (See
tutorial (examples/defpred.org).)
• Predicate heading-regexp, which matches regular expressions
against heading text (alias: h*).
*Changed*
• Helm support (including the command helm-org-ql) has been moved to
a separate package, helm-org-ql.
• Predicate heading now matches plain strings instead of regular
expressions.
*Internal*
• Predicates are now defined more cleanly with a macro
@ -1477,40 +1485,40 @@ Node: org-ql-sparse-tree7544
Node: Queries8344
Node: Non-sexp query syntax9455
Node: General predicates11162
Node: Ancestor/descendant predicates16575
Node: Date/time predicates17703
Node: Functions / Macros20358
Node: Agenda-like views20656
Node: Listing / acting-on results22061
Node: Custom predicates27683
Node: Dynamic block31174
Node: Links33872
Node: Tips34559
Node: Changelog34877
Node: 06-pre35573
Node: 0536196
Node: 04937673
Node: 04837947
Node: 04738294
Node: 04638689
Node: 04539089
Node: 04439448
Node: 04339805
Node: 04240000
Node: 04140161
Node: 0440402
Node: 03244335
Node: 03144714
Node: 0344911
Node: 02347886
Node: 02248114
Node: 02148382
Node: 0248581
Node: 0152616
Node: Notes52717
Node: Comparison with Org Agenda searches52879
Node: org-sidebar53751
Node: License54030
Node: Ancestor/descendant predicates16805
Node: Date/time predicates17933
Node: Functions / Macros20588
Node: Agenda-like views20886
Node: Listing / acting-on results22291
Node: Custom predicates27913
Node: Dynamic block31404
Node: Links34102
Node: Tips34789
Node: Changelog35107
Node: 06-pre35803
Node: 0536638
Node: 04938115
Node: 04838389
Node: 04738736
Node: 04639131
Node: 04539531
Node: 04439890
Node: 04340247
Node: 04240442
Node: 04140603
Node: 0440844
Node: 03244777
Node: 03145156
Node: 0345353
Node: 02348328
Node: 02248556
Node: 02148824
Node: 0249023
Node: 0153058
Node: Notes53159
Node: Comparison with Org Agenda searches53321
Node: org-sidebar54193
Node: License54472

End Tag Table

View file

@ -656,6 +656,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.rld"))
;; 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 ('(h* "T.ke over" "wor.*"))
'("Take over the world"))))
(describe "(link)"
(org-ql-it "without arguments"
(org-ql-expect ('(link))