| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 | 
							- ;;; org-choose.el --- decision management for org-mode
 
- ;; Copyright (C) 2009-2014 Tom Breton (Tehom)
 
- ;; This file is not part of GNU Emacs.
 
- ;; 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)))
 
- (provide 'org-choose)
 
- ;;; org-choose.el ends here
 
 
  |