From mboxrd@z Thu Jan 1 00:00:00 1970 From: Dima Kogan Subject: Nicer export of an ipython-notebook style file Date: Tue, 10 Mar 2020 14:21:38 -0700 Message-ID: <87pndjc3dp.fsf@secretsauce.net> Mime-Version: 1.0 Content-Type: text/plain Return-path: Received: from eggs.gnu.org ([2001:470:142:3::10]:48608) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jBmJv-0002jz-6T for emacs-orgmode@gnu.org; Tue, 10 Mar 2020 17:21:48 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1jBmJt-0003wz-Qz for emacs-orgmode@gnu.org; Tue, 10 Mar 2020 17:21:47 -0400 Received: from wout5-smtp.messagingengine.com ([64.147.123.21]:35121) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1jBmJt-0003tJ-5X for emacs-orgmode@gnu.org; Tue, 10 Mar 2020 17:21:45 -0400 Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.west.internal (Postfix) with ESMTP id CA1EB7ED for ; Tue, 10 Mar 2020 17:21:43 -0400 (EDT) Received: from shorty.local (wsip-98-171-133-120.sd.sd.cox.net [98.171.133.120]) by mail.messagingengine.com (Postfix) with ESMTPA id DA0D93280065 for ; Tue, 10 Mar 2020 17:21:42 -0400 (EDT) Received: from dima by shorty.local with local (Exim 4.92.1) (envelope-from ) id 1jBmJp-0001LE-If for emacs-orgmode@gnu.org; Tue, 10 Mar 2020 14:21:41 -0700 List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane-mx.org@gnu.org Sender: "Emacs-orgmode" To: emacs-orgmode@gnu.org 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.