emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* Nicer export of an ipython-notebook style file
@ 2020-03-10 21:21 Dima Kogan
  0 siblings, 0 replies; only message in thread
From: Dima Kogan @ 2020-03-10 21:21 UTC (permalink / raw)
  To: emacs-orgmode

Hi.

Recently I used org-babel to create documentation for a python plotting
library. The end-product of this documentation is a sequence of code
snippets and images (the result of evaluating the snippets).

A wrinkle is that I didn't want to use org to do the export. I'd use org
to generate all the images, and then push the .org to github, and use
its (half-assed) renderer to display the results.

This mostly worked out of the box, but some elisp was needed to get this
done fully. So this email is meant to

1. Ask the question "what is the right way to have done this?"

2. Talk about the elisp I wrote to get this done, and maybe get some of
   it merged upstream

The final product lives here:

  https://github.com/dkogan/gnuplotlib/blob/master/guide/guide.org

The big "init" block at the end is the emacs lisp in question. I can't
get github to not display it, but that's a separate thing.

Now, each required piece, one at a time.


Each python block needs to know the file it should write the results to.
Org and python need to agree about this. It looks like some org-babel
backends do this (gnuplot for example), but in general there's no way. I
wrote a lisp thing to make ALL the org-babel headers for that snippet
available to the python block in a dict called "_org_babel_params". So
the python code writes its output to _org_babel_params['_file']. Clearly
this is specific to python, but any backend that has any sort of
associative array could maybe support something like this. The code:

  (defun dima-org-babel-python-var-to-python (var)
    "Convert an elisp value to a python variable.
    Like the original, but supports (a . b) cells and symbols
    "
    (if (listp var)
        (if (listp (cdr var))
            (concat "[" (mapconcat #'org-babel-python-var-to-python var ", ") "]")
          (format "\"\"\"%s\"\"\"" var))
      (if (symbolp var)
          (format "\"\"\"%s\"\"\"" var)
        (if (eq var 'hline)
            org-babel-python-hline-to
          (format
           (if (and (stringp var) (string-match "[\n\r]" var)) "\"\"%S\"\"" "%S")
           (if (stringp var) (substring-no-properties var) var))))))
  (defun dima-alist-to-python-dict (alist)
    "Generates a string defining a python dict from the given alist"
    (let ((keyvalue-list
           (mapcar (lambda (x)
                     (format "%s = %s, "
                             (replace-regexp-in-string
                              "[^a-zA-Z0-9_]" "_"
                              (symbol-name (car x)))
                             (dima-org-babel-python-var-to-python (cdr x))))
                   alist)))
      (concat
       "dict( "
       (apply 'concat keyvalue-list)
       ")")))
  (defun dima-org-babel-python-pass-all-params (f params)
    (cons
     (concat
      "_org_babel_params = "
      (dima-alist-to-python-dict params))
     (funcall f params)))
  (advice-add
   #'org-babel-variable-assignments:python
   :around #'dima-org-babel-python-pass-all-params)

Now that org-babel can tell python where to write its output, we need to
tell org-babel this. I don't actually care what the output file is
called, as long as it's unique. So each of my org-babel snippets does
not specify the :file at all, instead I advice
org-babel-execute-src-block to set a :file with "guide-%d.svg" where the
%d is an integer that's incremented with each block:

  (defun dima-org-babel-python-unique-plot-filename
      (f &optional arg info params)
    (funcall f arg info
             (cons (cons ':file
                         (format "guide-%d.svg"
                                 (condition-case nil
                                     (setq dima-unique-plot-number (1+ dima-unique-plot-number))
                                   (error (setq dima-unique-plot-number 0)))))
                   params)))
  (advice-add
   #'org-babel-execute-src-block
   :around #'dima-org-babel-python-unique-plot-filename)

I'd like the count to start from 0 each time I (org-babel-execute-buffer):

  (defun dima-reset-unique-plot-number
      (&rest args)
      (setq dima-unique-plot-number 0))
  (advice-add
   #'org-babel-execute-buffer
   :after #'dima-reset-unique-plot-number)


Was there a better way to do this? Any of these advices upstreamable?

Thanks.

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2020-03-10 21:21 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-10 21:21 Nicer export of an ipython-notebook style file Dima Kogan

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).