emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
08372761307b0bccac8b6d92a7d07546399114c5 blob 10341 bytes (raw)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
 
;;; org-depend.el --- TODO dependencies for Org-mode
;; Copyright (C) 2008 Free Software Foundation, Inc.
;;
;; Author: Carsten Dominik <carsten at orgmode dot org>
;; Keywords: outlines, hypermedia, calendar, wp
;; Homepage: http://orgmode.org
;; Version: 0.08
;;
;; This file is not part of GNU Emacs.
;;
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;;
;; WARNING: This file is just a PROOF OF CONCEPT, not a supported part
;;          of Org-mode.
;;
;; This is an example implementation of TODO dependencies in Org-mode.
;; It uses the new hooks in version 5.13 of Org-mode,
;; `org-trigger-hook' and `org-blocker-hook'.
;;
;; It implements the following:
;;
;; Triggering
;; ----------
;;
;; 1) If an entry contains a TRIGGER property that contains the string
;;    "chain-siblings(KEYWORD)", then switching that entry to DONE does
;;    do the following:
;;    - The sibling following this entry switched to todo-state KEYWORD.
;;    - The sibling also gets a TRIGGER property "chain-sibling(KEYWORD)",
;;      property, to make sure that, when *it* is DONE, the chain will
;;      continue.
;;
;; 2) If an entry contains a TRIGGER property that contains the string
;;    "chain-siblings-scheduled", then switching that entry to DONE does
;;    the following actions, similarly to "chain-siblings(KEYWORD)":
;;    - The sibling receives the same scheduled time as the entry
;;      marked as DONE (or, in the case, in which there is no scheduled
;;      time, the sibling does not get any either).
;;    - The sibling also gets the same TRIGGER property
;;      "chain-siblings-scheduled", so the chain can continue.
;;
;; 3) If the TRIGGER property contains any other words like
;;    XYZ(KEYWORD), these are treated as entry id's with keywords.  That
;;    means, Org-mode will search for an entry with the ID property XYZ
;;    and switch that entry to KEYWORD as well.
;;
;; Blocking
;; --------
;;
;; 1) If an entry contains a BLOCKER property that contains the word
;;    "previous-sibling", the sibling above the current entry is
;;    checked when you try to mark it DONE.  If it is still in a TODO
;;    state, the current state change is blocked.
;;
;; 2) If the BLOCKER property contains any other words, these are
;;    treated as entry id's.  That means, Org-mode will search for an
;;    entry with the ID property exactly equal to this word.  If any
;;    of these entries is not yet marked DONE, the current state change
;;    will be blocked.
;;
;; 3) Whenever a state change is blocked, an org-mark is pushed, so that
;;    you can find the offending entry with `C-c &'.
;;
;;; Example:
;;
;; When trying this example, make sure that the settings for TODO keywords
;; have been activated, i.e. include the following line and press C-c C-c
;; on the line before working with the example:
;;
;; #+TYP_TODO: TODO NEXT | DONE
;;
;; * TODO Win a million in Las Vegas
;;   The "third" TODO (see above) cannot become a TODO without this money.
;;
;;   :PROPERTIES:
;;     :ID: I-cannot-do-it-without-money
;;   :END:
;;
;; * Do this by doing a chain of TODO's
;; ** NEXT This is the first in this chain
;;    :PROPERTIES:
;;      :TRIGGER: chain-siblings(NEXT)
;;    :END:
;; 
;; ** This is the second in this chain
;;
;; ** This is the third in this chain
;;    :PROPERTIES:
;;      :BLOCKER: I-cannot-do-it-without-money
;;    :END:
;;
;; ** This is the forth in this chain
;;    When this is DONE, we will also trigger entry XYZ-is-my-id
;;   :PROPERTIES:
;;     :TRIGGER: XYZ-is-my-id(TODO)
;;   :END:
;;
;; ** This is the fifth in this chain
;; 
;; * Start writing report
;;   :PROPERTIES:
;;     :ID: XYZ-is-my-id
;;   :END:
;;
;;

(require 'org)

(defcustom org-depend-tag-blocked t
  "Whether to indicate blocked TODO items by a special tag."
  :group 'org
  :type 'boolean)

(defmacro org-depend-act-on-sibling (trigger-val &rest rest)
  "Perform a set of actions on the next sibling, if it exists,
copying the sibling spec TRIGGER-VAL to the next sibling."
  `(catch 'exit
     (save-excursion
       (goto-char pos)
       ;; find the sibling, exit if no more siblings
       (condition-case nil
           (outline-forward-same-level 1)
         (error (throw 'exit t)))
       ;; mark the sibling TODO
       ,@rest
       ;; make sure the sibling will continue the chain
       (org-entry-add-to-multivalued-property
        nil "TRIGGER" ,trigger-val))))

(defun org-depend-trigger-todo (change-plist)
  "Trigger new TODO entries after the current is switched to DONE.
This does two different kinds of triggers:

- If the current entry contains a TRIGGER property that contains
  \"chain-siblings(KEYWORD)\", it goes to the next sibling, marks it
  KEYWORD and also installs the \"chain-sibling\" trigger to continue
  the chain.
- If the current entry contains a TRIGGER property that contains
  \"chain-siblings-scheduled\", we go to the next sibling and copy
  the scheduled time from the current task, also installing the property
  in the sibling.
- Any other word (space-separated) like XYZ(KEYWORD) in the TRIGGER
  property is seen as an entry id.  Org-mode finds the entry with the
  corresponding ID property and switches it to the state TODO as well."

  ;; Get information from the plist
  (let* ((type (plist-get change-plist :type))
	       (pos (plist-get change-plist :position))
	 (from (plist-get change-plist :from))
	 (to (plist-get change-plist :to))
	 (org-log-done nil) ; IMPROTANT!: no logging during automatic trigger!
	 trigger triggers tr p1 kwd)
    (catch 'return
      (unless (eq type 'todo-state-change)
	;; We are only handling todo-state-change....
	(throw 'return t))
      (unless (and (member from org-not-done-keywords)
		   (member to org-done-keywords))
	;; This is not a change from TODO to DONE, ignore it
	(throw 'return t))

      ;; OK, we just switched from a TODO state to a DONE state
      ;; Lets see if this entry has a TRIGGER property.
      ;; If yes, split it up on whitespace.
      (setq trigger (org-entry-get pos "TRIGGER")
	    triggers (and trigger (org-split-string trigger "[ \t]+")))

      ;; Go through all the triggers
      (while (setq tr (pop triggers))
	(cond
	 ((string-match "\\`chain-siblings(\\(.*?\\))\\'" tr)
	  ;; This is a TODO chain of siblings
	  (setq kwd (match-string 1 tr))
          (org-depend-act-on-sibling (format "chain-siblings(%s)" kwd)
                                     (org-todo kwd)))
	 ((string-match "\\`\\(\\S-+\\)(\\(.*?\\))\\'" tr)
	  ;; This seems to be ENTRY_ID(KEYWORD)
	  (setq id (match-string 1 tr)
		kwd (match-string 2 tr)
		p1 (org-find-entry-with-id id))
	  (when p1
	    ;; there is an entry with this ID, mark it TODO
	    (save-excursion
	      (goto-char p1)
	      (org-todo kwd))))
         ((string-match "\\`chain-siblings-scheduled\\'" tr)
          (let ((time (org-get-scheduled-time pos)))
            (when time
              (org-depend-act-on-sibling
               "chain-siblings-scheduled"
               (org-schedule nil time))))))))))

(defun org-depend-block-todo (change-plist)
  "Block turning an entry into a TODO.
This checks for a BLOCKER property in an entry and checks
all the entries listed there.  If any of them is not done,
block changing the current entry into a TODO entry.  If the property contains
the word \"previous-sibling\", the sibling above the current entry is checked.
Any other words are treated as entry id's. If an entry exists with the
this ID property, that entry is also checked."
  ;; Get information from the plist
  (let* ((type (plist-get change-plist :type))
	       (pos (plist-get change-plist :position))
	 (from (plist-get change-plist :from))
	 (to (plist-get change-plist :to))
	 (org-log-done nil) ; IMPROTANT!: no logging during automatic trigger
	 blocker blockers bl p1
	 (proceed-p
	  (catch 'return
	    (unless (eq type 'todo-state-change)
	      ;; We are not handling this kind of change
	      (throw 'return t))
	    (unless (and (not from) (member to org-not-done-keywords))
	      ;; This is not a change from nothing to TODO, ignore it
	      (throw 'return t))

	    ;; OK, the plan is to switch from nothing to TODO
	    ;; Lets see if we will allow it.  Find the BLOCKER property
	    ;; and split it on whitespace.
	    (setq blocker (org-entry-get pos "BLOCKER")
		  blockers (and blocker (org-split-string blocker "[ \t]+")))
	    
	    ;; go through all the blockers
	    (while (setq bl (pop blockers))
	      (cond
	       ((equal bl "previous-sibling")
		;; the sibling is required to be DONE.
		(catch 'ignore
		  (save-excursion
		    (goto-char pos)
		    ;; find the older sibling, exit if no more siblings
		    (condition-case nil
			(outline-backward-same-level 1)
		      (error (throw 'ignore t)))
		    ;; Check if this entry is not yet done and block
		    (unless (org-entry-is-done-p)
		      ;; return nil, to indicate that we block the change!
		      (org-mark-ring-push)
		      (throw 'return nil)))))

	       ((setq p1 (org-find-entry-with-id bl))
		;; there is an entry with this ID, check it out
		(save-excursion
		  (goto-char p1)
		  (unless (org-entry-is-done-p)
		    ;; return nil, to indicate that we block the change!
		    (org-mark-ring-push)
		    (throw 'return nil))))))
	    t ; return t to indicate that we are not blocking
	    )))
    (when org-depend-tag-blocked
      (org-toggle-tag "blocked" (if proceed-p 'off 'on)))
    
    proceed-p))

(add-hook 'org-trigger-hook 'org-depend-trigger-todo)
(add-hook 'org-blocker-hook 'org-depend-block-todo)

(provide 'org-depend)

;;; org-depend.el ends here
debug log:

solving 0837276 ...
found 0837276 in https://list.orgmode.org/orgmode/c8389b600902030928v772a558bsec62df51780c196c@mail.gmail.com/
found 079d194 in https://git.savannah.gnu.org/cgit/emacs/org-mode.git
preparing index
index prepared:
100644 079d194670257f26184b733062041a34908f96fe	contrib/lisp/org-depend.el

applying [1/1] https://list.orgmode.org/orgmode/c8389b600902030928v772a558bsec62df51780c196c@mail.gmail.com/
diff --git a/contrib/lisp/org-depend.el b/contrib/lisp/org-depend.el
index 079d194..0837276 100644

Checking patch contrib/lisp/org-depend.el...
Applied patch contrib/lisp/org-depend.el cleanly.

index at:
100644 08372761307b0bccac8b6d92a7d07546399114c5	contrib/lisp/org-depend.el

Code repositories for project(s) associated with this inbox:

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).