The focus of ExDocMakeup is always syntax highlighting, of course. But then I started wondering: I already have a custom markdown implementation included in ExDocMakeup. Currently it highlights the code. But I can use it to experiment with other features that may not be desirable for inclusion in ExDoc itself.

Besides being used for API documentation, ExDoc can also be used to author general documents about Elixir (i.e “Guides”). Guides often need to incorporate code fragments, but they can become out of sync with the code, or even contain erros that make them impossible to compile.

Enter the include directive. It allows you to include fragments of code taken from files. It’s invoked inside the markdown file. It’s a standard Earmark plugin (Earmark is the markdown implementation behind ExDoc and ExDocMakeup), and like all plugins, it must appear in a line all by itself, starting with $$ . Currently I support:

Include an entire file

$$ include "lib/my_file.ex"

Include a range of lines (inclusive)

This is inconvenient because line numbers may change if you change the contents of the file

$$ include "lib/my_file.ex", lines: 45..67

Include a block of code:

$$ include "lib/my_file.ex", block: "my_func"

where the block is delimited by comments in the source file:

... # !begin: my_func def my_func(x), do: x + x # !end: my_func ...

I prefer the block format because unlike line numbers, it doesn’t change when you add lines above.

Configuring the language

Include some Elixir code:

$$ include "lib/my_file.ex", lines: 55..66, lang: "elixir"

Include some Python code:

$$ include "lib/external/monty.py", block: "my_python_class", lang: "python"

All options:

Currently (master branch of the GitHub repo), the include directive supports the following options, as shown above:

:lines ( Range.t ) - range of lines; can’t have both :lines and :block

( ) - range of lines; can’t have both and :block ( String.t ) - block delimited by comments; can’t: have both :lines and :block

( ) - block delimited by comments; can’t: have both and :lang - ( String.t ) - programming language

The directive is a normal Elixir function call, extracted using Code.string_to_quoted and then evaluated by a mini-interpreter. This is on purpose: supporting arbitrary Elixir here doesn’t seem very smart; you’d get scoping issues and attack vectors. The current implementation is not 100% safe yet (I need to restrict further the datatypes that can be passed as arguments).

By extracting the code directly from the files, you guarantee that everything is up to date.

Besides that, you can also apply normal quality control to the code fragments (coverage, static analysis, unit testing, etc.).

Example

Suppose you have the following module doc:

defmodule ExDocMakeup do @moduledoc """ ExDoc-compliant markdown processor using [Makeup](https://github.com/tmbb/makeup) for syntax highlighting. This package is optimized to be used with ExDoc, and not alone by itself. It's just [Earmark](https://github.com/pragdave/earmark) customized to use Makeup as a syntax highlighter plus some functions to make it play well with ExDoc. $$ include "lib/ex_doc_makeup/code_renderer.ex", block: "get_options" """

And the lib/ex_doc_makeup/code_rendere.ex file contains the following fragment:

... # !begin: get_options # Get the options from the app's environment defp get_options() do Application.get_env(:ex_doc_makeup, :config_options, %{}) end # !end: get_options ...

When run mix docs , ExDocMakeup will fetch the appropriate fragment, and render:

Feedback?

What do you think of this API? What do you think of the block delimiters ( # !begin: and # !end: )? What other features would you like to have?

In particular, @OvermindDL1, what do you think of something like this to write the upcoming Super Duper ExSpirit Tutorial? Currently we can’t use ExDocMakeup to document ExSpirit, but we can create a “dummy” Mix project where the code of the tutorial lives and then host the ExDocs on Github Pages or something like that (making it an hex package would be useless and would only take space from hex.pm)

Also pinging @josevalim. If this ends up being widely used, it can be merged into ExDoc’s core, as the underlying markdown implementation is the same as the one used by ExDoc (Earmark). In fact, this is something you can implement right now, as it’s mostly independent of Makeup and the rest of ExDoc. It yould be wise to have people experiment with ExDocMakeup first, though.

Inspiration

This feature was inspired by a similar feature in Sphinx, the main python documentation tool: http://www.sphinx-doc.org/en/stable/