Skip to content

Commit 2dfd378

Browse files
committed
Add/Change: (heading-regexp), (heading) predicates
Closes #64. Closes #68. Thanks to Feng Shu (@tumashu) for suggestions.
1 parent 31efd5a commit 2dfd378

File tree

4 files changed

+95
-45
lines changed

4 files changed

+95
-45
lines changed

README.org

+5-1
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,10 @@ Arguments are listed next to predicate names, where applicable.
207207
+ =category (&optional categories)= :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings).
208208
+ =done= :: Return non-nil if entry's ~TODO~ keyword is in ~org-done-keywords~.
209209
+ =habit= :: Return non-nil if entry is a habit.
210-
+ =heading (&rest regexps)= :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings).
210+
+ =heading (&rest strings)= :: Return non-nil if current entry's heading matches all ~STRINGS~. Matching is done case-insensitively.
211211
- Aliases: =h=.
212+
+ ~heading-regexp (&rest regexps)~ :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings). Matching is done case-insensitively.
213+
- Aliases: ~h*~.
212214
+ =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 ~>=~.
213215
+ =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.
214216
+ =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:
518520

519521
*Added*
520522
+ Macro =org-ql-defpred=, used to define search predicates. (See [[file:examples/defpred.org][tutorial]].)
523+
+ Predicate ~heading-regexp~, which matches regular expressions against heading text (alias: ~h*~).
521524

522525
*Changed*
523526
+ Helm support (including the command =helm-org-ql=) has been moved to a separate package, =helm-org-ql=.
527+
+ Predicate ~heading~ now matches plain strings instead of regular expressions.
524528

525529
*Internal*
526530
+ 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.

org-ql.el

+35-7
Original file line numberDiff line numberDiff line change
@@ -1027,28 +1027,56 @@ predicates."
10271027
(list :regexp (rx bol (0+ space) ":STYLE:" (1+ space) "habit" (0+ space) eol))))
10281028
:body (org-is-habit-p))
10291029

1030-
(org-ql-defpred (heading h) (&rest regexps)
1031-
"Return non-nil if current entry's heading matches all REGEXPS (regexp strings)."
1030+
(org-ql-defpred (heading h) (&rest strings)
1031+
"Return non-nil if current entry's heading matches all STRINGS.
1032+
Matching is done case-insensitively."
10321033
:normalizers ((`(,predicate-names . ,args)
10331034
;; "h" alias.
10341035
`(heading ,@args)))
1036+
;; TODO: Adjust regexp to avoid matching in tag list.
1037+
:preambles ((`(,predicate-names ,string)
1038+
;; Only one string: match with preamble, then let predicate confirm (because
1039+
;; the match could be in e.g. the tags rather than the heading text).
1040+
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
1041+
,string)
1042+
'no-group)
1043+
:case-fold t :query query))
1044+
(`(,predicate-names . ,strings)
1045+
;; Multiple strings: use preamble to match against first
1046+
;; string, then let the predicate match the rest.
1047+
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
1048+
,(car strings))
1049+
'no-group)
1050+
:case-fold t :query query)))
1051+
;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments.
1052+
:body (let ((heading (org-get-heading 'no-tags 'no-todo))
1053+
(case-fold-search t))
1054+
(--all? (string-match it heading) strings)))
1055+
1056+
(org-ql-defpred (heading-regexp h*) (&rest regexps)
1057+
"Return non-nil if current entry's heading matches all REGEXPS (regexp strings).
1058+
Matching is done case-insensitively."
1059+
:normalizers ((`(,predicate-names . ,args)
1060+
;; "h" alias.
1061+
`(heading-regexp ,@args)))
10351062
;; MAYBE: Adjust regexp to avoid matching in tag list.
10361063
:preambles ((`(,predicate-names ,regexp)
10371064
;; Only one regexp: match with preamble, then let predicate confirm (because
10381065
;; the match could be in e.g. the tags rather than the heading text).
10391066
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
1040-
,regexp)
1067+
(regexp ,regexp))
10411068
'no-group)
1042-
:query query))
1069+
:case-fold t :query query))
10431070
(`(,predicate-names . ,regexps)
10441071
;; Multiple regexps: use preamble to match against first
10451072
;; regexp, then let the predicate match the rest.
10461073
(list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl)
1047-
,(car regexps))
1074+
(regexp ,(car regexps)))
10481075
'no-group)
1049-
:query query)))
1076+
:case-fold t :query query)))
10501077
;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments.
1051-
:body (let ((heading (org-get-heading 'no-tags 'no-todo)))
1078+
:body (let ((heading (org-get-heading 'no-tags 'no-todo))
1079+
(case-fold-search t))
10521080
(--all? (string-match it heading) regexps)))
10531081

10541082
(org-ql-defpred level (level-or-comparator &optional level)

org-ql.info

+45-37
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,14 @@ Arguments are listed next to predicate names, where applicable.
401401
Return non-nil if entry’s ‘TODO’ keyword is in ‘org-done-keywords’.
402402
‘habit’
403403
Return non-nil if entry is a habit.
404-
‘heading (&rest regexps)’
405-
Return non-nil if current entry’s heading matches all ‘REGEXPS’
406-
(regexp strings).
404+
‘heading (&rest strings)’
405+
Return non-nil if current entry’s heading matches all ‘STRINGS’.
406+
Matching is done case-insensitively.
407407
• Aliases: h.
408+
‘‘heading-regexp (&rest regexps)’’
409+
Return non-nil if current entry’s heading matches all ‘REGEXPS’
410+
(regexp strings). Matching is done case-insensitively.
411+
• Aliases: ‘h*’.
408412
‘level (level-or-comparator &optional level)’
409413
Return non-nil if current heading’s outline level matches
410414
arguments. The following forms are accepted: ‘(level NUMBER)’:
@@ -962,10 +966,14 @@ File: README.info, Node: 06-pre, Next: 05, Up: Changelog
962966
*Added*
963967
• Macro org-ql-defpred, used to define search predicates. (See
964968
tutorial (examples/defpred.org).)
969+
• Predicate ‘heading-regexp’, which matches regular expressions
970+
against heading text (alias: ‘h*’).
965971

966972
*Changed*
967973
• Helm support (including the command helm-org-ql) has been moved to
968974
a separate package, helm-org-ql.
975+
• Predicate ‘heading’ now matches plain strings instead of regular
976+
expressions.
969977

970978
*Internal*
971979
• Predicates are now defined more cleanly with a macro
@@ -1477,40 +1485,40 @@ Node: org-ql-sparse-tree7544
14771485
Node: Queries8344
14781486
Node: Non-sexp query syntax9455
14791487
Node: General predicates11162
1480-
Node: Ancestor/descendant predicates16575
1481-
Node: Date/time predicates17703
1482-
Node: Functions / Macros20358
1483-
Node: Agenda-like views20656
1484-
Node: Listing / acting-on results22061
1485-
Node: Custom predicates27683
1486-
Node: Dynamic block31174
1487-
Node: Links33872
1488-
Node: Tips34559
1489-
Node: Changelog34877
1490-
Node: 06-pre35573
1491-
Node: 0536196
1492-
Node: 04937673
1493-
Node: 04837947
1494-
Node: 04738294
1495-
Node: 04638689
1496-
Node: 04539089
1497-
Node: 04439448
1498-
Node: 04339805
1499-
Node: 04240000
1500-
Node: 04140161
1501-
Node: 0440402
1502-
Node: 03244335
1503-
Node: 03144714
1504-
Node: 0344911
1505-
Node: 02347886
1506-
Node: 02248114
1507-
Node: 02148382
1508-
Node: 0248581
1509-
Node: 0152616
1510-
Node: Notes52717
1511-
Node: Comparison with Org Agenda searches52879
1512-
Node: org-sidebar53751
1513-
Node: License54030
1488+
Node: Ancestor/descendant predicates16805
1489+
Node: Date/time predicates17933
1490+
Node: Functions / Macros20588
1491+
Node: Agenda-like views20886
1492+
Node: Listing / acting-on results22291
1493+
Node: Custom predicates27913
1494+
Node: Dynamic block31404
1495+
Node: Links34102
1496+
Node: Tips34789
1497+
Node: Changelog35107
1498+
Node: 06-pre35803
1499+
Node: 0536638
1500+
Node: 04938115
1501+
Node: 04838389
1502+
Node: 04738736
1503+
Node: 04639131
1504+
Node: 04539531
1505+
Node: 04439890
1506+
Node: 04340247
1507+
Node: 04240442
1508+
Node: 04140603
1509+
Node: 0440844
1510+
Node: 03244777
1511+
Node: 03145156
1512+
Node: 0345353
1513+
Node: 02348328
1514+
Node: 02248556
1515+
Node: 02148824
1516+
Node: 0249023
1517+
Node: 0153058
1518+
Node: Notes53159
1519+
Node: Comparison with Org Agenda searches53321
1520+
Node: org-sidebar54193
1521+
Node: License54472
15141522

15151523
End Tag Table
15161524

tests/test-org-ql.el

+10
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,16 @@ RESULTS should be a list of strings as returned by
656656
(org-ql-expect ('(heading "Take over" "world"))
657657
'("Take over the world"))))
658658

659+
(describe "(heading-regexp)"
660+
(org-ql-it "with one argument"
661+
(org-ql-expect ('(heading-regexp "w.rld"))
662+
;; NOTE: This--correctly--does not match the "Skype with president of
663+
;; Antarctica" heading, which has the tag ":world:" in its heading line.
664+
'("Take over the world")))
665+
(org-ql-it "with two arguments"
666+
(org-ql-expect ('(h* "T.ke over" "wor.*"))
667+
'("Take over the world"))))
668+
659669
(describe "(link)"
660670
(org-ql-it "without arguments"
661671
(org-ql-expect ('(link))

0 commit comments

Comments
 (0)