суббота, 25 февраля 2017 г.

Format of table data in Emacs ORG mode

I'm continue to work with Spacemacs/Org/Babel, so... :)

The idea of the task is to use ORG mode some kind of Literate Programming (LP). It's a good known technique in Emacs/ORG but here I want to share little bit of ELisp helping to format table data - kind of LP. Imagine, you have documentation about Finite Automata or some table function, or some data set and you want to create some static data code (in C, Tcl, Python, whatever). So, to do it, you need function to format table data, which consists, eventually, of:

  • selecting data (because you may want to skip some columns/rows)
  • formatting it in custom format
  • and sure save it somewhere...

Suppose, we have table in ORG mode:

#+TBLNAME: t1
| arg0 | arg1 | func |
|------+------+------|
| a00  | a01  | f0   |
| a10  | a11  | f1   |
| a20  | a21  | f2   |
| a30  | a31  | f3   |

And we want to format data from the table to free text. Let's consider output like this:

table = 'a10', 'f1',
        'a20', 'f2',
        'a30', 'f3';

To achieve this we create Babel function table-format which has arguments:

  • tbl - table data (put table name)
  • skip - pair of lists: skipped-rows, skipped-columns, eg. '((1 2) (1))
  • rfmt - format funtion w/ args (row-index max-row-index row)
  • cfmt - format function w/ args: (col-index max-col-index col)

while all have default values (and default formatters, looks like original ORG format for table data). Indexes are indexes of visible columns/rows!

So, our function looks like:

#+NAME: table-format
#+HEADERS: :var tbl='(), skip='(), rfmt='(), cfmt='()
#+BEGIN_SRC elisp :session my
; mr - max row index; mc - max column index;
; skip - ((rows-to-skip) (columns-to-skip))
; vmr - visual max row index, ri - row index, vri - visual row index
  (let* ((rs)
         (mr (- (length tbl) 1))
         (def-rfmt #'(lambda (ri mr row) (format "%s\n" row)))
         (def-cfmt #'(lambda (ci mc col) (format (if (< ci mc) "%s|" "%s") col)))
         (rfmt (if (null rfmt) def-rfmt rfmt))
         (cfmt (if (null cfmt) def-cfmt cfmt))
         (skip (if (null skip) '(() ()) skip))
         (rskp (car skip)) (cskp (cadr skip))
         (vmr (- mr (length rskp)))
         (vri 0))
   (dotimes (ri (length tbl) rs)
    (if (not (member ri rskp))
     (let* ((cs)
            (vci 0)
            (row (nth ri tbl))
            (mc (- (length row) 1))
            (vmc (- mc (length cskp))))
      (dotimes (ci (length row) cs)
       (if (not (member ci cskp))
        (let ((col (nth ci row)))
         (setq cs (concat cs (funcall cfmt vci vmc col)))
         (setq vci (+ 1 vci)))))
      (setq rs (concat rs (funcall rfmt vri vmr cs)))
      (setq vri (+ 1 vri))))))
#+END_SRC

Here is example of usage. Note, that we don't use default formatters of raw/column but custom: mycfmt and myrfmt.

#+NAME: mycfmt
#+BEGIN_SRC elisp :session my
(defun mycfmt (ci mc col)
  (format (if (< ci mc) "'%s', " "'%s'") col)
)
#+END_SRC


#+NAME: myrfmt
#+BEGIN_SRC elisp :session my
(defun myrfmt (ri mr row)
 (format 
  (if (< ri mr)
   (if (> ri 0) "        %s,\n" "%s,\n")
   "        %s;\n") row))
#+END_SRC


#+BEGIN_SRC elisp :var d=table-format(t1, skip='((0) (1)), rfmt='myrfmt, cfmt='mycfmt) :results raw
(prin1 (format "table = %s" d))
#+END_SRC