1
* one 
* two
** books
*** IN-PROGRESS book1 
*** book2 
*** book3
* three
* four
** five
*** books
**** book4

How to get:

  1. all headings title with todo-'IN-PROGRESS' keyword which are children of heading 'books'
2
  • Are you looking for a solution in just one file? Or all your agenda files?
    – NickD
    Dec 27 '20 at 19:06
  • only one file :)
    – slk500
    Dec 27 '20 at 19:40
2

The simplest thing is to use isearch and look for IN-PROGRESS, then eyeball the heading to see if it's in a books hierarchy. Org mode files are plain text, so everything that applies to text applies to them as well.

But assuming you want an Org mode specific method, here's a simple example implementation in the context of a slightly augmented version (adding a TODO list at the top and two more instances to test the matching) of your file:

#+TODO: START IN-PROGRESS | DONE

* one 
* two
** books
*** IN-PROGRESS book1 
*** book2 
*** book3
* three
** IN-PROGRESS book-not-3
* four
** five
*** books
**** book4
**** IN-PROGRESS book5
     
*  Code                                                       :noexport:

  #+begin_src emacs-lisp :results drawer
    (defun ndk/select-heading ()
       (if (member "books" (org-get-outline-path))
          (org-get-heading t t t t)))

    (org-map-entries 'ndk/select-heading "/+TODO=\"IN-PROGRESS\"" 'file)
  #+end_src

Explanation:

  • org-map-entries takes a function and applies it on all entries that match a criterion in a given scope (here, the entries that match the TODO state IN-PROGRESS in the scope of the whole file). The matching is explained in the Matchings Tags and Properties section of the Org mode manual. In this case, we match every entry in the file whose TODO state is IN-PROGRESS and on each one, we apply the function ndk/select-heading.

  • The function ndk/select-heading is called at the beginning of each matching entry. It first gets the breadcrumbs of the entry, i.e. the path of headings leading from the top level to the entry as a list, and then checks whether books is a member of the list, i.e. whether the entry is a sub-entry of a books entry. If so, it returns the current heading, shorn of all extras (TODO state, tags, etc).

If you press C-c C-c on the code block to run it, it will return ("book1" nil "book5"), the first and last entries matching the state and being sub-entries of books, while the nil comes from an entry matching the state but which is not a sub-entry of books (the entry book-not-3 in the file).

Two more things:

  • The match specification has a short-cut form: instead of saying "+TODO=\"FOO\"", you can say /+FOO. But the match specification also allows the form /+FOO-BAR to specify that you want to match FOO but NOT BAR. That precludes your using the short form in your case because /+IN-PROGRESS would mean: match IN but NOT PROGRESS which is not what you want. So you have to use the long form or rename your states so that they don't contain dashes.

  • The search above is tailored to your exact criteria at the cost of hardwiring the values you are looking for into the code. It's not hard to write a more general function that takes these things as arguments, but it does complicate the code a bit, so I'll leave it as an exercise for the reader. If you understand the code above and have a little Lisp under your belt, you should be able to write the generalization easily.

  • It's also easy to get rid of any nil entries in the result if you don't want them: just say (delete nil (org-map-entries ....)).

Note that org-map-entries is a very powerful function: it's a good idea to read its doc string with C-h f org-map-entries and also read the Using the Mapping API section in the manual.

4

Another approach could be using org-ql. Here an example that should match your original question:

(require 'org-ql)

(org-ql-query
  :select #'org-get-heading
  :from "/tmp/so.org"
  :where '(and (todo "IN-PROGRESS")
               (parent "books")))
  • The :select key denotes the function that will be called on each entry matching the predicate in the :where condition
  • /tmp/so.org is the name of the file where I saved the content prepared by @NickD

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.