0

I am trying to automatically toggle a 'blocking' tag to a todo entry based on certain conditions. The tag label is #Ordered.

I have two todo keywords EPIC and PHASE that should receive this tag when calling the function. The tag should only be applied if there exists a 'previous sibling' in a 'todo state' in the buffer.

To illustrate, I add a few examples:

=> BEGIN BUFFER

* PHASE Do not tag at BOF
* EPIC This should receive the tag
* TODO This should not receive the tag
* PHASE This should receive the tag

=> END BUFFER

=> BEGIN BUFFER
* DONE This should not receive the tag
* PHASE This should not receive the tag
* EPIC This sould receive the tag
* PHASE This should receive the tag

There is one more edge case I have not even tried to cover yet, and I would be more than happy if only the cases above where working ! I still mention it for the sake of completeness.

* PHASE should not be tagged
* DONE shoud not be tagged
* PHASE this should be tagged in theory

I have tried many different approach, I will just post my latest failure that basically tags everything but DONE tasks. I find it hard to go to previous sibling, and check if we got a todo state without throwing an error if there is no previous sibling.

Please do not mind missing parenthesis, I removed irrelevant parts of the code for more clarity so to speak :)

                    (when-let ((todo (org-entry-get (point) "TODO")))

                        (if (and (= 1 (org-current-level))
                                 (member todo '("PHASE" "EPIC")))
                        (progn

                          (let ((ordered nil))

                            (save-excursion
                              (if (org-get-previous-sibling) 
                              (setq ordered (org-entry-is-todo-p)
                                )))
                            
                            (if ordered
                                (org-toggle-tag "#Ordered"))))
2
  • Is previous sibling a node in the same subtree at the same level as the node you are dealing with? That is my guess, but I just wanted to make absolutely sure.
    – NickD
    Commented Jul 29 at 17:51
  • Thanks for asking. Like I tried to describe, I am only dealing with TODO entries at first level. No sub items or recursive search.
    – lyndhurst
    Commented Jul 29 at 18:25

1 Answer 1

0

Not a complete answer but I think you'll find the following function useful. It is to be called at a level-1 heading and it returns the list of todo states of all the previous headings. You can then check if your criteria are satisfied and, if so, apply the tag.

Here's an org mode file that contains the definition of the function and some code to test it as well:

#+TODO: TODO EPIC PHASE

* Link

https://emacs.stackexchange.com/questions/81825/how-can-i-check-if-a-todo-has-a-previous-todo-sibling

* headings


* PHASE Do not tag at BOF
* EPIC This should receive the tag
* TODO This should not receive the tag
* PHASE This should receive the tag

* Code

#+begin_src elisp :results drawer

  (defun my/org-previous-headings-todo-states ()
    "This can be called with cursor on a particular level-1 heading.
     It will return the list of todo states of all previous level-1 headings."
    (save-excursion
      (save-restriction
        (narrow-to-region (point-min) (org-back-to-heading))
        (org-map-entries (lambda () (org-entry-get nil "TODO")) "LEVEL=1" nil))))
         

#+end_src

#+RESULTS:
:results:
my/org-previous-headings-todo-states
:end:



#+begin_src elisp :results verbatim
  (goto-char 222)
  ;(goto-char 266)
  ;(goto-char 300)
  (my/org-previous-headings-todo-states)
#+end_src

#+RESULTS:
: (nil nil "PHASE" "EPIC")

Hope this helps.


Some explanations: org-map-entries is a very powerful function as you can see above, but I spent an hour in vain trying to apply it to region. I knew that I had to narrow the buffer: that was the easy part. I tried setting the mark and activating the region but that tends to pollute the mark ring and it is generally considered a bad idea (do C-h f push-mark and C-h f set-mark for the reasons); I also tried to copy the narrowed buffer to a temp buffer and run org-map-entries in the temp buffer, using the file scope, but it didn't work (and I'm not quite sure why: I guess that the buffer needs to be backed by a file, but it's just a guess1). I finally noticed the following description in the docstring of the function, when it describes the scope argument:

SCOPE determines the scope of this command.  It can be any of:

nil     The current buffer, respecting the restriction if any
...

which is exactly what I want: that nil scope argument in the org-map-entries call is extremely important (but uncommon: I had never used it before). I wanted to highlight it here for future reference.

Moral of the story: you really need to read the documentation closely :-) I knew that, but I still managed to miss that detail for quite a while...


Footnotes:

[1] BTW, I checked the code and that was a correct guess: file scope requires the buffer to be backed by a file, so a temp buffer won't do.

3
  • 1
    Thank you, it is an interesting approach, it helped me understand the 'narrow-to-region' logic, I hope I will remember it next time I need it. I am still working on the final function, but I had time to test yours with simple member conditionals, and it works well for my use case.
    – lyndhurst
    Commented Jul 30 at 8:16
  • 1
    Yes, indeed: I had the narrow-to-region part down, but I just couldn't figure out for a while how to get org-map-entries to respect the restriction. I went down two wrong paths, before I saw the light. See the additional explanations added to the answer.
    – NickD
    Commented Jul 30 at 13:57
  • Describing the thought process is really useful, and it makes complete beginners like me feel better when going through some awkward error and trials.I have just used this SCOPE part this afternoon to do some simple tag renaming. I didn't get the 'respecting restrictions' part of the documentation, and skipped over it since I actually only needed 'agenda. I understand it a little better now.
    – lyndhurst
    Commented Jul 30 at 18:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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