From notes to documentation

My workflow for the last few years has been very org-mode heavy. Every ticket has a subsection in my agenda where I will gather information and write a PoC or benchmarks. A lot of work at my day job involves hitting a few REST endpoints, and that happens in ob-restclient.

My flow for creating design documentation is very similar, also all in org-mode.

A design doc tends to evolve until implementation time, and a big challenge has been keeping my notes and the documentation in sync. We share documentation over google docs or confluence and updating graphs, diagrams and text is very manual when using the web interface.

With LaTeX

A bit of experimenting found that google drive supports commenting on PDFs, without converting to google docs. Simply share the document with your team and enable commenting. You can also upload updates into the same folder and it will keep past versions for you. This means that you can org-export to PDF through LaTeX.

Now you can begin rearranging your notes under your design doc subtree and providing some exposition.

* TODO Feature Extraction Design
  :PROPERTIES:
  :EXPORT_OPTIONS: ^:{} author:nil date:nil
  :EXPORT_FILE_NAME: feature_extraction_design
  :END:
** Preamble
   For real-time blah blah blah, we need a blah.
** Work to be done
*** Async processor
    The async processor will consume data from XYZ and blah
    #+BEGIN_SRC dot :file feature_async_processor
    digraph P {
      a->b
      b->c
    }
    #+END_SRC

*** Processor logic
    #+BEGIN_SRC python :python python3
    from functools import reduce
    from typing import List

    def extract(xs: List[int]) -> int:
	return reduce(lambda acc, x: acc ^ x, xs)
    #+END_SRC
** Benchmarks
   | source  |   50th |  90th |   95th |
   |---------+--------+-------+--------|
   | kinesis | 121.32 | 423.2 | 425.91 |
   | kafka   | 242.11 | 324.3 | 340.81 |

Of note is the PROPERTIES drawer. Here I have disabled the author and date from the exported document. To make working with snake_case languages easier, we can partially disable underscore substrings with ^:{} 1. I have also defined a file name so that I don’t have to rename agenda.pdf (or your filename of choice) each time I export.

Now put your cursor at the topmost subtree of your design doc, org-export-dispatch, C-s to restrict to just that subtree, and choose Export to LaTeXAs PDF file. Now you have a very fancy PDF in the same folder as your org file, ready for upload to google drive.

ODT

If you expect to need to collaboratively edit your document, you can export as an ODT file instead. I’ve found that the ODT file looks like a dog’s breakfast in the google drive viewer, but clicking “open in google docs” fixes it right up.

Helpful functions

Many diagrams, especially thin ones, tend to take up a whole page. With a little bit of elisp, we can restrict their size to something manageable.

The wrap_width function decorates the file output with a couple of attributes to tell both the LaTeX exporter and ODT exporter to constrain the output size of the embedded image.

#+NAME: wrap_width
#+BEGIN_SRC elisp :var data="" :var width="10cm" :results output
(princ (format "#+ATTR_LATEX: :width %s\n" width))
(princ (format "#+ATTR_ODT: :width %s\n" (s-replace "cm" "" width)))
(princ data)
#+END_SRC

Example usage:

#+BEGIN_SRC dot :file wrap_example.png :post wrap_width(data=*this*, width="7cm") :results drawer :exports results
digraph G {
 a -> b
 b -> c
 c -> d
}
#+END_SRC

#+RESULTS:
:RESULTS:
#+ATTR_LATEX: :width 7cm
#+ATTR_ODT: :width 7
[[file:wrap_example.png]]
:END:

Other thoughts

Hidden subtrees

You might wish to keep some subtrees within your document that don’t get exported to the PDF. You can simply add the :noexport: tag to these subtrees.

* secret ideas                                                     :noexport:
   my manager smells

Ditaa

Ditaa2 is a useful tool for drawing system diagrams. You can edit the source block in artist-mode to draw what you need.

Org-mode does tend to convert spaces to tabs, which will quickly ruin your diagrams. To get around this, I added the following to my init.el

;; init.el
(add-hook 'org-mode-hook (lambda () (setq indent-tabs-mode nil)))