From 65fb37c30d6c0764a5f9f2b17b7062439804740e Mon Sep 17 00:00:00 2001 From: Josh Moller-Mara Date: Wed, 18 Dec 2019 01:01:47 +0800 Subject: [PATCH 01/10] Add: parent and ancestors predicates These predicates are analogs to the existing "children" and "descendents" predicates. They may be useful for testing parents for specific todo states, or if they have non-inherited tags (i.e. tags from "org-tags-exclude-from-inheritance"). --- README.org | 3 +++ org-ql.el | 73 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/README.org b/README.org index 4922e12..23f672f 100644 --- a/README.org +++ b/README.org @@ -181,7 +181,9 @@ 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). + =children (&optional query)= :: Return non-nil if current heading has direct child headings. If ~QUERY~, test it against child headings. This selector may be nested, e.g. to match grandchild headings. ++ =parent (&optional query)= :: Return non-nil if current heading has a direct parent heading. If ~QUERY~, test it against the parent heading. This selector may be nested, e.g. to match grandparent headings. + =descendants (&optional query)= :: Return non-nil if current heading has descendant headings. If ~QUERY~, test it against descendant headings. This selector may be nested (if you can grok the nesting!). ++ =ancestors (&optional query)= :: Return non-nil if current heading has ancestor headings (which is true if it has a parent heading). With ~QUERY~, return non-nil if some ancestor heading matches it. This selector may also be nested. + =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). @@ -395,6 +397,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience - Negation of terms in plain queries using ~!~. For example, ~tags:space !moon~ to exclude entries which contain ~moon~. - Predicates =outline-path= (alias =olp=) and =outline-path-segment= (alias =olps=). - Predicate ~src~, which matches Org Babel source blocks. + - Predicates =parent= and =ancestors=. - Alias =h= for =heading= predicate. - Alias =r= for =regexp= predicate. (Thanks to [[https://github.com/tumashu][Feng Shu]].) + Info manual. diff --git a/org-ql.el b/org-ql.el index effa411..e643ca5 100644 --- a/org-ql.el +++ b/org-ql.el @@ -548,6 +548,10 @@ Replaces bare strings with (regexp) selectors, and appropriate (`(children) '(children (lambda () t))) (`(descendants ,query) `(descendants ',query)) (`(descendants) '(descendants (lambda () t))) + (`(parent ,query) `(parent ,(org-ql--query-predicate (rec query)))) + (`(parent) `(parent ,(org-ql--query-predicate (rec '(lambda () t))))) + (`(ancestors ,query) `(ancestors ,(org-ql--query-predicate (rec query)))) + (`(ancestors) `(ancestors ,(org-ql--query-predicate (rec '(lambda () t))))) ;; Timestamp-based predicates. I think this is the way that makes the most sense: ;; set the limit to N days in the future, adjusted to 23:59:59 (since Org doesn't ;; support timestamps down to the second, anyway, there should be no need to adjust @@ -931,38 +935,49 @@ Arguments STRING, POS, FILL, and LEVEL are according to (org-ql--defpred children (query) "Return non-nil if current entry has children matching QUERY." - (save-excursion - (save-restriction - (org-narrow-to-subtree) - (when (org-goto-first-child) - ;; Lisp makes this easy and elegant: all we do is modify the query, - ;; nesting it inside an (and), and it doesn't descend into grandchildren. - (let* ((level (org-current-level)) - (query (cl-typecase query - (byte-code-function `(and (level ,level) - (funcall ,query))) - (t `(and (level ,level) - ,query))))) - (catch 'found - (org-ql-select (current-buffer) - query - :narrow t - :action (lambda () - (throw 'found t))))))))) + (org-with-wide-buffer + ;; Widening is needed if inside an "ancestors" query + (org-narrow-to-subtree) + (when (org-goto-first-child) + ;; Lisp makes this easy and elegant: all we do is modify the query, + ;; nesting it inside an (and), and it doesn't descend into grandchildren. + (let* ((level (org-current-level)) + (query (cl-typecase query + (byte-code-function `(and (level ,level) + (funcall ,query))) + (t `(and (level ,level) + ,query))))) + (catch 'found + (org-ql-select (current-buffer) + query + :narrow t + :action (lambda () + (throw 'found t)))))))) + +(org-ql--defpred parent (predicate) + "Return non-nil if the current entry's parent satisfies PREDICATE." + (org-with-wide-buffer + (when (org-up-heading-safe) + (org-ql--value-at (point) predicate)))) (org-ql--defpred descendants (query) "Return non-nil if current entry has descendants matching QUERY." - (save-excursion - (save-restriction - (org-narrow-to-subtree) - (when (org-goto-first-child) - (narrow-to-region (point) (point-max)) - (catch 'found - (org-ql-select (current-buffer) - query - :narrow t - :action (lambda () - (throw 'found t)))))))) + (org-with-wide-buffer + (org-narrow-to-subtree) + (when (org-goto-first-child) + (narrow-to-region (point) (point-max)) + (catch 'found + (org-ql-select (current-buffer) + query + :narrow t + :action (lambda () + (throw 'found t))))))) + +(org-ql--defpred ancestors (predicate) + "Return non-nil if any of current entry's ancestors satisfy PREDICATE." + (org-with-wide-buffer + (cl-loop while (org-up-heading-safe) + thereis (org-ql--value-at (point) predicate)))) (org-ql--defpred category (&rest categories) "Return non-nil if current heading is in one or more of CATEGORIES (a list of strings)." From e48cdf71437cb5030b23048486bb848afd569d9a Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 13 Jan 2020 20:22:53 -0600 Subject: [PATCH 02/10] Tests: Add ancestor/parent tests --- tests/test-org-ql.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index 2d1e6ed..982e226 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -347,6 +347,24 @@ RESULTS should be a list of strings as returned by ;; TODO: Other predicates. + (describe "(ancestors)" + (org-ql-it "without sub-query" + (org-ql-expect ((ancestors)) + '("Take over the world" "Skype with president of Antarctica" "Take over Mars" "Visit Mars" "Take over the moon" "Visit the moon" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Learn universal sign language" "/r/emacs" "Shop for groceries" "Sunrise/sunset" "Rewrite Emacs in Common Lisp" "Write a symphony"))) + + (org-ql-it "with sub-query" + (org-ql-expect ((ancestors (heading "universe"))) + '("Take over the world" "Skype with president of Antarctica" "Take over Mars" "Visit Mars" "Take over the moon" "Visit the moon" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Learn universal sign language")))) + + (describe "(parent)" + (org-ql-it "without sub-query" + (org-ql-expect ((parent)) + '("Take over the world" "Skype with president of Antarctica" "Take over Mars" "Visit Mars" "Take over the moon" "Visit the moon" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Learn universal sign language" "/r/emacs" "Shop for groceries" "Sunrise/sunset" "Rewrite Emacs in Common Lisp" "Write a symphony"))) + + (org-ql-it "with sub-query" + (org-ql-expect ((parent (and (todo) (priority "A")))) + '("Take over the world" "Skype with president of Antarctica" "Take over Mars" "Take over the moon" "Practice leaping tall buildings in a single bound" "Renew membership in supervillain club" "Learn universal sign language")))) + (describe "(category)" (org-ql-it "without arguments" From 3003eb8ad490a89861ddce3ffb21df5f7bec0af4 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 18:42:20 -0600 Subject: [PATCH 03/10] Comment: Add tasks --- org-ql.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/org-ql.el b/org-ql.el index e643ca5..ec41546 100644 --- a/org-ql.el +++ b/org-ql.el @@ -941,6 +941,9 @@ Arguments STRING, POS, FILL, and LEVEL are according to (when (org-goto-first-child) ;; Lisp makes this easy and elegant: all we do is modify the query, ;; nesting it inside an (and), and it doesn't descend into grandchildren. + + ;; TODO: However, this could probably be rewritten like the `ancestors' predicate, + ;; which avoids calling `org-ql-select' recursively and its associated overhead. (let* ((level (org-current-level)) (query (cl-typecase query (byte-code-function `(and (level ,level) @@ -962,6 +965,8 @@ Arguments STRING, POS, FILL, and LEVEL are according to (org-ql--defpred descendants (query) "Return non-nil if current entry has descendants matching QUERY." + ;; TODO: This could probably be rewritten like the `ancestors' predicate, + ;; which avoids calling `org-ql-select' recursively and its associated overhead. (org-with-wide-buffer (org-narrow-to-subtree) (when (org-goto-first-child) From adc5447c7287082dec8cb16ec90ac634147b75bf Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 22:45:16 -0600 Subject: [PATCH 04/10] Tidy/Comment: Ancestor/descendant predicates --- org-ql.el | 119 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 51 deletions(-) diff --git a/org-ql.el b/org-ql.el index ec41546..90f752f 100644 --- a/org-ql.el +++ b/org-ql.el @@ -933,57 +933,6 @@ Arguments STRING, POS, FILL, and LEVEL are according to ;;;;; Predicates -(org-ql--defpred children (query) - "Return non-nil if current entry has children matching QUERY." - (org-with-wide-buffer - ;; Widening is needed if inside an "ancestors" query - (org-narrow-to-subtree) - (when (org-goto-first-child) - ;; Lisp makes this easy and elegant: all we do is modify the query, - ;; nesting it inside an (and), and it doesn't descend into grandchildren. - - ;; TODO: However, this could probably be rewritten like the `ancestors' predicate, - ;; which avoids calling `org-ql-select' recursively and its associated overhead. - (let* ((level (org-current-level)) - (query (cl-typecase query - (byte-code-function `(and (level ,level) - (funcall ,query))) - (t `(and (level ,level) - ,query))))) - (catch 'found - (org-ql-select (current-buffer) - query - :narrow t - :action (lambda () - (throw 'found t)))))))) - -(org-ql--defpred parent (predicate) - "Return non-nil if the current entry's parent satisfies PREDICATE." - (org-with-wide-buffer - (when (org-up-heading-safe) - (org-ql--value-at (point) predicate)))) - -(org-ql--defpred descendants (query) - "Return non-nil if current entry has descendants matching QUERY." - ;; TODO: This could probably be rewritten like the `ancestors' predicate, - ;; which avoids calling `org-ql-select' recursively and its associated overhead. - (org-with-wide-buffer - (org-narrow-to-subtree) - (when (org-goto-first-child) - (narrow-to-region (point) (point-max)) - (catch 'found - (org-ql-select (current-buffer) - query - :narrow t - :action (lambda () - (throw 'found t))))))) - -(org-ql--defpred ancestors (predicate) - "Return non-nil if any of current entry's ancestors satisfy PREDICATE." - (org-with-wide-buffer - (cl-loop while (org-up-heading-safe) - thereis (org-ql--value-at (point) predicate)))) - (org-ql--defpred category (&rest categories) "Return non-nil if current heading is in one or more of CATEGORIES (a list of strings)." (when-let ((category (org-get-category (point)))) @@ -1213,6 +1162,74 @@ language." ;; No regexps to check: return non-nil. t)))))) +;;;;;; Ancestor/descendant + +;; These predicates search ancestor and descendant headings for sub-queries. + +;; Note that the implementations of the upward-searching, ancestor/parent predicates differ +;; from that of the downward-searching, descendants/children predicates in that the former +;; take a predicate function as their argument and test it on each heading (the predicate +;; being created by the `--query-pre-process' function, which see), while the latter take an +;; `org-ql' query form as their argument and execute another `org-ql-select' query inside of +;; the currently running query. This "split" implementation seems like the most generally +;; efficient one, because searching descendants searches potentially many more headings than +;; searching ancestors, so executing a full query in that case can be faster due to use of +;; the "preambles" provided by running a full query. However, see note below. + +(org-ql--defpred parent (predicate) + "Return non-nil if the current entry's parent satisfies PREDICATE." + (org-with-wide-buffer + (when (org-up-heading-safe) + (org-ql--value-at (point) predicate)))) + +(org-ql--defpred ancestors (predicate) + "Return non-nil if any of current entry's ancestors satisfy PREDICATE." + (org-with-wide-buffer + (cl-loop while (org-up-heading-safe) + thereis (org-ql--value-at (point) predicate)))) + +;; MAYBE: The `children' and `descendants' predicates could probably be rewritten like +;; the `ancestors' predicate, which avoids calling `org-ql-select' recursively and its +;; associated overhead. However, that would preclude the use of preambles, so depending +;; on the Org file being searched and the sub-query, performance could be better or +;; worse. It should be benchmarked extensively before so changing the implementation. + +(org-ql--defpred children (query) + "Return non-nil if current entry has children matching QUERY." + (org-with-wide-buffer + ;; Widening is needed if inside an "ancestors" query + (org-narrow-to-subtree) + (when (org-goto-first-child) + ;; Lisp makes this easy and elegant: all we do is modify the query, + ;; nesting it inside an (and), and it doesn't descend into grandchildren. + (let* ((level (org-current-level)) + (query (cl-typecase query + (byte-code-function `(and (level ,level) + (funcall ,query))) + (t `(and (level ,level) + ,query))))) + (catch 'found + (org-ql-select (current-buffer) + query + :narrow t + :action (lambda () + (throw 'found t)))))))) + +(org-ql--defpred descendants (query) + "Return non-nil if current entry has descendants matching QUERY." + ;; TODO: This could probably be rewritten like the `ancestors' predicate, + ;; which avoids calling `org-ql-select' recursively and its associated overhead. + (org-with-wide-buffer + (org-narrow-to-subtree) + (when (org-goto-first-child) + (narrow-to-region (point) (point-max)) + (catch 'found + (org-ql-select (current-buffer) + query + :narrow t + :action (lambda () + (throw 'found t))))))) + ;;;;;; Timestamps ;; TODO: Remove the _on vars from these arg lists. I think they're not From 0e1196fd6f6bb35d9027578590e3986461beb3a9 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 22:53:57 -0600 Subject: [PATCH 05/10] Tidy/Docs/Comment: Ancestor/descendant predicates --- README.org | 23 +++++++++++++++-------- org-ql.el | 16 ++++++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/README.org b/README.org index 23f672f..dd58a61 100644 --- a/README.org +++ b/README.org @@ -143,10 +143,14 @@ Show a sparse tree for ~QUERY~ in ~BUFFER~ and return number of results. The tr ~QUERY~ is an ~org-ql~ query sexp (quoted, since this is a function). ~BUFFER~ defaults to the current buffer. When ~KEEP-PREVIOUS~ is non-nil (interactively, with prefix), the outline is not reset to the overview state before finding matches, which allows stacking calls to this command. Runs ~org-occur-hook~ after making the sparse tree. ** Queries +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: :CONTENTS: -- [[#non-sexp-query-syntax][Non-sexp query syntax]] -- [[#predicates][Predicates]] -- [[#datetime-predicates][Date/time predicates]] +- [[#non-sexp-query-syntax][Non-sexp query syntax]] +- [[#general-predicates][General predicates]] +- [[#ancestordescendant-predicates][Ancestor/descendant predicates]] +- [[#datetime-predicates][Date/time predicates]] :END: An =org-ql= query is a lisp form which may contain arbitrary lisp forms, as well as certain built-in predicates. It is byte-compiled into a predicate function which is tested with point on each heading in an Org buffer; when it returns non-nil, the heading matches the query. @@ -175,15 +179,11 @@ The command =org-ql-search= also accepts, and the command =helm-org-ql= only acc Note that the =priority= predicate does not support comparators in the non-sexp syntax, so multiple priorities should be passed instead, as seen in the last example. -*** Predicates +*** General predicates 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). -+ =children (&optional query)= :: Return non-nil if current heading has direct child headings. If ~QUERY~, test it against child headings. This selector may be nested, e.g. to match grandchild headings. -+ =parent (&optional query)= :: Return non-nil if current heading has a direct parent heading. If ~QUERY~, test it against the parent heading. This selector may be nested, e.g. to match grandparent headings. -+ =descendants (&optional query)= :: Return non-nil if current heading has descendant headings. If ~QUERY~, test it against descendant headings. This selector may be nested (if you can grok the nesting!). -+ =ancestors (&optional query)= :: Return non-nil if current heading has ancestor headings (which is true if it has a parent heading). With ~QUERY~, return non-nil if some ancestor heading matches it. This selector may also be nested. + =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). @@ -208,6 +208,13 @@ Arguments are listed next to predicate names, where applicable. - Aliases: ~tags&~. + =todo (&optional keywords)= :: Return non-nil if current heading is a ~TODO~ item. With ~KEYWORDS~, return non-nil if its keyword is one of ~KEYWORDS~ (a list of strings). When called without arguments, only matches non-done tasks (i.e. does not match keywords in ~org-done-keywords~). +*** Ancestor/descendant predicates + ++ =ancestors (&optional query)= :: Return non-nil if current heading has ancestor headings. If ~QUERY~, return non-nil if an ancestor heading matches it. This selector may be nested. ++ =children (&optional query)= :: Return non-nil if current heading has direct child headings. If ~QUERY~, return non-nil if a child heading matches it. This selector may be nested, e.g. to match grandchild headings. ++ =descendants (&optional query)= :: Return non-nil if current heading has descendant headings. If ~QUERY~, return non-nil if a descendant heading matches it. This selector may be nested (if you can grok the nesting!). ++ =parent (&optional query)= :: Return non-nil if current heading has a direct parent heading. If ~QUERY~, return non-nil if the parent heading matches it. This selector may be nested, e.g. to match grandparent headings. + *** Date/time predicates All of these predicates take optional keyword arguments ~:from~, ~:to:~, and ~:on~: diff --git a/org-ql.el b/org-ql.el index 90f752f..279d747 100644 --- a/org-ql.el +++ b/org-ql.el @@ -1169,18 +1169,16 @@ language." ;; Note that the implementations of the upward-searching, ancestor/parent predicates differ ;; from that of the downward-searching, descendants/children predicates in that the former ;; take a predicate function as their argument and test it on each heading (the predicate -;; being created by the `--query-pre-process' function, which see), while the latter take an +;; being created by the `--pre-process-query' function, which see), while the latter take an ;; `org-ql' query form as their argument and execute another `org-ql-select' query inside of ;; the currently running query. This "split" implementation seems like the most generally ;; efficient one, because searching descendants searches potentially many more headings than ;; searching ancestors, so executing a full query in that case can be faster due to use of ;; the "preambles" provided by running a full query. However, see note below. -(org-ql--defpred parent (predicate) - "Return non-nil if the current entry's parent satisfies PREDICATE." - (org-with-wide-buffer - (when (org-up-heading-safe) - (org-ql--value-at (point) predicate)))) +;; NOTE: The ancestors and parent predicates' docstrings are developer-facing +;; rather than user-facing, since their arguments are predicates provided +;; automatically by `--pre-process-query'. (org-ql--defpred ancestors (predicate) "Return non-nil if any of current entry's ancestors satisfy PREDICATE." @@ -1188,6 +1186,12 @@ language." (cl-loop while (org-up-heading-safe) thereis (org-ql--value-at (point) predicate)))) +(org-ql--defpred parent (predicate) + "Return non-nil if the current entry's parent satisfies PREDICATE." + (org-with-wide-buffer + (when (org-up-heading-safe) + (org-ql--value-at (point) predicate)))) + ;; MAYBE: The `children' and `descendants' predicates could probably be rewritten like ;; the `ancestors' predicate, which avoids calling `org-ql-select' recursively and its ;; associated overhead. However, that would preclude the use of preambles, so depending From 18c6787244604064ed7c0bbd795c013401225e54 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 22:56:04 -0600 Subject: [PATCH 06/10] Tidy: (org-ql--pre-process-query) Calling of --query-predicate --- org-ql.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org-ql.el b/org-ql.el index 279d747..70e7ea5 100644 --- a/org-ql.el +++ b/org-ql.el @@ -549,9 +549,9 @@ Replaces bare strings with (regexp) selectors, and appropriate (`(descendants ,query) `(descendants ',query)) (`(descendants) '(descendants (lambda () t))) (`(parent ,query) `(parent ,(org-ql--query-predicate (rec query)))) - (`(parent) `(parent ,(org-ql--query-predicate (rec '(lambda () t))))) + (`(parent) '(parent (lambda () t))) (`(ancestors ,query) `(ancestors ,(org-ql--query-predicate (rec query)))) - (`(ancestors) `(ancestors ,(org-ql--query-predicate (rec '(lambda () t))))) + (`(ancestors) '(ancestors (lambda () t))) ;; Timestamp-based predicates. I think this is the way that makes the most sense: ;; set the limit to N days in the future, adjusted to 23:59:59 (since Org doesn't ;; support timestamps down to the second, anyway, there should be no need to adjust From 22cacbd8fe46e549400c3164533cc4648f60dea8 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 22:59:13 -0600 Subject: [PATCH 07/10] Docs: Add credit --- README.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.org b/README.org index dd58a61..46ff63a 100644 --- a/README.org +++ b/README.org @@ -404,7 +404,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience - Negation of terms in plain queries using ~!~. For example, ~tags:space !moon~ to exclude entries which contain ~moon~. - Predicates =outline-path= (alias =olp=) and =outline-path-segment= (alias =olps=). - Predicate ~src~, which matches Org Babel source blocks. - - Predicates =parent= and =ancestors=. + - Predicates =parent= and =ancestors=. (Thanks to [[https://github.com/mm--][Josh Moller-Mara]].) - Alias =h= for =heading= predicate. - Alias =r= for =regexp= predicate. (Thanks to [[https://github.com/tumashu][Feng Shu]].) + Info manual. From 8f2baa10a3573b1f487dbc176dc68adcd88d370d Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 23:02:10 -0600 Subject: [PATCH 08/10] Docs: Fix ToC, info file --- README.org | 13 ++++-- org-ql.info | 123 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 82 insertions(+), 54 deletions(-) diff --git a/README.org b/README.org index 46ff63a..0103630 100644 --- a/README.org +++ b/README.org @@ -60,10 +60,17 @@ Installing with [[https://framagit.org/steckerhalter/quelpa][Quelpa]] is easy: #+END_SRC * Usage +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: :CONTENTS: -- [[#commands][Commands]] -- [[#queries][Queries]] -- [[#functions--macros][Functions / Macros]] +- [[#commands][Commands]] +- [[#queries][Queries]] + - [[#non-sexp-query-syntax][Non-sexp query syntax]] + - [[#general-predicates][General predicates]] + - [[#ancestordescendant-predicates][Ancestor/descendant predicates]] + - [[#datetime-predicates][Date/time predicates]] +- [[#functions--macros][Functions / Macros]] :END: # These links work on GitHub's Org renderer but not in Org. diff --git a/org-ql.info b/org-ql.info index 5184668..08b38c5 100644 --- a/org-ql.info +++ b/org-ql.info @@ -50,7 +50,8 @@ Commands Queries * Non-sexp query syntax:: -* Predicates:: +* General predicates:: +* Ancestor/descendant predicates:: * Date/time predicates:: @@ -146,7 +147,8 @@ File: README.info, Node: Usage, Next: Changelog, Prev: Installation, Up: Top 4 Usage ******* - • • • + • • + • • • • • Feedback on these APIs is welcome. Eventually, after being tested and polished, they will be considered stable. @@ -294,7 +296,7 @@ File: README.info, Node: Queries, Next: Functions / Macros, Prev: Commands, 4.2 Queries =========== - • • • + • • • • An org-ql query is a lisp form which may contain arbitrary lisp forms, as well as certain built-in predicates. It is byte-compiled into a predicate function which is tested with point on each heading in an @@ -311,11 +313,12 @@ Org buffer; when it returns non-nil, the heading matches the query. * Menu: * Non-sexp query syntax:: -* Predicates:: +* General predicates:: +* Ancestor/descendant predicates:: * Date/time predicates::  -File: README.info, Node: Non-sexp query syntax, Next: Predicates, Up: Queries +File: README.info, Node: Non-sexp query syntax, Next: General predicates, Up: Queries 4.2.1 Non-sexp query syntax --------------------------- @@ -345,24 +348,16 @@ non-sexp syntax, so multiple priorities should be passed instead, as seen in the last example.  -File: README.info, Node: Predicates, Next: Date/time predicates, Prev: Non-sexp query syntax, Up: Queries +File: README.info, Node: General predicates, Next: Ancestor/descendant predicates, Prev: Non-sexp query syntax, Up: Queries -4.2.2 Predicates ----------------- +4.2.2 General predicates +------------------------ 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). -‘children (&optional query)’ - Return non-nil if current heading has direct child headings. If - ‘QUERY’, test it against child headings. This selector may be - nested, e.g. to match grandchild headings. -‘descendants (&optional query)’ - Return non-nil if current heading has descendant headings. If - ‘QUERY’, test it against descendant headings. This selector may be - nested (if you can grok the nesting!). ‘done’ Return non-nil if entry’s ‘TODO’ keyword is in ‘org-done-keywords’. ‘habit’ @@ -444,9 +439,32 @@ Arguments are listed next to predicate names, where applicable. ‘org-done-keywords’).  -File: README.info, Node: Date/time predicates, Prev: Predicates, Up: Queries +File: README.info, Node: Ancestor/descendant predicates, Next: Date/time predicates, Prev: General predicates, Up: Queries -4.2.3 Date/time predicates +4.2.3 Ancestor/descendant predicates +------------------------------------ + +‘ancestors (&optional query)’ + Return non-nil if current heading has ancestor headings. If + ‘QUERY’, return non-nil if an ancestor heading matches it. This + selector may be nested. +‘children (&optional query)’ + Return non-nil if current heading has direct child headings. If + ‘QUERY’, return non-nil if a child heading matches it. This + selector may be nested, e.g. to match grandchild headings. +‘descendants (&optional query)’ + Return non-nil if current heading has descendant headings. If + ‘QUERY’, return non-nil if a descendant heading matches it. This + selector may be nested (if you can grok the nesting!). +‘parent (&optional query)’ + Return non-nil if current heading has a direct parent heading. If + ‘QUERY’, return non-nil if the parent heading matches it. This + selector may be nested, e.g. to match grandparent headings. + + +File: README.info, Node: Date/time predicates, Prev: Ancestor/descendant predicates, Up: Queries + +4.2.4 Date/time predicates -------------------------- All of these predicates take optional keyword arguments ‘:from’, ‘:to:’, @@ -722,6 +740,8 @@ will be pushed to the master branch when ready. • Predicates outline-path (alias olp) and outline-path-segment (alias olps). • Predicate ‘src’, which matches Org Babel source blocks. + • Predicates parent and ancestors. (Thanks to Josh Moller-Mara + (https://github.com/mm--).) • Alias h for heading predicate. • Alias r for regexp predicate. (Thanks to Feng Shu (https://github.com/tumashu).) @@ -1037,39 +1057,40 @@ GPLv3  Tag Table: Node: Top225 -Node: Contents1367 -Node: Screenshots1541 -Node: Installation1659 -Node: Quelpa2297 -Node: Usage2740 -Node: Commands3064 -Node: org-ql-search3537 -Node: helm-org-ql5185 -Node: org-ql-view5597 -Node: org-ql-view-sidebar5795 -Node: org-ql-view-recent-items6151 -Node: org-ql-sparse-tree6635 -Node: Queries7435 -Node: Non-sexp query syntax8296 -Node: Predicates9995 -Node: Date/time predicates15217 -Node: Functions / Macros17852 -Node: Agenda-like views18085 -Node: Listing / acting-on results19490 -Node: Changelog24092 -Node: 04-pre24629 -Node: 03228251 -Node: 03128632 -Node: 0328827 -Node: 02331800 -Node: 02232026 -Node: 02132292 -Node: 0232489 -Node: 0136522 -Node: Notes36621 -Node: Comparison with Org Agenda searches36783 -Node: org-sidebar37654 -Node: License37933 +Node: Contents1410 +Node: Screenshots1584 +Node: Installation1702 +Node: Quelpa2340 +Node: Usage2783 +Node: Commands3132 +Node: org-ql-search3605 +Node: helm-org-ql5253 +Node: org-ql-view5665 +Node: org-ql-view-sidebar5863 +Node: org-ql-view-recent-items6219 +Node: org-ql-sparse-tree6703 +Node: Queries7503 +Node: Non-sexp query syntax8411 +Node: General predicates10118 +Node: Ancestor/descendant predicates14925 +Node: Date/time predicates16053 +Node: Functions / Macros18708 +Node: Agenda-like views18941 +Node: Listing / acting-on results20346 +Node: Changelog24948 +Node: 04-pre25485 +Node: 03229219 +Node: 03129600 +Node: 0329795 +Node: 02332768 +Node: 02232994 +Node: 02133260 +Node: 0233457 +Node: 0137490 +Node: Notes37589 +Node: Comparison with Org Agenda searches37751 +Node: org-sidebar38622 +Node: License38901  End Tag Table From 66f58b97f266b8e149418f72e6cd5513aff26fcd Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 23:12:11 -0600 Subject: [PATCH 09/10] Docs: Add examples, update ToC --- examples.org | 83 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/examples.org b/examples.org index af84e17..4167330 100644 --- a/examples.org +++ b/examples.org @@ -2,17 +2,20 @@ * Contents :PROPERTIES: -:TOC: this +:TOC: :include siblings :ignore this +:END: +:CONTENTS: +- [[#agenda-like-view][Agenda-like view]] +- [[#entries-from-the-past-week][Entries from the past week]] +- [[#find-entries-matching-a-certain-custom_id][Find entries matching a certain CUSTOM_ID]] +- [[#listing-bills-coming-due][Listing bills coming due]] +- [[#music-database][Music database]] +- [[#return-org-elements][Return Org elements]] +- [[#set-tags-on-certain-entries][Set tags on certain entries]] +- [[#show-entries-with-recent-timestamps][Show entries with recent timestamps]] +- [[#stuck-projects-block-agenda][Stuck projects block agenda]] +- [[#subproject-and-subtask-queries][Subproject and subtask queries]] :END: - - [[#agenda-like-view][Agenda-like view]] - - [[#entries-from-the-past-week][Entries from the past week]] - - [[#find-entries-matching-a-certain-custom_id][Find entries matching a certain CUSTOM_ID]] - - [[#listing-bills-coming-due][Listing bills coming due]] - - [[#music-database][Music database]] - - [[#return-org-elements][Return Org elements]] - - [[#set-tags-on-certain-entries][Set tags on certain entries]] - - [[#show-entries-with-recent-timestamps][Show entries with recent timestamps]] - - [[#stuck-projects-block-agenda][Stuck projects block agenda]] * Agenda-like view @@ -173,9 +176,67 @@ With this =org-ql-block= agenda view, like: ((org-ql-block-header "Stuck Projects"))))))) #+END_SRC +* Subproject and subtask queries + +#+BEGIN_SRC elisp + ;; Search for subprojects. + (org-ql-search (org-agenda-files) + '(and (todo "PROJECT") + (ancestors (todo "PROJECT")))) + + ;; Search for all subtasks of projects, grouped by parent heading. + (org-ql-search (org-agenda-files) + '(and (todo) + (ancestors (todo "PROJECT"))) + :super-groups '((:auto-parent t))) + + ;; Search for direct top-level tasks of projects. + (org-ql-search (org-agenda-files) + '(and (todo) + (parent (todo "PROJECT"))) + :super-groups '((:auto-parent t))) +#+END_SRC + +Of course, all of those presume using a =PROJECT= keyword to define projects. If one defines a project as any task which has an ancestor task, one could use queries like: + +#+BEGIN_SRC elisp + ;; Search for all subtasks of top-level projects, grouped by parent heading. + (org-ql-search (org-agenda-files) + '(and (todo) + (ancestors + (and (todo) + (not (parent))))) + :super-groups '((:auto-parent t))) + + ;; Search for all subtasks of all projects, including subprojects, grouped by project. + (org-ql-search (org-agenda-files) + '(and (todo) + (ancestors (todo))) + :super-groups '((:auto-parent t))) +#+END_SRC + +Other interesting queries: + +#+BEGIN_SRC elisp + ;; Subtasks of upcoming deadline items. + (org-ql-search (org-agenda-files) + '(and (todo) + (ancestors + (and (not (done)) + (deadline auto)))) + :super-groups '((:auto-parent t))) + + ;; TODO items whose ancestor is already DONE, and should therefore be + ;; either marked DONE or CANCELLED. + (org-ql-search (org-agenda-files) + '(and (todo) + (ancestors (done))) + :super-groups '((:auto-parent t))) +#+END_SRC + * COMMENT Code :noexport: :PROPERTIES: -:TOC: ignore +:TOC: :ignore (this descendants) :END: ** File-local variables From 843564ad8c6f381e190d93ff9502b94f068bee95 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Wed, 22 Jan 2020 23:15:00 -0600 Subject: [PATCH 10/10] Add: (org-ql-views) Dangling tasks view --- README.org | 1 + org-ql-view.el | 9 +++++++++ org-ql.info | 27 +++++++++++++++------------ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/README.org b/README.org index 0103630..dadb623 100644 --- a/README.org +++ b/README.org @@ -420,6 +420,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience + Respect Org Agenda restriction in =org-ql-block=. (Thanks to [[https://github.com/yantar92][Ihor Radchenko]] for reporting.) + Option =org-ql-view-sidebar-sort-views=. + Mouseover =help-echo= text for =org-ql-views= default view names. ++ "Dangling tasks" default view in =org-ql-views=. (Users who have modified =org-ql-views= from the default will not see the new view unless they copy it into their config.) *Changed* + Some default =org-ql-view= views (users who have modified =org-ql-views= from the default will not see the new views unless they copy them into their config): diff --git a/org-ql-view.el b/org-ql-view.el index 4ce6a37..f141270 100644 --- a/org-ql-view.el +++ b/org-ql-view.el @@ -177,6 +177,15 @@ See info node `(elisp)Cyclic Window Ordering'." :super-groups 'org-super-agenda-groups :sort '(priority))))) (cons "Review: Recently timestamped" #'org-ql-view-recent-items) + (cons (propertize "Review: Dangling tasks" + 'help-echo "Tasks whose ancestor is done") + (list :buffers-files #'org-agenda-files + :query '(and (todo) + (ancestors (done))) + :title (propertize "Review: Dangling tasks" + 'help-echo "Tasks whose ancestor is done") + :sort '(date priority todo) + :super-groups '((:auto-parent t)))) (cons (propertize "Review: Stale tasks" 'help-echo "Tasks without a timestamp in the past 2 weeks") (list :buffers-files #'org-agenda-files diff --git a/org-ql.info b/org-ql.info index 08b38c5..78c67b3 100644 --- a/org-ql.info +++ b/org-ql.info @@ -755,6 +755,9 @@ will be pushed to the master branch when ready. Radchenko (https://github.com/yantar92) for reporting.) • Option org-ql-view-sidebar-sort-views. • Mouseover help-echo text for org-ql-views default view names. + • "Dangling tasks" default view in org-ql-views. (Users who have + modified org-ql-views from the default will not see the new view + unless they copy it into their config.) *Changed* • Some default org-ql-view views (users who have modified @@ -1079,18 +1082,18 @@ Node: Agenda-like views18941 Node: Listing / acting-on results20346 Node: Changelog24948 Node: 04-pre25485 -Node: 03229219 -Node: 03129600 -Node: 0329795 -Node: 02332768 -Node: 02232994 -Node: 02133260 -Node: 0233457 -Node: 0137490 -Node: Notes37589 -Node: Comparison with Org Agenda searches37751 -Node: org-sidebar38622 -Node: License38901 +Node: 03229405 +Node: 03129786 +Node: 0329981 +Node: 02332954 +Node: 02233180 +Node: 02133446 +Node: 0233643 +Node: 0137676 +Node: Notes37775 +Node: Comparison with Org Agenda searches37937 +Node: org-sidebar38808 +Node: License39087  End Tag Table