TransWikia.com

Get a table of org-mode file from elisp to be passed to R

Emacs Asked by Omar113 on September 2, 2021

I am bad at elisp. I am trying to return to follow the idea of just getting everything from the orgfile, then pass it to R code to do easier analysis for me.

This works to generate only tags table

#+name: orgtable
#+begin_src emacs-lisp
(let (orgtable)
  (org-map-entries
   (lambda ()
     (dolist (tag (org-get-tags))
       (push (list tag) orgtable))))
  orgtable)
#+end_src

#+begin_src R :var orgtable=orgtable
table(orgtable)

#+end_src

The above snippet works good. But I am trying to make a full table of the following columns

* TODO Task 1  :tag1:
 deadline:1-9-2021
* Waiting Task2
** DONE subtask :tag2:
::LOGBOOK::
completed: 7-7-2020
clock: start time- endtime => 2hours

#+begin_src
;;some script
#+End_src

#+ results
todo:task name:tags: schedule:deadline: clock: completed:
-----------------------------------------------------------------------
todo: task1   : tag1:        :1-9-2021:     :
waiting: task2:     :        :        :     :
done: subtask : tag2:        :        : 2hrs: 7-7-2020

I just don’t know how to nest all of that in elisp code something like:

#+begin_src emacs-lisp
(let (orgtable)
  (org-map-entries
   (lambda ()
     (dolist (tag '((org-get-tags)
                    (org-get-todo-state)
                    (org-get-property-block)
                    ))
       (push (list tag) orgtable))))
  orgtable)
#+end_src

This just fails and not evaluated. Also it would be nice if I can extract every property on it’s own column.

One Answer

Here's an example Org file (based on what you provided but fixed up for syntax etc). It does not contain everything you want, but it does most of it - if I find time, I might come back for the rest:

#+TODO: TODO WAITING | DONE
#+STARTUP: logdrawer logdone

* TODO Task 1                                              :tag1:tag2:
DEADLINE: <2020-07-21 Tue>
 
* WAITING Task2
SCHEDULED: <2020-07-21 Tue>

** DONE subtask                                            :tag2:tag3:
CLOSED: [2020-07-21 Tue 15:50]

:LOGBOOK:
CLOCK: [2020-07-21 Tue 15:47]--[2020-07-21 Tue 15:49] =>  0:02
:END:


* COMMENT Code
#+name: tags
#+begin_src emacs-lisp
  (defun entry-get (prop)
     (or (org-entry-get (point) prop) ""))

  (defun ndk/entries-table ()
     (interactive)
     (let ((tbl '(hline ("Name" "TODO" "Tags" "Scheduled" "Deadline" "Clock" "Closed"))))
       (org-clock-sum)
       (org-map-entries
        (lambda ()
          (push (list (entry-get "ITEM")
                      (entry-get "TODO")
                      (entry-get "TAGS")
                      (entry-get "SCHEDULED")
                      (entry-get "DEADLINE")
                      (entry-get "CLOCKSUM")
                      (entry-get "CLOSED"))
                      tbl))
        t
        'file
        'comment)
       (reverse tbl)))

   (ndk/entries-table)
#+end_src

#+RESULTS: tags
| Name    | TODO    | Tags        | Scheduled        | Deadline         | Clock | Closed                 |
|---------+---------+-------------+------------------+------------------+-------+------------------------|
| Task 1  | TODO    | :tag1:tag2: |                  | <2020-07-21 Tue> |       |                        |
| Task2   | WAITING |             | <2020-07-21 Tue> |                  |  0:02 |                        |
| subtask | DONE    | :tag2:tag3: |                  |                  |  0:02 | [2020-07-21 Tue 15:50] |

Some notes:

  • I've added a #+TODO: setting, defining the TODO states, to make the file self-contained.
  • I have not worried about the LOGBOOK stuff yet: that needs fixing up, both in the file and in the code.
  • I've marked the Code section as a COMMENT and I make sure in the code to skip COMMENTed sections from processing.
  • The call to org-map-entries is limited to the current file and is instructed to skip COMMENT sections.
  • The table is initialized with the column labels and a separator (see next item for why they are in the "wrong" order).
  • We push new elements to the front of the table, so at the end we need to reverse it.
  • If something is missing, it is shown in the table as nil because that's what org-entry-get returns. We can replace those calls with (or (org-entry-get (point) "....") "") to make it return an empty string instead.
  • The special properties I use are documented here where you may find other interesting ones.

EDIT: Added the following changes:

  • Added CLOCK and CLOSED entries in the file and the corresponding entries in the table. For the clock times to be shown correctly, you need to run the function org-clock-sum in the buffer before you try to retrieve the CLOCKSUM property, so I added a call to it.
  • Added a small wrapper around org-entry-get to deal with the "nil vs empty string" problem above.
  • Made the table production code into a function ndk/entries-table and added a call to it in the source block to produce the table.

I believe this completes everything you asked for in the original question.

For the question in your comment, there are a couple of ways of dealing with it: probably the easiest is to leave everything as is, but change the ndk/entries-table function to take an argument, either a filename or a buffer. We then do everything in that buffer (but still produce the output in *this* buffer). Here's an implementation (for the whole file: you can't pick just a headline with this version, but I probably won't be able to do that any time soon - the implementation could be something like this though: make the headline a second optional argument, search in the chosen buffer for the desired headline, make sure you are in the right place, then call org-map-enrieswith atreeargument instead of afileargumentorg-map-entries` is nothing if not flexible ;-) ):

#+name: tags
#+begin_src emacs-lisp
  (defun entry-get (prop)
     (or (org-entry-get (point) prop) ""))

  (defun ndk/entries-table (&optional file-or-buffer)
     (interactive)
     (cond
      ((null file-or-buffer)
       ;; just use the current buffer
       )
      ((bufferp file-or-buffer)
       (set-buffer file-or-buffer))
      (t
       (set-buffer (find-file-noselect file-or-buffer))))
     (let ((tbl '(hline ("Name" "TODO" "Tags" "Scheduled" "Deadline" "Clock" "Closed"))))
       (org-clock-sum)
       (org-map-entries
        (lambda ()
          (push (list (entry-get "ITEM")
                      (entry-get "TODO")
                      (entry-get "TAGS")
                      (entry-get "SCHEDULED")
                      (entry-get "DEADLINE")
                      (entry-get "CLOCKSUM")
                      (entry-get "CLOSED"))
                      tbl))
        t
        'file
        'comment)
       (reverse tbl)))

   (ndk/entries-table "./bar.org")
#+end_src

#+RESULTS: tags
| Name              | TODO | Tags        | Scheduled        | Deadline         | Clock | Closed                 |
|-------------------+------+-------------+------------------+------------------+-------+------------------------|
| Task bar1         | TODO | :tag1:tag2: |                  | <2020-07-21 Tue> |       |                        |
| WAITING Task bar2 |      |             | <2020-07-21 Tue> |                  |  0:02 |                        |
| subtask bar       | DONE | :tag2:tag3: |                  |                  |  0:02 | [2020-07-21 Tue 15:50] |

BTW bar.org is more or less a copy of the original file with the titles changed, and no code included.

Correct answer by NickD on September 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP