diff --git a/README.org b/README.org index 34dde96..693c7f1 100644 --- a/README.org +++ b/README.org @@ -118,7 +118,7 @@ Note that, for convenience, standard numeric comparator function symbols (~<~, ~ + ~done~ :: Return non-nil if entry's ~TODO~ keyword is in ~org-done-keywords~. + ~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 ~LEVEL~ with ~COMPARATOR~. If ~LEVEL~ is nil, ~LEVEL-OR-COMPARATOR~ should be an integer level, which will be tested for equality to the heading's outline level. If ~LEVEL~ is non-nil, ~LEVEL-OR-COMPARATOR~ should be a comparator function (like ~<=~). ++ ~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 ~>=~. + ~planning (&optional comparator target-date)~ :: Return non-nil if entry's planning date (deadline or scheduled) compares with ~TARGET-DATE~ using ~COMPARATOR~. ~TARGET-DATE~ should be a string parseable by ~date-to-day~. ~COMPARATOR~ should be a function (like ~<=~). + ~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. + ~property (property &optional value)~ :: Return non-nil if current entry has ~PROPERTY~ (a string), and optionally ~VALUE~ (a string). diff --git a/org-ql.el b/org-ql.el index 9782f3e..6399d22 100644 --- a/org-ql.el +++ b/org-ql.el @@ -281,7 +281,7 @@ replace the clause with a preamble." (pcase element (`(or _) element) (`(regexp . ,regexps) - (let* ((regexp (rx-to-string `(or ,@regexps)))) + (let* ((regexp (rx-to-string `(or ,@regexps) t))) (setq org-ql-preamble regexp) ;; Return nil nil)) @@ -289,13 +289,21 @@ replace the clause with a preamble." (let* ((regexps (--map (list 'regexp (format org-heading-keyword-regexp-format it)) todo-keywords)) - (regexp (rx-to-string `(or ,@regexps)))) + (regexp (rx-to-string `(or ,@regexps) t))) (setq org-ql-preamble regexp) ;; Return nil nil)) - ;; FIXME: Need to handle e.g. (level <= 2) + (`(level ,comparator-or-num ,num) + (let ((repeat (pcase comparator-or-num + ('< `(repeat 1 ,(1- num) "*")) + ('<= `(repeat 1 ,num "*")) + ('> `(>= ,(1+ num) "*")) + ('>= `(>= ,num "*")) + ((pred integerp) `(repeat ,comparator-or-num ,num "*"))))) + (setq org-ql-preamble (rx-to-string `(seq bol ,repeat " ") t)) + nil)) (`(level ,num) - (let* ((regexp (rx-to-string `(seq bol (repeat ,num "*") " ")))) + (let* ((regexp (rx-to-string `(seq bol (repeat ,num "*") " ") t))) (setq org-ql-preamble regexp) nil)) (`(and . ,rest) @@ -502,20 +510,25 @@ With KEYWORDS, return non-nil if its keyword is one of KEYWORDS (a list of strin (otherwise (seq-intersection tags tags-at))))) (org-ql--defpred level (level-or-comparator &optional level) - "Return non-nil if current heading's outline level matches LEVEL with COMPARATOR. + "Return non-nil if current heading's outline level matches arguments. +The following forms are accepted: -If LEVEL is nil, LEVEL-OR-COMPARATOR should be an integer level, -which will be tested for equality to the heading's outline level. -If LEVEL is non-nil, LEVEL-OR-COMPARATOR should be a comparator -function (like `<=')." + (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 `>='." ;; NOTE: It might be necessary to take into account `org-odd-levels'; see docstring for ;; `org-outline-level'. (when-let ((outline-level (org-outline-level))) - (pcase level - ;; Check for equality - ((pred null) (= outline-level level-or-comparator)) - ;; Check with comparator - (_ (funcall level-or-comparator outline-level level))))) + (pcase level-or-comparator + ((pred numberp) (pcase level + ('nil ;; Equality + (= outline-level level-or-comparator)) + ((pred numberp) ;; Between two levels + (>= level-or-comparator outline-level level)))) + ((pred symbolp) ;; Compare with function + (funcall level-or-comparator outline-level level))))) (org-ql--defpred priority (&optional comparator-or-priority priority) "Return non-nil if current heading has a certain priority. diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index 427a30d..0cb32e3 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -107,7 +107,34 @@ Based on Buttercup macro `it'." (cl-loop while (re-search-forward org-heading-regexp nil t) sum 1))))) - (describe "Predicates" + (describe "Query compiling" + ;; Okay, so it's not really "compiling," but it sounds fancy. :) + + ;; TODO: Other predicates. + + (describe "(level)" + (it "with a number" + (expect (org-ql--query-preamble '(level 2)) + :to-equal `(t ,(rx bol (repeat 2 "*") " ")))) + (it "with two numbers" + (expect (org-ql--query-preamble '(level 2 4)) + :to-equal `(t ,(rx bol (repeat 2 4 "*") " ")))) + (it "<" + (expect (org-ql--query-preamble '(level < 3)) + :to-equal `(t ,(rx bol (repeat 1 2 "*") " ")))) + (it "<=" + (expect (org-ql--query-preamble '(level <= 2)) + :to-equal `(t ,(rx bol (repeat 1 2 "*") " ")))) + (it ">" + (expect (org-ql--query-preamble '(level > 2)) + :to-equal `(t ,(rx bol (>= 3 "*") " ")))) + (it ">=" + (expect (org-ql--query-preamble '(level >= 2)) + :to-equal `(t ,(rx bol (>= 2 "*") " ")))))) + + (describe "Query results" + + ;; TODO: Other predicates. (describe "(category)" (org-ql-it "without arguments" @@ -364,10 +391,6 @@ Based on Buttercup macro `it'." (expect (org-ql test-buffer (ts :on "2019-06-09") :action (org-ql-test-org-get-heading)) - :to-equal nil)) - ) - - ;; TODO: Other predicates. - )) + :to-equal nil))))) ;;; org-ql.el ends here