Multi-File Org-Babel Tangles with Include Directives
Tags: emacs, org, literateprogramming, leetcode
Published: 2020-04-24 15:00:30

---
title: Multi-File Org-Babel Tangles with Include Directives
published: true
description: Use #+INCLUDE with org-babel to tangle multiple .org files into a single source output, with a Leetcode problem as an example
tags: emacs,org,literateprogramming,leetcode
---

I've recently been using [org-mode](https://orgmode.org/) with [emacs](https://www.gnu.org/software/emacs/) as a [literate programming](https://en.wikipedia.org/wiki/Literate_programming) environment. The way it works is that you embed source blocks in your org files, similar to markdown, but with metadata that says which file to write the output to - and if you did everything right, you can output the source files by running `org-babel-tangle` on the file by mashing `C-c C-v t`.

One use case I had for this was working on [LeetCode](https://leetcode.com/) problems. I'm slowly gearing up to get a new job ([hire me](https://josh.agency)) and so I've been doing the odd problem here and there to stay sharp. In particular, I was working on [Container With Most Water](https://leetcode.com/problems/container-with-most-water/). I will try not to spoil it for people, but I'm going to be using the work I did there as my example.

So for instance, here's a snippet of `./problems/11_container_with_most_water.org` from my git repo that shows what a source block might look like:

```org
* Candidate Class
This class is used for tracking lower and upper bounds on area and for finding
the "best" candidates to test by sorting.

#+BEGIN_SRC python
@functools.total_ordering
class Candidate:
    # Pretend that there's a big ol' class definition here - No spoilers!
    pass
#+END_SRC
```

In this particular instance, all of the code in this file tangles to the same source output, so the metadata is defined in the org file's header at the very top of the file:

```org
#+TITLE: Problem 11: Container With Most Water
#+PROPERTY: header-args      :tangle "../dist/11_container_with_most_water.py"
```

With this configuration, if I tangle the file, which lives in `./problems`, it'll write to `./dist/11_container_with_most_water.py`. From there, I can copy-paste the output into Leetcode and have my solution time out when trying to solve the test case with 15,000 entries.

This is pretty cool, because I can combine my solution with a bunch of notes on things I tried, things I didn't, why I did or didn't try those things, and anything else going on in my head.

Things got more complicated though once I wanted to reuse some code. For instance, I wrote a class for tracing and debugging execution and put it in `./leettrace.org`:

```org
#+TITLE: LeetTrace - pastable snippets for debug tracing
#+PROPERTY: header-args :tangle yes

This is a collection of pastable/includable snippets for debug tracing in my
Leetcode solutions.

* Python
#+BEGIN_SRC python
def pad(item, depth, justify='left'):
    # Elided


class LeetTrace:
    enabled = True
    max_depth = None
    tag_width = 8
    iter_width = 3

    def __init__(self):
        # Elided

    def log(self, message, *args, **kwargs):
        # Prints the message + metadata to the screen

    def __call__(self, *args, **kwargs):
        # A neat trick - You can call the instance like a function

    def log_if(self, pred, message, *args, **kwargs):
        # Logs if pred is truthy

    # ...

    @contextlib.contextmanager
    def context(self, label):
        # Adds the label to logging statements inside a with block

    def iter(self):
        # Log statements include the iteration number

    def sep(self):
        # A helper to separate big logging blocks


trace = LeetTrace()
#+END_SRC
```

I posted the full source, plus a JavaScript implementation, [in my gists](https://gist.github.com/jfhbrook/96d3cd717f12a888e6e6588f3152909e). Feel free to steal this!

But this is where I ran into issues. Generally, an org file can tangle to multiple source files, but tangling multiple org files into a single source file is more difficult.

I wanted to use [an #+INCLUDE directive](https://orgmode.org/manual/Include-Files.html) to inline this logger/tracer into my solution:

```org
#+INCLUDE: "../leettrace.org::*Python"
```

If you [export](https://orgmode.org/manual/Exporting.html) this org file, org will inline everything under the "Python" headline (the `* Python` line in the snippet from `./leettrace.org`) in the output.

Unfortunately, `org-babel-tangle` doesn't do this step, which is a problem.

The way I solved this was by exporting my org file to another org file. Org supports exporting to all sorts of file formats, and one of them is org itself. I exported the file `./problems/11_container_with_most_water.org` to `./exports/11_container_with_most_water.org`, and then tangled *that* file to `./dist/11_container_with_most_water.py`.

I put this in an [Emacs lisp](https://www.gnu.org/software/emacs/manual/html_node/eintr/) script at `./build.el`, which looks like this:

```elisp
(require 'seq)
(require 'org)
(require 'ob-tangle)
(require 'ox-org)

(let* ((problems (expand-file-name "./problems/"))
       (exports (expand-file-name "./exports/"))
       (dist (expand-file-name "./dist/"))
       (filenames
        (seq-filter
         (lambda (f) (string-suffix-p ".org" f))
         (directory-files problems)))
       (acc nil))
  (dolist (f filenames acc)
    (let ((problem-file (concat problems f))
          (export-file (concat exports f)))
      (progn
        (format-message "Exporting %s to %s..." problem-file export-file)
        (with-current-buffer (find-file-noselect problem-file)
          (org-export-to-file 'org export-file))
        (with-current-buffer (find-file-noselect export-file)
          (org-babel-tangle))))))
```

I can then run this file with `emacs.exe --batch --load build.el`. Props to [u/celeritasCelery on Reddit](https://www.reddit.com/r/emacs/comments/g7avkh/multifile_orgbabel_tangles_with_include_directives/foh8rvd/) for helping me golf out a use of [s.el](https://github.com/magnars/s.el), ensuring that I didn't have to manually add libraries to Emacs' load path!