From a99759d4d9e38ade40a4020d5050d166544fd5a4 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Sun, 25 Aug 2019 21:19:51 -0500 Subject: [PATCH] Add: (priority) Preambles and plain predicate style --- README.org | 2 +- org-ql.el | 47 +++++++++++++++++++++++++++++++++++++++++++- tests/test-org-ql.el | 23 ++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/README.org b/README.org index 9b11864..113cf8a 100644 --- a/README.org +++ b/README.org @@ -205,7 +205,7 @@ Arguments are listed next to predicate names, where applicable. + ~habit~ :: Return non-nil if entry is a habit. + ~heading (regexp)~ :: Return non-nil if current entry's heading matches ~REGEXP~ (a regexp string). + ~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 ~>=~. -+ ~priority (&optional comparator-or-priority priority)~ :: Return non-nil if current heading has a certain priority. ~COMPARATOR-OR-PRIORITY~ should be either a comparator function, like ~<=~, or a priority string, like "A" (in which case (~=~ will be the comparator). If ~COMPARATOR-OR-PRIORITY~ is a comparator, ~PRIORITY~ should be a priority string. ++ ~priority (&optional comparator-or-priority priority)~ :: Return non-nil if current heading has a certain priority. ~COMPARATOR-OR-PRIORITY~ should be either a comparator function, like ~<=~, or a priority string, like "A" (in which case (~=~ will be the comparator). If ~COMPARATOR-OR-PRIORITY~ is a comparator, ~PRIORITY~ should be a priority string. If both arguments are nil, return non-nil if heading has any defined priority. + ~property (property &optional value)~ :: Return non-nil if current entry has ~PROPERTY~ (a string), and optionally ~VALUE~ (a string). Note that property inheritance is currently /not/ enabled for this predicate. If you need to test with inheritance, you could use a custom predicate form, like ~(org-entry-get (point) "PROPERTY" 'inherit)~. + ~regexp (regexp)~ :: Return non-nil if current entry matches ~REGEXP~ (a regexp string). Matches against entire entry, from beginning of its heading to the next heading. + ~tags (&optional tags)~ :: Return non-nil if current heading has one or more of ~TAGS~ (a list of strings). diff --git a/org-ql.el b/org-ql.el index 34a0aeb..e47a863 100644 --- a/org-ql.el +++ b/org-ql.el @@ -311,6 +311,16 @@ Replaces bare strings with (regexp) selectors, and appropriate (ts-adjust 'day num-days) (ts-apply :hour 23 :minute 59 :second 59)))) `(,pred :to ,to))) + ;; Priorities + (`(priority) ;; Match any defined priority by comparing to C. + ;; Note that we quote the comparator again for consistency. + `(priority '>= "C")) + (`(priority ,_letter) element) + (`(priority ,comparator ,letter) + ;; Quote comparator. + `(priority ',comparator ,letter)) + + ;; Timestamps (`(,(or 'ts-active 'ts-a) . ,rest) `(ts :type active ,@rest)) (`(,(or 'ts-inactive 'ts-i) . ,rest) `(ts :type inactive ,@rest)) ;; Any other form: passed through unchanged. @@ -435,6 +445,39 @@ replace the clause with a preamble." (setq org-ql-preamble org-ql-planning-regexp) ;; Return element, because the predicate still needs testing. element) + + ;; Priorities. + ;; NOTE: This only accepts A, B, or C. I haven't seen + ;; other priorities in the wild, so this will do for now. + (`(priority) + ;; Any priority. + (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) "[#" (in "ABC") "]") t)) + nil) + (`(priority ,letter) + ;; Specific priority without comparator. + (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) + (optional (1+ upper) (1+ blank)) + "[#" ,letter "]") t)) + nil) + (`(priority ,comparator ,letter) + (let* ((priority-letters '("A" "B" "C")) + (index (-elem-index letter priority-letters)) + ;; NOTE: Higher priority == lower number. + ;; NOTE: Because we need to support both preamble-based queries and + ;; regular predicate ones, we work around an idiosyncrasy of query + ;; pre-processing by accepting both quoted and double-quoted comparator + ;; function symbols. Not the most elegant solution, but it works. + (priorities (s-join "" (pcase comparator + ((or '= ''=) (list letter)) + ((or '> ''>) (cl-subseq priority-letters 0 index)) + ((or '>= ''>=) (cl-subseq priority-letters 0 (1+ index))) + ((or '< ''<) (cl-subseq priority-letters (1+ index))) + ((or '<= ''<=) (cl-subseq priority-letters index)))))) + (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (optional (1+ upper) (1+ blank)) + "[#" (in ,priorities) "]") t)) + nil)) + + ;; Properties. (`(property ,property ,value) ;; We do NOT return nil, because the predicate still needs to be tested, ;; because the regexp could match a string not inside a property drawer. @@ -687,7 +730,9 @@ COMPARATOR may be `<', `<=', `>', or `>='." COMPARATOR-OR-PRIORITY should be either a comparator function, like `<=', or a priority string, like \"A\" (in which case (`=' will be the comparator). If COMPARATOR-OR-PRIORITY is a -comparator, PRIORITY should be a priority string." +comparator, PRIORITY should be a priority string. If both +arguments are nil, return non-nil if heading has any defined +priority." (let* (comparator) (cond ((null priority) ;; No comparator given: compare only given priority with = diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index e057641..c976146 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -447,6 +447,29 @@ RESULTS should be a list of strings as returned by (org-ql-expect ((planning :to today)) '("Skype with president of Antarctica" "Practice leaping tall buildings in a single bound" "Learn universal sign language" "Order a pizza" "Get haircut" "Fix flux capacitor" "/r/emacs" "Shop for groceries" "Rewrite Emacs in Common Lisp"))))) + (describe "(priority)" + + (org-ql-it "without arguments" + (org-ql-expect ((priority)) + '("Take over the universe" "Take over the world" "Skype with president of Antarctica" "Take over Mars" "Take over the moon" "Renew membership in supervillain club" "Learn universal sign language" "Get haircut" "Internet" "Spaceship lease" "Fix flux capacitor"))) + (org-ql-it "with a priority" + (org-ql-expect ((priority "A")) + '("Take over the universe" "Take over the world" "Skype with president of Antarctica" "Spaceship lease"))) + (org-ql-it "= a priority" + (org-ql-expect ((priority = "A")) + '("Take over the universe" "Take over the world" "Skype with president of Antarctica" "Spaceship lease"))) + (org-ql-it "< a priority" + (org-ql-expect ((priority < "B")) + '("Take over the moon" "Get haircut"))) + (org-ql-it "<= a priority" + (org-ql-expect ((priority <= "B")) + '("Take over Mars" "Take over the moon" "Renew membership in supervillain club" "Learn universal sign language" "Get haircut" "Internet" "Fix flux capacitor"))) + (org-ql-it "> a priority" + (org-ql-expect ((priority > "B")) + '("Take over the universe" "Take over the world" "Skype with president of Antarctica" "Spaceship lease"))) + (org-ql-it ">= a priority" + (org-ql-expect ((priority >= "B")) + '("Take over the universe" "Take over the world" "Skype with president of Antarctica" "Take over Mars" "Renew membership in supervillain club" "Learn universal sign language" "Internet" "Spaceship lease" "Fix flux capacitor")))) (describe "(property)" ;; MAYBE: Add support for (property) without arguments.