/ob-clojure-literate

Setup scaffold for Clojure Literate Programming in Org-mode.

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

ob-clojure-literate

Screenshots

use default session

#+begin_src clojure :session "*cider-repl ob-clojure*" :results output
(prn *ns*)
#+end_src

#+RESULTS:
: #namespace[user]

support generate plot

support header argument :dir

Installation

use-package

(use-package ob-clojure-literate
  :ensure t
  :after org
  :init
  (setq ob-clojure-literate-auto-jackin-p t)
  (add-hook 'org-mode-hook #'ob-clojure-literate-mode)
  )

Motivation

I like Emacs Org-mode “Literate Programming” very much. It’s a kind of paradigm. I can apply this idea on many places. Now Clojure is my favourite programming language. I hope to combine them together. But ob-clojure does not suitable for Literate Programming very much like other language babel (like Python) supports.

This README is totally written in Org-mode Literate Programming.

So I decide to solve this problem in my way.

Todos

Support generate inline plot image under current working directory

  • State “TODO” from [2017-12-22 Fri 09:52]

Support babel header argument :dir

  • State “TODO” from [2017-12-22 Fri 09:52]

Usage

This package workflow

auto start an CIDER REPL for ob-clojure

  1. First, create a plain Clojure project with Leiningen to used for ob-clojure.
    lein new ob-clojure
        
  2. Then auto start CIDER REPL session in this plain Clojure project.
    1. Set ob-clojure default header arguments to a static session name:
      (add-to-list 'org-babel-default-header-args:clojure
                   '(:session . "*cider-repl ob-clojure*"))
              
    2. open a file in project to prepare for CIDER jack-in.
      (progn
        (find-file (expand-file-name "~/.emacs.d/Org-mode/ob-clojure/src/ob_clojure/core.clj"))
        (cider-jack-in))
              
  3. To fix org-babel-execute:clojure has a line (cider-current-ns) which will invoke (cider-find-ns). The (cider-find-ns) will try to extract Clojure namespace from current buffer.

    This will cause a problem, like in following org-mode file content:

    * test results output
    
    #+BEGIN_SRC clojure :result output
    (println "hi")
    (println (str *ns*))
    #+END_SRC
    
    When I execute first src block [C-c C-c], it will find namespace and
    return wrong namespace ~kk~ in second src block. This is not a
    expected behavior.
    
    * different namespace
    
    #+BEGIN_SRC clojure :result output
    (in-ns 'kk)
    (println (str *ns*))
    #+END_SRC
        

    In order to fix this problem, I asked a lot of places, and try many methods.

    Finally I found the variable cider-buffer-ns (which in function cider-current-ns) docstring description.

    Current Clojure namespace of some buffer.
    
    Useful for special buffers (e.g. REPL, doc buffers) that have to
    keep track of a namespace.
    
    This should never be set in Clojure buffers, as there the namespace
    should be extracted from the buffer's ns form.
        

    Then I come up an idea:

    • should I include org-mode as special for CIDER cider-buffer-ns?
      • It is nil in Clojure buffer.
      • It is ”user” in cider-repl ob-clojure session.
      • Maybe I should use elisp code to manually set this ns to user.
  4. So the final solution source code is:
    ;; auto start CIDER REPL session in a complete Leiningen project environment for Org-mode Babel by jack-in.
    (add-to-list 'org-babel-default-header-args:clojure
                 '(:session . "*cider-repl ob-clojure*"))
    
    (progn
      (find-file (expand-file-name "~/.emacs.d/Org-mode/ob-clojure/src/ob_clojure/core.clj"))
      (cider-jack-in))
    
    (defun ob-clojure-cider-do-not-find-ns ()
      "Fix the issue that `cider-current-ns' try to invoke `clojure-find-ns' to extract ns from buffer."
      (setq-local cider-buffer-ns "user"))
    (add-hook 'org-mode-hook #'ob-clojure-cider-do-not-find-ns)
        

    But the function ob-clojure-cider-don-not-find-ns can be smarter:

    How to execute elisp code in a specific buffer without actually switching to it? I can writing a function get a buffer local variable in a specific (regex matched) buffer.

    (defun ob-clojure-cider-do-not-find-ns ()
      "Fix the issue that `cider-current-ns' try to invoke `clojure-find-ns' to extract ns from buffer."
      (with-current-buffer "*cider-repl ob-clojure*"
        (defvar ob-clojure-cider-repl-ns cider-buffer-ns)
        (setq-local cider-buffer-ns ob-clojure-cider-repl-ns)))