org-mode/contrib/scripts/org-docco.org

8.7 KiB

Org-Docco

The docco tool (see http://jashkenas.github.com/docco/) generates HTML from JavaScript source code providing an attractive side-by-side display of source code and comments. This file (see org-docco.org) generates the same type of output from Org-mode documents with code embedded in code blocks.

The way this works is an Org-mode document with embedded code blocks is exported to html using the standard Org-mode export functions. This file defines a new function named org-docco-buffer which, when added to the org-export-html-final-hook, will be run automatically as part of the Org-mod export process doccoizing your Org-mode document.

A pure source code file can be extracted (or "tangled") from the Org-mode document using the normal org-babel-tangle function. See Working With Source Code chapter of the Org-mode manual for more information on using code blocks in Org-mode files.

Disclaimer: this currently only works on very simple Org-mode files which have no headings but rather are just a collection of alternating text and code blocks. It wouldn't be difficult to generalize the following code so that it could be run in particular sub-trees but I simply don't have the time to do so myself, and this version perfectly satisfies my own limit needs. I make no promises to support this code moving forward. Caveat Emptor

;;; org-docco.el --- docco type html generation from Org-mode

;; Copyright (C) 2012 Eric Schulte

;; Author: Eric Schulte
;; Keywords: org-mode, literate programming, html
;; Homepage: http://orgmode.org/worg/org-contrib/org-mime.php
;; Version: 0.01

;; This file is not part of GNU Emacs.

;;; License:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; <- look over there

The cl package provides all of the state-changing functions used below e.g., push and incf. It looks like a namespace-safe version of cl may soon be permissible for use in official Emacs packages.

;;; Code:
(require 'cl)

This is a function which returns the buffer positions of matching regular expressions. It has two special features…

  1. It only counts matched instances of beg-re and end-re which are properly nested, so for example if beg-re and end-re are set to ( and ) respectively and we run this against the following,

    1    2       3 4   5     6
    |    |       | |   |     |
    v    v       v v   v     v
    (foo (bar baz) (qux) quux)
    

    it will return 1 and 6 rather than 1 and 3.

  2. It uses markers which save their position in a buffer even as the buffer is changed (e.g., by me adding in extra HTML text).
(defun org-docco-balanced-re (beg-re end-re)
  "Return the beginning and of a balanced regexp."
  (save-excursion
    (save-match-data
      (let ((both-re (concat "\\(" beg-re "\\|" end-re "\\)"))
            (beg-count 0) (end-count 0)
            beg end)
        (when (re-search-forward beg-re nil t)
          (goto-char (match-beginning 0))
          (setq beg (point-marker))
          (incf beg-count)
          (goto-char (match-end 0))
          (while (and (not end) (re-search-forward both-re nil t))
            (goto-char (match-beginning 0))
            (cond ((looking-at beg-re) (incf beg-count))
                  ((looking-at end-re) (incf end-count))
                  (:otherwise (error "miss-matched")))
            (goto-char (match-end 0))
            (when (= beg-count end-count) (setq end (point-marker))))
          (when end (cons beg end)))))))

This ugly large function does the actual conversion. It wraps the entire main content div of the exported Org-mode html into a single large table. Each row of the table has documentation on the left side and code on the right side. This function has two parts.

  1. We use (org-docco-balanced-re "<div" "</div>") to find the beginning and end of the main content div. We then break up this div at <pre></pre> boundaries with multiple calls to (org-docco-balanced-re "<pre class\"src" "</pre>").
  2. With all documentation/code boundaries in hand we step through the buffer inserting the table html code at boundary locations.
(defun org-docco-buffer ()
  "Call from within an HTML buffer to doccoize it."
  (interactive)
  (let ((table-start "<table>\n")
        (doc-row-start  "<tr><th class=\"docs\">\n") (doc-row-end  "</th>\n")
        (code-row-start "    <td class=\"code\">\n") (code-row-end "</td></tr>\n")
        (table-end "</table>" )
        pair transition-points next)
    (save-excursion
      (save-match-data
        (goto-char (point-min))
        (when (re-search-forward "<div id=\"content\">" nil t)
          (goto-char (match-end 0))
          (push (point-marker) transition-points)
          (goto-char (match-beginning 0))
          (setq pair (org-docco-balanced-re "<div" "</div>"))
          (while (setq next (org-docco-balanced-re "<pre class=\"src" "</pre>"))
            (goto-char (cdr next))
            (push (car next) transition-points)
            (push (cdr next) transition-points))
          (goto-char (cdr pair))
          (push (and (re-search-backward "</div>" nil t) (point-marker))
                transition-points)
          ;; collected transitions, so build the table
          (setq transition-points (nreverse transition-points))
          (goto-char (pop transition-points))
          (insert table-start doc-row-start)
          (while (> (length transition-points) 1)
            (goto-char (pop transition-points))
            (insert doc-row-end code-row-start)
            (goto-char (pop transition-points))
            (insert code-row-end doc-row-start))
          (goto-char (pop transition-points))
          (insert code-row-end table-end)
          (unless (null transition-points)
            (error "leftover points")))))))

We'll use Emacs File Local Variables and the org-export-html-final-hook to control which buffers have org-docco-buffer run as part of their export process.

  (defvar org-docco-doccoize-me nil
    "File local variable controlling if html export should be doccoized.")
  (make-local-variable 'org-docco-doccoize-me)

A simple function will conditionally process HTML output based on the value of this variable.

  (defun org-docco-buffer-maybe ()
    (when org-docco-doccoize-me (org-docco-buffer)))

Finally this function is added to the org-export-html-final-hook.

  (add-hook 'org-export-html-final-hook #'org-docco-buffer-maybe)

That's it. To use this simply;

  1. Checkout this file from https://github.com/eschulte/org-docco,

    git clone git://github.com/eschulte/org-docco.git
    

    and open it using Emacs.

  2. Tangle org-docco.el out of this file by calling org-babel-tangle or C-c C-v t.
  3. Load the resulting Emacs Lisp file.
  4. Execute the following in any Org-mode buffer to add file local variable declarations which will enable post-processed with org-docco-buffer.

    (add-file-local-variable 'org-export-html-postamble nil)
    (add-file-local-variable 'org-export-html-style-include-default nil)
    (add-file-local-variable 'org-docco-doccoize-me t)
    

    And add the following style declaration to make use of the docco.css style sheet taken directly from https://github.com/jashkenas/docco.

    #+Style: <link rel="stylesheet" href="docco.css" type="text/css">
    
(provide 'org-docco)
;;; org-docco.el ends here