| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 | 
							- ;;;_ org-choose.el --- decision management for org-mode
 
- ;;;_. Headers
 
- ;;;_ , License
 
- ;; Copyright (C) 2009  Tom Breton (Tehom)
 
- ;; Author: Tom Breton (Tehom)
 
- ;; Keywords: outlines, convenience
 
- ;; 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 2, or (at your option)
 
- ;; any later version.
 
- ;; This file 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., 59 Temple Place - Suite 330,
 
- ;; Boston, MA 02111-1307, USA.
 
- ;;;_ , Commentary:
 
- ; This is code to support decision management.  It lets you treat a
 
- ; group of sibling items in org-mode as alternatives in a decision.
 
- ; There are no user commands in this file.  You use it by:
 
- ;   * Loading it (manually or by M-x customize-apropos org-modules)
 
- ;;   * Setting up at least one set of TODO keywords with the
 
- ;;     interpretation "choose" by either:
 
- ;;     * Using the file directive #+CHOOSE_TODO:
 
- ;;       * For instance, "#+CHOOSE_TODO: NO(,-) MAYBE(,0) YES"
 
- ;;     * Or by M-x customize-apropos org-todo-keywords
 
- ;;   * Operating on single items with the TODO commands.
 
- ;;     * Use C-S-right to change the keyword set.  Use this to change to
 
- ;;       the "choose" keyword set that you just defined.
 
- ;;     * Use S-right to advance the TODO mark to the next setting.  
 
- ;;       For "choose", that means you like this alternative more than
 
- ;;       before.  Other alternatives will be automatically demoted to
 
- ;;       keep your settings consistent.
 
- ;;     * Use S-left to demote TODO to the previous setting.  
 
- ;;       For "choose", that means you don't like this alternative as much
 
- ;;       as before.  Other alternatives will be automatically promoted,
 
- ;;       if this item was all that was keeping them down.
 
- ;;     * All the other TODO commands are available and behave essentially
 
- ;;       the normal way.
 
- ;;;_ , Requires
 
- (require 'org)
 
- ;(eval-when-compile
 
- ;   (require 'cl))
 
- (require 'cl)
 
- ;;;_. Body
 
- ;;;_ , The variables
 
- (defstruct (org-choose-mark-data. (:type list))
 
-    "The format of an entry in org-choose-mark-data.
 
- Indexes are 0-based or `nil'.
 
- "
 
-    keyword
 
-    bot-lower-range
 
-    top-upper-range
 
-    range-length
 
-    static-default
 
-    all-keywords)
 
- (defvar org-choose-mark-data 
 
-    ()
 
-    "Alist of information for choose marks.
 
- Each entry is an `org-choose-mark-data.'" )
 
- (make-variable-buffer-local 'org-choose-mark-data)
 
- ;;;_ , For setup
 
- ;;;_  . org-choose-filter-one
 
- (defun org-choose-filter-one (i)
 
-    "Return a list of
 
-  * a canonized version of the string
 
-  * optionally one symbol"
 
-    (if
 
-       (not
 
- 	 (string-match "(.*)" i))
 
-       (list i i)
 
-       (let* 
 
- 	 (
 
- 	    (end-text (match-beginning 0))
 
- 	    (vanilla-text (substring i 0 end-text))
 
- 	    ;;Get the parenthesized part.
 
- 	    (match (match-string 0 i))
 
- 	    ;;Remove the parentheses.
 
- 	    (args (substring match 1 -1))
 
- 	    ;;Split it
 
- 	    (arglist
 
- 	       (let
 
- 		  ((arglist-x (org-split-string args ",")))
 
- 		  ;;When string starts with "," `split-string' doesn't
 
- 		  ;;make a first arg, so in that case make one
 
- 		  ;;manually.
 
- 		  (if 
 
- 		     (string-match "^," args)
 
- 		     (cons nil arglist-x)
 
- 		     arglist-x)))
 
- 	    (decision-arg (second arglist))
 
- 	    (type
 
- 	       (cond
 
- 		  ((string= decision-arg "0")
 
- 		     'default-mark)
 
- 		  ((string= decision-arg "+")
 
- 		     'top-upper-range)
 
- 		  ((string= decision-arg "-")
 
- 		     'bot-lower-range)
 
- 		  (t nil)))
 
- 	    (vanilla-arg (first arglist))
 
- 	    (vanilla-mark
 
- 	       (if vanilla-arg
 
- 		  (concat vanilla-text "("vanilla-arg")")
 
- 		  vanilla-text)))
 
- 	 (if type
 
- 	    (list vanilla-text vanilla-mark type)
 
- 	    (list vanilla-text vanilla-mark)))))
 
- ;;;_  . org-choose-setup-vars
 
- (defun org-choose-setup-vars (bot-lower-range top-upper-range
 
- 				   static-default num-items all-mark-texts)
 
-    "Add to org-choose-mark-data according to arguments"
 
-    (let*
 
-       (
 
- 	 (tail
 
- 	    ;;If there's no bot-lower-range or no default, we don't
 
- 	    ;;have ranges.
 
- 	    (cdr
 
- 	       (if (and static-default bot-lower-range)
 
- 		  (let*
 
- 		     (
 
- 			;;If there's no top-upper-range, use the last
 
- 			;;item.
 
- 			(top-upper-range
 
- 			   (or top-upper-range (1- num-items)))
 
- 			(lower-range-length 
 
- 			   (1+ (- static-default bot-lower-range)))
 
- 			(upper-range-length 
 
- 			   (- top-upper-range static-default))
 
- 			(range-length 
 
- 			   (min upper-range-length lower-range-length)))
 
- 		     (make-org-choose-mark-data.
 
- 			:keyword nil
 
- 			:bot-lower-range bot-lower-range
 
- 			:top-upper-range top-upper-range
 
- 			:range-length    range-length
 
- 			:static-default static-default
 
- 			:all-keywords all-mark-texts))
 
- 		  (make-org-choose-mark-data.
 
- 		     :keyword nil
 
- 		     :bot-lower-range nil
 
- 		     :top-upper-range nil
 
- 		     :range-length    nil
 
- 		     :static-default (or static-default 0)
 
- 		     :all-keywords all-mark-texts)))))
 
-       (dolist (text all-mark-texts)
 
- 	 (pushnew (cons text tail)
 
- 	    org-choose-mark-data
 
- 	    :test
 
- 	    #'(lambda (a b)
 
- 		 (equal (car a) (car b)))))))
 
- ;;;_  . org-choose-filter-tail
 
- (defun org-choose-filter-tail (raw)
 
-    "Return a translation of RAW to vanilla and set appropriate
 
- buffer-local variables. 
 
- RAW is a list of strings representing the input text of a choose
 
- interpretation."
 
-    (let
 
-       ((vanilla-list nil)
 
- 	 (all-mark-texts nil)
 
- 	 (index 0)
 
- 	 bot-lower-range top-upper-range range-length static-default)
 
-       (dolist (i raw)
 
- 	 (destructuring-bind
 
- 	    (vanilla-text vanilla-mark &optional type)
 
- 	    (org-choose-filter-one i)
 
- 	    (cond
 
- 	       ((eq type 'bot-lower-range)
 
- 		  (setq bot-lower-range index))
 
- 	       ((eq type 'top-upper-range)
 
- 		  (setq top-upper-range index))
 
- 	       ((eq type 'default-mark)
 
- 		  (setq static-default index)))
 
- 	    (incf index)
 
- 	    (push vanilla-text all-mark-texts)
 
- 	    (push vanilla-mark vanilla-list)))
 
-       (org-choose-setup-vars bot-lower-range top-upper-range
 
- 	 static-default index (reverse all-mark-texts)) 
 
-       (nreverse vanilla-list)))
 
- ;;;_  . org-choose-setup-filter
 
- (defun org-choose-setup-filter (raw)
 
-    "A setup filter for choose interpretations."
 
-    (when (eq (car raw) 'choose)
 
-       (cons
 
- 	 'choose
 
- 	 (org-choose-filter-tail (cdr raw)))))
 
- ;;;_  . org-choose-conform-after-promotion
 
- (defun org-choose-conform-after-promotion (entry-pos keywords highest-ok-ix)
 
-   "Conform the current item after another item was promoted"
 
-    
 
-    (unless
 
-       ;;Skip the entry that triggered this by skipping any entry with
 
-       ;;the same starting position.  plist uses the start of the
 
-       ;;header line as the position, but map no longer does, so we
 
-       ;;have to go back to the heading.
 
-       (= 
 
- 	 (save-excursion
 
- 	    (org-back-to-heading)
 
- 	    (point)) 
 
- 	 entry-pos)
 
-       (let
 
- 	 ((ix
 
- 	     (org-choose-get-entry-index keywords)))
 
- 	 ;;If the index of the entry exceeds the highest allowable
 
- 	 ;;index, change it to that.
 
- 	 (when (and ix 
 
- 		  (> ix highest-ok-ix))
 
- 	    (org-todo 
 
- 	       (nth highest-ok-ix keywords))))))
 
- ;;;_  . org-choose-conform-after-demotion
 
- (defun org-choose-conform-after-demotion (entry-pos keywords
 
- 					       raise-to-ix
 
- 					       old-highest-ok-ix) 
 
-   "Conform the current item after another item was demoted."
 
-    (unless
 
-       ;;Skip the entry that triggered this.
 
-       (= 
 
- 	 (save-excursion
 
- 	    (org-back-to-heading)
 
- 	    (point))
 
- 	 entry-pos)
 
-       (let
 
- 	 ((ix
 
- 	     (org-choose-get-entry-index keywords)))
 
- 	 ;;If the index of the entry was at or above the old allowable
 
- 	 ;;position, change it to the new mirror position if there is
 
- 	 ;;one.
 
- 	 (when (and 
 
- 		  ix 
 
- 		  raise-to-ix
 
- 		  (>= ix old-highest-ok-ix))
 
- 	    (org-todo 
 
- 	       (nth raise-to-ix keywords))))))
 
- ;;;_ , org-choose-keep-sensible (the org-trigger-hook function)
 
- (defun org-choose-keep-sensible (change-plist)
 
-   "Bring the other items back into a sensible state after an item's
 
- setting was changed."
 
-    (let*
 
-       (  (from (plist-get change-plist :from))
 
- 	 (to (plist-get change-plist :to))
 
- 	 (entry-pos 
 
- 	    (set-marker
 
- 	       (make-marker)
 
- 	       (plist-get change-plist :position)))
 
- 	 (kwd-data
 
- 	    (assoc to org-todo-kwd-alist)))
 
-       (when
 
- 	 (eq (nth 1 kwd-data) 'choose)
 
- 	 (let*
 
- 	    (
 
- 	       (data
 
- 		  (assoc to org-choose-mark-data))
 
- 	       (keywords
 
- 		  (org-choose-mark-data.-all-keywords data))
 
- 	       (old-index
 
- 		  (org-choose-get-index-in-keywords
 
- 		     from 
 
- 		     keywords))
 
- 	       (new-index
 
- 		  (org-choose-get-index-in-keywords
 
- 		     to 
 
- 		     keywords))
 
- 	       (highest-ok-ix
 
- 		  (org-choose-highest-other-ok
 
- 		     new-index
 
- 		     data))
 
- 	       (funcdata
 
- 		  (cond
 
- 		     ;;The entry doesn't participate in conformance,
 
- 		     ;;so give `nil' which does nothing.
 
- 		     ((not highest-ok-ix) nil)
 
- 		     ;;The entry was created or promoted
 
- 		     ((or
 
- 			 (not old-index)
 
- 			 (> new-index old-index))
 
- 			(list
 
- 			   #'org-choose-conform-after-promotion
 
- 			   entry-pos keywords 
 
- 			   highest-ok-ix))
 
- 		     (t	;;Otherwise the entry was demoted.
 
- 			(let
 
- 			   (
 
- 			      (raise-to-ix
 
- 				 (min
 
- 				    highest-ok-ix
 
- 				    (org-choose-mark-data.-static-default
 
- 				       data)))
 
- 			      (old-highest-ok-ix
 
- 				 (org-choose-highest-other-ok
 
- 				    old-index
 
- 				    data)))
 
- 			   
 
- 			   (list
 
- 			      #'org-choose-conform-after-demotion 
 
- 			      entry-pos 
 
- 			      keywords
 
- 			      raise-to-ix
 
- 			      old-highest-ok-ix))))))
 
- 	    
 
- 	    (if funcdata
 
- 	       ;;The funny-looking names are to make variable capture
 
- 	       ;;unlikely.  (Poor-man's lexical bindings).
 
- 	       (destructuring-bind (func-d473 . args-46k) funcdata
 
- 		  (let
 
- 		     ((map-over-entries
 
- 			 (org-choose-get-fn-map-group))
 
- 			;;We may call `org-todo', so let various hooks
 
- 			;;`nil' so we don't cause loops.
 
- 			org-after-todo-state-change-hook
 
- 			org-trigger-hook 
 
- 			org-blocker-hook 
 
- 			org-todo-get-default-hook
 
- 			;;Also let this alist `nil' so we don't log
 
- 			;;secondary transitions.
 
- 			org-todo-log-states)
 
- 		     ;;Map over group
 
- 		     (funcall map-over-entries
 
- 			#'(lambda ()
 
- 			     (apply func-d473 args-46k))))))))
 
-       
 
-       ;;Remove the marker
 
-       (set-marker entry-pos nil)))
 
- ;;;_ , Getting the default mark
 
- ;;;_  . org-choose-get-index-in-keywords
 
- (defun org-choose-get-index-in-keywords (ix all-keywords)
 
-   "Return the index of the current entry."
 
-    (if ix
 
-       (position ix all-keywords
 
- 	 :test #'equal)))
 
- ;;;_  . org-choose-get-entry-index
 
- (defun org-choose-get-entry-index (all-keywords)
 
-    "Return index of current entry."
 
-    (let*
 
-       ((state (org-entry-get (point) "TODO")))
 
-       (org-choose-get-index-in-keywords state all-keywords)))
 
- ;;;_  . org-choose-get-fn-map-group
 
- (defun org-choose-get-fn-map-group ()
 
-    "Return a function to map over the group"
 
-    
 
-    #'(lambda (fn)
 
-        (require 'org-agenda) ;; `org-map-entries' seems to need it.
 
- 	(save-excursion
 
- 	  (unless (org-up-heading-safe)
 
- 	    (error "Choosing is only supported between siblings in a tree, not on top level"))
 
- 	  (let
 
-  	      ((level (org-reduced-level (org-outline-level))))
 
- 	    (save-restriction
 
- 	      (org-map-entries 
 
- 	       fn
 
- 	       (format "LEVEL=%d" level)
 
- 	       'tree))))))
 
- ;;;_  . org-choose-get-highest-mark-index
 
- (defun org-choose-get-highest-mark-index (keywords)
 
-    "Get the index of the highest current mark in the group.
 
- If there is none, return 0"
 
-    (let*
 
-       (
 
- 	 ;;Func maps over applicable entries.
 
- 	 (map-over-entries
 
- 	    (org-choose-get-fn-map-group))
 
- 	 
 
- 	 (indexes-list
 
- 	    (remove nil
 
- 	       (funcall map-over-entries 
 
- 		  #'(lambda ()
 
- 		       (org-choose-get-entry-index keywords))))))
 
-       (if
 
- 	 indexes-list
 
- 	 (apply #'max indexes-list)
 
- 	 0)))
 
- ;;;_  . org-choose-highest-ok
 
- (defun org-choose-highest-other-ok (ix data)
 
-   "Return the highest index that any choose mark can sensibly have,
 
- given that another mark has index IX.
 
- DATA must be a `org-choose-mark-data.'."
 
-    (let
 
-       (		
 
- 	 (bot-lower-range
 
- 	    (org-choose-mark-data.-bot-lower-range data))
 
- 	 (top-upper-range
 
- 	    (org-choose-mark-data.-top-upper-range data))
 
- 	 (range-length
 
- 	    (org-choose-mark-data.-range-length data)))
 
-       (when (and ix bot-lower-range)
 
- 	 (let*
 
- 	    ((delta
 
- 		(- top-upper-range ix)))
 
- 	    (unless
 
- 	       (< range-length delta)
 
- 	       (+ bot-lower-range delta))))))
 
- ;;;_  . org-choose-get-default-mark-index
 
- (defun org-choose-get-default-mark-index (data) 
 
-   "Return the index of the default mark in a choose interpretation.
 
- DATA must be a `org-choose-mark-data.'."
 
-    (or
 
-       (let
 
- 	 ((highest-mark-index
 
- 	     (org-choose-get-highest-mark-index
 
- 		(org-choose-mark-data.-all-keywords data))))
 
- 	 (org-choose-highest-other-ok
 
- 	    highest-mark-index data))
 
-       (org-choose-mark-data.-static-default data)))
 
- ;;;_  . org-choose-get-mark-N
 
- (defun org-choose-get-mark-N (n data)
 
-    "Get the text of the nth mark in a choose interpretation."
 
-    
 
-    (let*
 
-       ((l (org-choose-mark-data.-all-keywords data)))
 
-       (nth n l)))
 
- ;;;_  . org-choose-get-default-mark
 
- (defun org-choose-get-default-mark (new-mark old-mark)
 
-    "Get the default mark IFF in a choose interpretation.
 
- NEW-MARK and OLD-MARK are the text of the new and old marks."
 
-    (let*
 
-       (
 
- 	 (old-kwd-data
 
- 	    (assoc old-mark org-todo-kwd-alist))
 
- 	 (new-kwd-data
 
- 	    (assoc new-mark org-todo-kwd-alist))
 
- 	 (becomes-choose
 
- 	    (and
 
- 	       (or
 
- 		  (not old-kwd-data)
 
- 		  (not
 
- 		     (eq (nth 1 old-kwd-data) 'choose)))
 
- 	       (eq (nth 1 new-kwd-data) 'choose))))
 
-       (when
 
- 	 becomes-choose
 
- 	 (let
 
- 	    ((new-mark-data
 
- 		(assoc new-mark org-choose-mark-data)))
 
- 	    (if
 
- 	       new-mark
 
- 	       (org-choose-get-mark-N
 
- 		  (org-choose-get-default-mark-index
 
- 		     new-mark-data)
 
- 		  new-mark-data)
 
- 	       (error "Somehow got an unrecognizable mark"))))))
 
- ;;;_ , Setting it all up
 
- (eval-after-load "org"
 
-   '(progn
 
-      (add-to-list 'org-todo-setup-filter-hook
 
- 		  #'org-choose-setup-filter)
 
-      (add-to-list 'org-todo-get-default-hook
 
- 		  #'org-choose-get-default-mark)
 
-      (add-to-list 'org-trigger-hook
 
- 		  #'org-choose-keep-sensible)
 
-      (add-to-list 'org-todo-interpretation-widgets
 
- 		  '(:tag "Choose   (to record decisions)" choose)
 
- 		  'append)
 
-      ))
 
- ;;;_. Footers
 
- ;;;_ , Provides
 
- (provide 'org-choose)
 
- ;;;_ * Local emacs vars.
 
- ;;;_  + Local variables:
 
- ;;;_  + End:
 
- ;;;_ , End
 
- ;;; org-choose.el ends here
 
 
  |