emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[O] [FYI] Programming with org-dp (by example)


From: Thorsten Jolitz
Subject: [O] [FYI] Programming with org-dp (by example)
Date: Mon, 05 Mar 2018 00:44:10 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/25.3 (gnu/linux)

Hello List,
due to some interest in org-dp recently on this list, I actually took
the challenge of implementing a feature request by John Kitchin (without
actually sticking close to the specification, this is just a showcase
for org-dp).

Task: implement a radio-list with org checkboxes 
Extra feature: a read only radio-list that changes its values
conditional on the values of read/write radio-lists (when mapping the
buffer)

Example org buffer 

,----
| * org-mode radio-list test
| 
| #+NAME: temp
| #+ATTR_ORG: :radio
| - [ ] freezing
| - [ ] cold
| - [ ] normal
| - [X] warm
| - [ ] hot
| - [ ] tropical
| 
| #+NAME: wind
| #+ATTR_ORG: :radio
| - [X] breeze
| - [ ] storm
| - [ ] hurricane
| 
| #+LABEL: read-only
| #+NAME: take-a-walk
| #+ATTR_ORG: :radio
| - [ ] red
| - [ ] yellow
| - [X] green
`----

This is the main program.
The nice thing about this kind of declarative programming:
- parsing and interpretation is left to the org-element framework
- its very clear what we want/need to do: implement tj/radio-cont 

,----
| ;; rewire function
| (defun tj/radio-switch ()
|   "docstring"
|   (interactive)
|   (let ((stmp (time-stamp-string)))
|     (forward-line)
|     (org-dp-rewire 'plain-list 
|                  'tj/radio-cont ;cont
|                  t ;ins 
|                  `(:attr_last_changed (,stmp)) ;aff 
|                  nil ;elem 
|                  )))
| 
| ;; mapping function
| (defun tj/radio-map ()
|   "docstring"
|   (interactive)
|   (let (temp wind)
|   (org-dp-map '(tj/radio-switch) tj/radio-rgxp)))
`----

tj/radio-switch () is the 'rewire' function to modify the plain
(checkbox) list at point.
It does two things:

1. call function 'tj/radio-cont to modifiy the content (= items) of the
plain list

2. add a new affiliated keyword with a current timestamp
`(:attr_last_changed (,stmp))

tj/radio-map () is the mapping function 

Here is the result of switching to more extreme weather conditions by
calling tj/radio-map and answering the user prompts two times:

,----
| #+NAME: temp
| #+ATTR_ORG: :radio
| #+ATTR_LAST_CHANGED: 2018-03-05 00:12:02 tj
| - [ ] freezing
| - [ ] cold
| - [ ] normal
| - [ ] warm
| - [ ] hot
| - [X] tropical
| 
| #+NAME: wind
| #+ATTR_ORG: :radio
| #+ATTR_LAST_CHANGED: 2018-03-05 00:12:05 tj
| - [ ] breeze
| - [ ] storm
| - [X] hurricane
| 
| #+NAME: take-a-walk
| #+ATTR_ORG: :radio
| #+ATTR_LAST_CHANGED: 2018-03-05 00:12:10 tj
| - [X] red
| - [ ] yellow
| - [ ] green
`----

Emacs Lisp is very good for working with text, but with regards to org
elements, whats needed for parsing and interpreting has already be
written by Nicolas, why not reuse it and work on a higher level.

So org-dp might be a light-weight alternative when acting locally on org
elements (no need for a full parse tree). It produces pretty clean code,
since working on text is replaced by working with lists.

PS 1

There was a bug in org-dp.el, I fixed it locally but cannot push to
origin due to github authentication problems (sight..), so if you want to
try the code here, you need to change this too:

,----
| (cont (let ((orig-elem-cont (org-dp-contents elem)))
|  (cond
|   ;; ((and (consp contents) (functionp contents))
|   ((and contents (functionp contents)) ...
`----

magit diff of same change:

,----
| 1 file changed, 2 insertions(+), 1 deletion(-)
| org-dp.el | 3 ++-
| 
| modified   org-dp.el
| @@ -709,7 +709,8 @@ (cl-defun org-dp-rewire (elem-type &optional contents 
replace affiliated element
|              (make-marker) (org-element-property :end elem)))
|        (cont (let ((orig-elem-cont (org-dp-contents elem)))
|                (cond
| -               ((and (consp contents) (functionp contents))
| +               ;; ((and (consp contents) (functionp contents))
| +               ((and contents (functionp contents))
|                  (apply contents (list orig-elem-cont elem)))
|                 ((and contents (booleanp contents))
|                  orig-elem-cont)
`----

PS 2 

here is the complete code:

#+BEGIN_SRC emacs-lisp  
(defconst tj/radio-rgxp "^#\\+attr_org:[[:space:]]+:radio")
(defconst tj/radio-temp "temp")
(defconst tj/radio-wind "wind")

(defvar tj/radio-rw '("temp" "wind")) ;read/write
(defvar tj/radio-r '("take-a-walk")) ;read only

(defvar tj/radio-temp-red '("freezing" "tropical"))
(defvar tj/radio-temp-yellow '("cold" "hot"))
(defvar tj/radio-temp-green '("normal" "warm"))

(defvar tj/radio-wind-red '("breeze"))
(defvar tj/radio-wind-yellow '("storm"))
(defvar tj/radio-wind-green '("hurricane"))

;; rewire function
(defun tj/radio-switch ()
  "docstring"
  (interactive)
  (let ((stmp (time-stamp-string)))
    (forward-line)
    (org-dp-rewire 'plain-list
                   'tj/radio-cont ;cont
                   t ;ins 
                   `(:attr_last_changed (,stmp)) ;aff 
                   nil ;elem 
                   )))

;; mapping function
(defun tj/radio-map ()
  "docstring"
  (interactive)
  (let (temp wind)
  (org-dp-map '(tj/radio-switch) tj/radio-rgxp)))



;; HELPER FUNCTIONS
;; helper function to actually modify the content
(defun tj/radio-cont (cont elem)
  "docstring"
  (let ((name (org-element-property :name elem))
        (prompt-options (tj/radio-get-itm-labels cont))
        (new-cont)
        (users-choice))
    (cond
     ((member name tj/radio-rw) ;prompt user for value
      (progn
        (setq users-choice (ido-completing-read name prompt-options))
        (set (intern name) users-choice)
        (setq new-cont (mapcar 'tj/radio-itm-rw cont))))
     ((member name tj/radio-r) ;set value
      (setq new-cont
            (mapcar 'tj/radio-itm-r cont)))
     (t)) ;do nothing
    (or new-cont cont)))


(defun tj/radio-get-itm-labels (cont)
  "docstring"
  (mapcar #'(lambda (itm)
              (string-remove-suffix
               "\n"
               (org-dp-contents itm t t)))
           cont))

(defun tj/radio-itm-rw (itm)
  "docstring"
  (let ((label (string-remove-suffix
                "\n" (org-dp-contents itm t t))))
    (if (string-equal label users-choice)
        (org-element-put-property itm :checkbox 'on)
        (org-element-put-property itm :checkbox 'off))
    itm))

(defun tj/radio-itm-r (itm)
  "docstring"
  (let ((label (string-remove-suffix
                "\n" (org-dp-contents itm t t))))

    (org-element-put-property itm :checkbox 'off)

    (cond
     ((and (string-equal label "red")
           (or (member temp tj/radio-temp-red)
               (member temp tj/radio-wind-red)))
      (org-element-put-property itm :checkbox 'on))

     ((and (string-equal label "yellow")
           (or (member temp tj/radio-temp-yellow)
               (member temp tj/radio-wind-yellow)))
      (org-element-put-property itm :checkbox 'on))

     ((and (string-equal label "green")
           (or (member temp tj/radio-temp-green)
               (member temp tj/radio-wind-green)))
      (org-element-put-property itm :checkbox 'on)))
    itm))
#+END_SRC

-- 
cheers,
Thorsten





reply via email to

[Prev in Thread] Current Thread [Next in Thread]