branch: externals/org-edna commit 8258a4dfa00aa522249cdf9aeea5be4de97bd7c1 Author: Ian Dunn <i...@keyspoke.net> Commit: Ian Dunn <i...@keyspoke.net>
Synced with upstream --- org-edna.el | 118 ++++++++++++++++-------- org-edna.info | 282 ++++++++++++++++++++++++++++++++++++++++------------------ org-edna.org | 78 ++++++++++++++++ 3 files changed, 357 insertions(+), 121 deletions(-) diff --git a/org-edna.el b/org-edna.el index f743c96..fac34ef 100644 --- a/org-edna.el +++ b/org-edna.el @@ -7,7 +7,7 @@ ;; Keywords: convenience, text, org ;; URL: https://savannah.nongnu.org/projects/org-edna-el/ ;; Package-Requires: ((emacs "25.1") (seq "2.19") (org "9.0.5")) -;; Version: 1.1.1 +;; Version: 1.1.2 ;; This file is part of GNU Emacs. @@ -88,6 +88,20 @@ it will be used. It should be either \"short\" or :type '(choice (const :tag "Short Format" short) (const :tag "Long Format" long))) +(defcustom org-edna-from-todo-states 'todo + "Category of TODO states that allow Edna to run. + +This is one of the following options: + +If `todo', Edna will run when changing TODO state from an entry +in `org-not-done-keywords'. + +If `not-done', Edna will run when changing TODO state from any +entry that's not in `org-done-keywords'. This includes TODO +state being empty." + :type '(choice (const :tag "TODO Keywords" todo) + (const :tag "Not DONE Keywords" not-done))) + ;;; Form Parsing ;; 3 types of "forms" here @@ -113,6 +127,14 @@ ERROR-POS is the positiong in MSG at which the error occurred." "Org Edna Syntax Error: %s\n%s\n%s" msg form (concat (make-string pos ?\ ) "^")))) +(defun org-edna--id-pred-p (arg) + "Return non-nil if ARG matches id:UUID. + +UUID is any UUID recognized by `org-uuidgen-p'." + (save-match-data + (when (string-match "^id:\\(.*\\)" (symbol-name arg)) + (org-uuidgen-p (match-string 1 (symbol-name arg)))))) + (defun org-edna--transform-arg (arg) "Transform argument ARG. @@ -126,6 +148,10 @@ Everything else is returned as is." ;; Name matches `org-uuidgen-p' (let (pred org-uuidgen-p) (symbol-name arg))) (symbol-name arg)) + ((and (pred symbolp) ;; Symbol + ;; Name matches `org-uuidgen-p' + (pred org-edna--id-pred-p)) + (symbol-name arg)) (_ arg))) @@ -631,34 +657,40 @@ this after reverting Org mode buffers." ;;; Interactive Functions -(defmacro org-edna-run (change-plist &rest body) - "Run a TODO state change. +(defun org-enda--should-run-in-from-state-p (from) + (pcase org-edna-from-todo-states + ('todo + (member from (cons 'todo org-not-done-keywords))) + ('not-done + (not (member from (cons 'done org-done-keywords)))))) + +(defun org-edna--should-run-p (change-plist) + "Check if Edna should run. The state information is held in CHANGE-PLIST. If the TODO state is changing from a TODO state to a DONE state, run BODY." - (declare (indent 1)) - `(let* ((pos (plist-get ,change-plist :position)) - (type (plist-get ,change-plist :type)) - (from (plist-get ,change-plist :from)) - (to (plist-get ,change-plist :to))) - (if (and - ;; We are only handling todo-state-change - (eq type 'todo-state-change) - ;; And only from a TODO state to a DONE state - (member from (cons 'todo org-not-done-keywords)) - (member to (cons 'done org-done-keywords))) - (condition-case-unless-debug err - ,@body - (error - (if (eq (car err) 'invalid-read-syntax) - (org-edna--print-syntax-error (cdr err)) - (message "Edna Error at heading %s: %s" (org-get-heading t t t) (error-message-string err))) - (setq org-block-entry-blocking (org-get-heading)) - ;; Block - nil)) - ;; Return t for the blocker to let the calling function know that there - ;; is no block here. - t))) + (let* ((type (plist-get change-plist :type)) + (from (plist-get change-plist :from)) + (to (plist-get change-plist :to))) + (and + ;; We are only handling todo-state-change + (eq type 'todo-state-change) + ;; And only from a TODO state to a DONE state + (org-enda--should-run-in-from-state-p from) + (member to (cons 'done org-done-keywords))))) + +(defmacro org-edna-run (&rest body) + "Run a TODO state change." + (declare (indent 0)) + `(condition-case-unless-debug err + ,@body + (error + (if (eq (car err) 'invalid-read-syntax) + (org-edna--print-syntax-error (cdr err)) + (message "Edna Error at heading %s: %s" (org-get-heading t t t) (error-message-string err))) + (setq org-block-entry-blocking (org-get-heading)) + ;; Block + nil))) (defun org-edna-trigger-function (change-plist) "Trigger function work-horse. @@ -666,9 +698,11 @@ is changing from a TODO state to a DONE state, run BODY." See `org-edna-run' for CHANGE-PLIST explanation. This shouldn't be run from outside of `org-trigger-hook'." - (org-edna-run change-plist - (when-let* ((form (org-entry-get pos "TRIGGER" org-edna-use-inheritance))) - (org-edna-process-form form 'action)))) + (when (org-edna--should-run-p change-plist) + (org-edna-run + (when-let* ((form (org-entry-get (plist-get change-plist :position) + "TRIGGER" org-edna-use-inheritance))) + (org-edna-process-form form 'action))))) (defun org-edna-blocker-function (change-plist) "Blocker function work-horse. @@ -676,11 +710,16 @@ This shouldn't be run from outside of `org-trigger-hook'." See `org-edna-run' for CHANGE-PLIST explanation. This shouldn't be run from outside of `org-blocker-hook'." - (org-edna-run change-plist - (if-let* ((form (org-entry-get pos "BLOCKER" org-edna-use-inheritance))) - ;; Return nil if there is no blocking entry - (not (setq org-block-entry-blocking (org-edna-process-form form 'condition))) - t))) + (if (org-edna--should-run-p change-plist) + (org-edna-run + (if-let* ((form (org-entry-get (plist-get change-plist :position) + "BLOCKER" org-edna-use-inheritance))) + ;; Return nil if there is no blocking entry + (not (setq org-block-entry-blocking (org-edna-process-form form 'condition))) + t)) + ;; Return t for the blocker to let the calling function know that there + ;; is no block here. + t)) ;;;###autoload (defun org-edna--load () @@ -755,10 +794,17 @@ honoring any restriction (the equivalent of the nil SCOPE in Edna Syntax: ids(ID1 ID2 ...) -Each ID is a UUID as understood by `org-id-find'. +Each ID is a UUID as understood by `org-id-find'. Alternatively, +ID may also be id:UUID, where UUID is a UUID as understood by +`org-id-find'. Note that in the edna syntax, the IDs don't need to be quoted." - (mapcar (lambda (id) (org-id-find id 'marker)) ids)) + (mapcar + (lambda (id) + (if (string-prefix-p "id:" id) + (org-id-find (string-remove-prefix "id:" id) 'marker) + (org-id-find id 'marker))) + ids)) (defun org-edna-finder/self () "Finder for the current heading. diff --git a/org-edna.info b/org-edna.info index 0bd114c..7e2eb71 100644 --- a/org-edna.info +++ b/org-edna.info @@ -81,6 +81,7 @@ Advanced Features * Conditions:: More than just DONE headings * Consideration:: Only some of them * Conditional Forms:: If/Then/Else +* What Constitutes ``Not Done''?:: Changing from which states Edna will activate * Setting the Properties:: The easy way to set BLOCKER and TRIGGER Conditions @@ -116,6 +117,9 @@ Contributing Changelog +* 1.1.2: 112. +* 1.1.1: 111. +* 1.1.0: 110. * 1.0.2: 102. * 1.0.1: 101. * 1.0: 10. @@ -134,7 +138,7 @@ File: org-edna.info, Node: Copying, Next: Introduction, Prev: Top, Up: Top Copying ******* -Copyright (C) 2017-2018 Free Software Foundation, Inc. +Copyright (C) 2017-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -488,6 +492,9 @@ ids Note that UUIDs need not be quoted; Edna will handle that for you. + The IDs may also be prefixed with ‘id:’, allowing the links to the +headings themselves to be used. + File: org-edna.info, Node: match, Next: next-sibling, Prev: ids, Up: Finders @@ -1168,6 +1175,7 @@ Advanced Features * Conditions:: More than just DONE headings * Consideration:: Only some of them * Conditional Forms:: If/Then/Else +* What Constitutes ``Not Done''?:: Changing from which states Edna will activate * Setting the Properties:: The easy way to set BLOCKER and TRIGGER @@ -1490,7 +1498,7 @@ applies. mean the same thing. -File: org-edna.info, Node: Conditional Forms, Next: Setting the Properties, Prev: Consideration, Up: Advanced Features +File: org-edna.info, Node: Conditional Forms, Next: What Constitutes ``Not Done''?, Prev: Consideration, Up: Advanced Features Conditional Forms ================= @@ -1559,8 +1567,75 @@ the condition. The conditional block tells it to evaluate that section. Thus, you can conditionally add targets, or conditionally check conditions. + Another common use case is to check for a property: + + * TODO My Task + :PROPERTIES: + :TRIGGER: if self has-property?("REPEAT" "2") then self set-property!("REPEAT" inc) todo!("TODO") endif + :REPEAT: 0 + :END: + + When “My Task” is set to DONE, REPEAT will be incremented, then the +task will be set back to TODO. This happens until REPEAT is 2. Once +that happens, the task will be left alone, staying in the DONE state. + + Be warned that trying the above example with a repeater will not +work. In order for that to work, Edna must take over the repeater: + + * TODO My Task + SCHEDULED: <2020-09-02 Wed> + :PROPERTIES: + :TRIGGER: if self has-property?("REPEAT" "2") then self set-property!("REPEAT" inc) scheduled!("+1d") todo!("TODO") endif + :REPEAT: 0 + :END: + + This example will increment the SCHEDULED time by one day every time +the task is marked DONE. + -File: org-edna.info, Node: Setting the Properties, Prev: Conditional Forms, Up: Advanced Features +File: org-edna.info, Node: What Constitutes ``Not Done''?, Next: Setting the Properties, Prev: Conditional Forms, Up: Advanced Features + +What Constitutes “Not Done”? +============================ + +Sometimes, you have a heading like this: + + * My Task + :PROPERTIES: + :TRIGGER: self set-property!("TEST" inc) + :END: + + You want to mark it as DONE because that’s how you do things. Will +Edna run when you do that? + + The answer is it can. There is user variable called +‘org-edna-from-todo-states’ that controls the categories of TODO states +from which changing a heading will cause Edna to run. This has two +possible values: + +todo + Edna will only run if the old TODO state is in + ‘org-not-done-keywords’. This is the default. +not-done + Edna will run if the old TODO state is *not* in + ‘org-done-keywords’. This includes no state at all. + + Further, using *note conditional forms: Conditional Forms, it is +possible to take different actions depending on the new TODO state. For +example: + + * My Task + :PROPERTIES: + :TRIGGER: if self todo-state?("COMPLETE") then self set-property!("TEST" inc) endif + :TEST: 0 + :END: + + This will only increment the TEST property if the new TODO state is +*not* “COMPLETE”. Currently, there is no way to take different actions +depending on the *old* TODO state. + + +File: org-edna.info, Node: Setting the Properties, Prev: What Constitutes ``Not Done''?, Up: Advanced Features Setting the Properties ====================== @@ -1876,6 +1951,9 @@ Changelog * Menu: +* 1.1.2: 112. +* 1.1.1: 111. +* 1.1.0: 110. * 1.0.2: 102. * 1.0.1: 101. * 1.0: 10. @@ -1888,7 +1966,37 @@ Changelog * 1.0beta2: 10beta2. -File: org-edna.info, Node: 102, Next: 101, Up: Changelog +File: org-edna.info, Node: 112, Next: 111, Up: Changelog + +1.1.2 +===== + + • Allow ‘id:’ syntax in ‘ids’ finder + • Added setting ‘org-edna-from-todo-states’ + • See *note What Constitutes “Not Done”: What Constitutes ``Not + Done''?. for more + + +File: org-edna.info, Node: 111, Next: 110, Prev: 112, Up: Changelog + +1.1.1 +===== + + • Marked ‘org-edna-load’ and ‘org-edna-unload’ as deprecated + • Renamed to ‘org-edna--load’ and ‘org-edna--unload’ to reflect + internal use only intention + + +File: org-edna.info, Node: 110, Next: 102, Prev: 111, Up: Changelog + +1.1.0 +===== + + • Added ‘org-edna-mode’ as a minor mode, as opposed to + ‘org-edna-load’ and ‘org-edna-unload’ + + +File: org-edna.info, Node: 102, Next: 101, Prev: 110, Up: Changelog 1.0.2 ===== @@ -2038,87 +2146,91 @@ Big release here, with three new features. Tag Table: Node: Top225 -Node: Copying4483 -Node: Introduction5306 -Node: Installation and Setup6254 -Node: Basic Operation7060 -Node: Blockers8911 -Node: Triggers9198 -Node: Syntax9460 -Node: Basic Features10150 -Node: Finders10504 -Node: ancestors12269 -Node: children12863 -Node: descendants13273 -Node: file13795 -Node: first-child14544 -Node: ids14804 -Node: match15465 -Node: next-sibling16103 -Node: next-sibling-wrap16360 -Node: olp16674 -Node: org-file17087 -Node: parent17732 -Node: previous-sibling17930 -Node: previous-sibling-wrap18191 -Node: relatives18470 -Node: rest-of-siblings22196 -Node: rest-of-siblings-wrap22481 -Node: self22830 -Node: siblings22991 -Node: siblings-wrap23228 -Node: Actions23532 -Node: Scheduled/Deadline24295 -Node: Timestamp Format27883 -Node: TODO State28771 -Node: Archive29496 -Node: Chain Property29816 -Node: Clocking30569 -Node: Property30981 -Node: Priority33154 -Node: Tag33723 -Node: Effort33940 -Node: Getting Help34324 -Node: Advanced Features34769 -Node: Finder Cache35217 -Node: Conditions36665 -Node: Heading is DONE37550 -Node: File Has Headings37756 -Node: Heading TODO State38178 -Node: Lisp Variable Set38472 -Node: Heading Has Property39141 -Node: Regexp Search39887 -Node: Checking Tags40330 -Node: Matching Headings41232 -Node: Negating Conditions41829 -Node: Multiple Conditions42252 -Node: Consideration42934 -Node: Conditional Forms45120 -Node: Setting the Properties47809 -Node: Extending Edna48893 -Node: Naming Conventions49383 -Node: Finders (1)50177 -Node: Actions (1)50543 -Node: Conditions (1)51008 -Node: Contributing51898 -Node: Bugs52764 -Node: Working with EDE53121 -Node: Compiling Edna54206 -Node: Testing Edna55075 -Node: Before Sending Changes56057 -Node: Developing with Bazaar56744 -Node: Documentation57485 -Node: Changelog57941 -Node: 10258230 -Node: 10158510 -Node: 1058647 -Node: 10beta859161 -Node: 10beta759284 -Node: 10beta659578 -Node: 10beta559854 -Node: 10beta460241 -Node: 10beta360494 -Node: 10beta260933 +Node: Copying4606 +Node: Introduction5429 +Node: Installation and Setup6377 +Node: Basic Operation7183 +Node: Blockers9034 +Node: Triggers9321 +Node: Syntax9583 +Node: Basic Features10273 +Node: Finders10627 +Node: ancestors12392 +Node: children12986 +Node: descendants13396 +Node: file13918 +Node: first-child14667 +Node: ids14927 +Node: match15695 +Node: next-sibling16333 +Node: next-sibling-wrap16590 +Node: olp16904 +Node: org-file17317 +Node: parent17962 +Node: previous-sibling18160 +Node: previous-sibling-wrap18421 +Node: relatives18700 +Node: rest-of-siblings22426 +Node: rest-of-siblings-wrap22711 +Node: self23060 +Node: siblings23221 +Node: siblings-wrap23458 +Node: Actions23762 +Node: Scheduled/Deadline24525 +Node: Timestamp Format28113 +Node: TODO State29001 +Node: Archive29726 +Node: Chain Property30046 +Node: Clocking30799 +Node: Property31211 +Node: Priority33384 +Node: Tag33953 +Node: Effort34170 +Node: Getting Help34554 +Node: Advanced Features34999 +Node: Finder Cache35528 +Node: Conditions36976 +Node: Heading is DONE37861 +Node: File Has Headings38067 +Node: Heading TODO State38489 +Node: Lisp Variable Set38783 +Node: Heading Has Property39452 +Node: Regexp Search40198 +Node: Checking Tags40641 +Node: Matching Headings41543 +Node: Negating Conditions42140 +Node: Multiple Conditions42563 +Node: Consideration43245 +Node: Conditional Forms45431 +Node: What Constitutes ``Not Done''?49054 +Node: Setting the Properties50452 +Node: Extending Edna51549 +Node: Naming Conventions52039 +Node: Finders (1)52833 +Node: Actions (1)53199 +Node: Conditions (1)53664 +Node: Contributing54554 +Node: Bugs55420 +Node: Working with EDE55777 +Node: Compiling Edna56862 +Node: Testing Edna57731 +Node: Before Sending Changes58713 +Node: Developing with Bazaar59400 +Node: Documentation60141 +Node: Changelog60597 +Node: 11260928 +Node: 11161214 +Node: 11061487 +Node: 10261691 +Node: 10161983 +Node: 1062120 +Node: 10beta862634 +Node: 10beta762757 +Node: 10beta663051 +Node: 10beta563327 +Node: 10beta463714 +Node: 10beta363967 +Node: 10beta264406 End Tag Table diff --git a/org-edna.org b/org-edna.org index eacbd76..408943c 100644 --- a/org-edna.org +++ b/org-edna.org @@ -338,6 +338,9 @@ Here, "Test" will block until the heading with ID Note that UUIDs need not be quoted; Edna will handle that for you. +The IDs may also be prefixed with ~id:~, allowing the links to the headings +themselves to be used. + *** match :PROPERTIES: :CUSTOM_ID: match @@ -1349,6 +1352,77 @@ The second is to switch the then and else clauses: The conditional block tells it to evaluate that section. Thus, you can conditionally add targets, or conditionally check conditions. +Another common use case is to check for a property: + +#+begin_src org +,* TODO My Task + :PROPERTIES: + :TRIGGER: if self has-property?("REPEAT" "2") then self set-property!("REPEAT" inc) todo!("TODO") endif + :REPEAT: 0 + :END: +#+end_src + +When "My Task" is set to DONE, REPEAT will be incremented, then the task will be +set back to TODO. This happens until REPEAT is 2. Once that happens, the task +will be left alone, staying in the DONE state. + +Be warned that trying the above example with a repeater will not work. In order +for that to work, Edna must take over the repeater: + +#+begin_src org +,* TODO My Task + SCHEDULED: <2020-09-02 Wed> + :PROPERTIES: + :TRIGGER: if self has-property?("REPEAT" "2") then self set-property!("REPEAT" inc) scheduled!("+1d") todo!("TODO") endif + :REPEAT: 0 + :END: +#+end_src + +This example will increment the SCHEDULED time by one day every time the task is +marked DONE. + +** What Constitutes "Not Done"? +:PROPERTIES: +:CUSTOM_ID: not_done +:DESCRIPTION: Changing from which states Edna will activate +:END: + +Sometimes, you have a heading like this: + +#+begin_src org +,* My Task + :PROPERTIES: + :TRIGGER: self set-property!("TEST" inc) + :END: +#+end_src + +You want to mark it as DONE because that's how you do things. Will Edna run +when you do that? + +The answer is it can. There is user variable called ~org-edna-from-todo-states~ +that controls the categories of TODO states from which changing a heading will +cause Edna to run. This has two possible values: + +- todo :: Edna will only run if the old TODO state is in + ~org-not-done-keywords~. This is the default. +- not-done :: Edna will run if the old TODO state is *not* in + ~org-done-keywords~. This includes no state at all. + +Further, using [[#conditional_forms][conditional forms]], it is possible to take different actions +depending on the new TODO state. For example: + +#+begin_src org +,* My Task + :PROPERTIES: + :TRIGGER: if self todo-state?("COMPLETE") then self set-property!("TEST" inc) endif + :TEST: 0 + :END: +#+end_src + +This will only increment the TEST property if the new TODO state is *not* +"COMPLETE". Currently, there is no way to take different actions depending on +the *old* TODO state. + ** Setting the Properties :PROPERTIES: :DESCRIPTION: The easy way to set BLOCKER and TRIGGER @@ -1643,6 +1717,10 @@ making any changes: :PROPERTIES: :DESCRIPTION: List of changes by version :END: +** 1.1.2 +- Allow ~id:~ syntax in ~ids~ finder +- Added setting ~org-edna-from-todo-states~ + - See [[#not_done][What Constitutes "Not Done"]] for more ** 1.1.1 - Marked ~org-edna-load~ and ~org-edna-unload~ as deprecated - Renamed to ~org-edna--load~ and ~org-edna--unload~ to reflect internal use only intention