diff --git a/README.org b/README.org index d7d3587c..5a096adf 100644 --- a/README.org +++ b/README.org @@ -242,7 +242,7 @@ Arguments are listed next to predicate names, where applicable. - Aliases: ~olps~. + =path (&rest regexps)= :: Return non-nil if current heading's buffer's filename path matches any of ~REGEXPS~ (regexp strings). Without arguments, return non-nil if buffer is file-backed. + =priority (&rest args)= :: Return non-nil if current heading has a certain priority. ~ARGS~ may be either a list of one or more priority letters as strings, or a comparator function symbol followed by a priority letter string. For example: ~(priority "A") (priority "A" "B") (priority '>= "B")~ Note that items without a priority cookie never match this predicate (while Org itself considers items without a cookie to have the default priority, which, by default, is equal to priority ~B~). -+ =property (property &optional value &key inherit)= :: Return non-nil if current entry has ~PROPERTY~ (a string), and optionally ~VALUE~ (a string). If ~INHERIT~ is nil, only match entries with ~PROPERTY~ set on the entry; if t, also match entries with inheritance. If ~INHERIT~ is not specified, use the Boolean value of ~org-use-property-inheritance~, which see (i.e. it is only interpreted as nil or non-nil). ++ =property (property &optional value &key inherit multi)= :: Return non-nil if current entry has ~PROPERTY~ (a string), and optionally ~VALUE~ (a string). If ~INHERIT~ is nil, only match entries with ~PROPERTY~ set on the entry; if t, also match entries with inheritance. If ~INHERIT~ is not specified, use the Boolean value of ~org-use-property-inheritance~, which see (i.e. it is only interpreted as nil or non-nil). If ~MULTI~ is non-nil, also check for multi-value properties. + =regexp (&rest regexps)= :: Return non-nil if current entry matches all of ~REGEXPS~ (regexp strings). Matches against entire entry, from beginning of its heading to the next heading. - Aliases: =r=. + =rifle (&rest strings)= :: Return non-nil if each string is found in either the entry or its outline path. Works like =org-rifle=. This is probably the most useful, intuitive, general-purpose predicate. diff --git a/org-ql.el b/org-ql.el index cf669c41..9a72f48d 100644 --- a/org-ql.el +++ b/org-ql.el @@ -1784,13 +1784,14 @@ priority B)." (cl-loop for priority-arg in args thereis (= item-priority (* 1000 (- org-lowest-priority (string-to-char priority-arg))))))))) -(org-ql-defpred property (property &optional value &key inherit) +(org-ql-defpred property (property &optional value &key inherit multi) "Return non-nil if current entry has PROPERTY, and optionally VALUE. If INHERIT is nil, only match entries with PROPERTY set on the entry; if t, also match entries with inheritance. If INHERIT is not specified, use the Boolean value of `org-use-property-inheritance', which see (i.e. it is only -interpreted as nil or non-nil)." +interpreted as nil or non-nil). If MULTI is non-nil, also check for +multi-value properties." :normalizers ((`(,predicate-names) ;; HACK: This clause protects against the case in ;; which the arguments are nil, which would cause an @@ -1811,26 +1812,31 @@ interpreted as nil or non-nil)." (list 'property property value :inherit (if (plist-member plist :inherit) (plist-get plist :inherit) - org-use-property-inheritance)))) + org-use-property-inheritance) + :multi (when (plist-member plist :multi) + (plist-get plist :multi))))) ;; MAYBE: Should case folding be disabled for properties? What about values? ;; MAYBE: Support (property) without args. ;; NOTE: When inheritance is enabled, the preamble can't be used, ;; which will make the search slower. - :preambles ((`(,predicate-names ,property ,value . ,(map :inherit)) + :preambles ((`(,predicate-names ,property ,value ,(map :multi) . ,(map :inherit)) ;; 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. (list :regexp (unless inherit - (rx-to-string `(seq bol (0+ space) ":" ,property ":" + (rx-to-string `(seq bol (0+ space) ":" ,property + ,@(when multi '((? "+"))) ":" (1+ space) ,value (0+ space) eol))) :query query)) - (`(,predicate-names ,property . ,(map :inherit)) + (`(,predicate-names ,property ,(map :multi) . ,(map :inherit)) ;; 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. ;; NOTE: The preamble only matches if there appears to be a value. ;; A line like ":ID: " without any other text does not match. (list :regexp (unless inherit - (rx-to-string `(seq bol (0+ space) ":" ,property ":" (1+ space) + (rx-to-string `(seq bol (0+ space) ":" ,property + ,@(when multi '((? "+"))) + ":" (1+ space) (minimal-match (1+ not-newline)) eol))) :query query))) :body @@ -1848,9 +1854,18 @@ interpreted as nil or non-nil)." ;; TODO: Since --value-at doesn't account for inheritance, ;; we should generalize --tags-at to also work for property ;; inheritance and use it here, which should be much faster. - (string-equal value (org-ql--value-at - (point) (lambda () - (org-entry-get (point) property inherit))))))))) + (if multi + (when-let (values (org-ql--value-at + (point) (lambda () + ;; The default separator is space + (let ((org-property-separators `((,property . "\n")))) + (org-entry-get (point) property inherit))))) + (seq-some (lambda (v) + (string-equal value v)) + (split-string values "\n"))) + (string-equal value (org-ql--value-at + (point) (lambda () + (org-entry-get (point) property inherit)))))))))) ;; TODO: Add property-local, property-inherit, etc. to match tags predicates. ;; TODO: Add tests for property inheritance.