<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://wardbrian.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://wardbrian.github.io/" rel="alternate" type="text/html" /><updated>2025-12-03T16:15:08-05:00</updated><id>https://wardbrian.github.io/feed.xml</id><title type="html">Brian Ward’s Pages</title><subtitle>personal description</subtitle><author><name>Brian Ward</name><email>bward@flatironinstitute.org</email></author><entry><title type="html">Introducing clay-rpi-matrix</title><link href="https://wardbrian.github.io/blog/2025/10/clay-rpi-matrix" rel="alternate" type="text/html" title="Introducing clay-rpi-matrix" /><published>2025-10-28T00:00:00-04:00</published><updated>2025-10-28T00:00:00-04:00</updated><id>https://wardbrian.github.io/blog/2025/10/clay-matrix</id><content type="html" xml:base="https://wardbrian.github.io/blog/2025/10/clay-rpi-matrix"><![CDATA[<h1 id="using-a-high-performance-layout-engine-when-you-only-have-2000-pixels">Using a high performance layout engine when you only have 2000 pixels</h1>

<p>In a rare success (for me, at least) of the algorithmically-suggested internet,
YouTube recently started surfacing <a href="https://www.nicbarker.com/">Nic Barker</a>’s
videos in my recommendations. I had never previously interacted with his videos,
or his code, but they’re well produced and cover topics that interest me like
data oriented design.</p>

<p>One of his recent-ish projects, which was the topic of the
<a href="https://www.youtube.com/watch?v=DYWTw19_8r4">first video I watched in full</a>, is
<a href="https://github.com/nicbarker/clay">clay</a>. Clay (<strong>C</strong> <strong>Lay</strong>out) is a library
for doing the kinds of things CSS/HTML do - laying out nested hierarchy’s of
declarative UI items, expanding to fit text, etc.</p>

<p>It’s a rather nifty library, and the design is very nicely modularized,
separating laying out a UI from actually rendering it. It turns out, rendering
a UI once it is laid out mostly just requires you to be able to draw some
rectangles and text, so it’s a nice, small API surface to generalize over.</p>

<p>Clay piqued my interest, but I am not often working on GUIs (and
<a href="https://stan-playground.flatironinstitute.org/">when I am</a>, I have access to
HTML/CSS, and might as well use that). But I do work on a project that needs to
control a display.</p>

<p>The <a href="https://github.com/MLB-LED-Scoreboard/mlb-led-scoreboard">mlb-led-scoreboard</a> project
predates my fascination with baseball, but
<a href="/coding-portfolio/mlb-led-scoreboard/">a pandemic rewrite and MLB API deprecation landed me a maintainer role</a>
in this small-but-dedicated community.</p>

<p>I even got shipped a hand made wooden frame for my matrix as a thank-you by a user:</p>

<p><img src="/images/code/mlb-led-scoreboard.jpg" alt="MLB LED Scoreboard" /></p>

<p>These matrices are quite small. Mine shown above is 64 pixels wide by 32 tall.
The smallest we “support” is 32x32, and the largest is 192x128 (which, I
believe, is only possible by chaining several smaller matrices together).</p>

<p>The UI is essentially entirely hard-coded, with customization primarily coming
from letting you tweak a coordinates.json file. This allows a lot of
customization, but is still limiting if you have a vision that we didn’t
anticipate or provide a flag for. And it’s entirely absolutely positioned - you
need to count pixels and do some back-of-the-envelope math to get a nice looking
result.</p>

<p>Anyway, if it’s not immediately obvious where this is going, I decided to throw
a layout engine powerful enough that
<a href="https://www.nicbarker.com/clay">its own website is implemented in a WASM-compiled version of itself</a>
at a LED Matrix with 2,048 total pixels.</p>

<p>The <a href="https://github.com/WardBrian/clay-rpi-matrix">clay-rpi-matrix</a> library is
the result. It’s a bit rough around the edges, and I made some choices for
simplicity, like including all the dependencies as submodules and keeping the
entire implementation in one <code class="language-plaintext highlighter-rouge">#include</code>-able <code class="language-plaintext highlighter-rouge">.c</code> file, but it does enough of
the basics to be usable in pratice.</p>

<h2 id="rendering">Rendering</h2>

<p>There already exists a <a href="https://github.com/hzeller/rpi-rgb-led-matrix">fantastic open source library</a>
for actually “driving” the matrices from Raspberry Pi’s, so the project
essentially consists of translating the render commands that clay outputs into
calls of that API, which provides a C wrapper for convenience (though I did need
to upstream one small patch to get text rendering sizes properly working).</p>

<p>The trickiest bits are around rounded corners, since the native support for
circles in such an environment is poor. Rather than using something clever like
<a href="https://en.wikipedia.org/wiki/Midpoint_circle_algorithm">Bresenham’s algorithm</a>,
I ended up just doing the simple double for-loop. The biggest circle you can
draw is only gonna be so big on these things, and I think even an old Pi can run
C fast enough for it to not really matter…</p>

<p>Besides that, it was all pretty straightforward. I found the existing Raylib
renderer for clay to be the most readable, so that was my template. The result?</p>

<p><img src="/images/posts/clay-matrix-example.jpg" alt="Clay Matrix Example" /></p>

<p><strong>Is this silly?</strong></p>

<p>Yes.</p>

<p><strong>Will we even use it for the scoreboard project?</strong></p>

<p>Doubtful, since we have so much work already invested and so many existing configs out there…
not to mention that, while a clay layout is in theory much more able to be customized,
it does generally require a recompilation if changed.</p>

<p>But, it gave me an excuse to write some C with some nice libraries and embrace
the “simplicity” people always talk about.</p>]]></content><author><name>Brian Ward</name><email>bward@flatironinstitute.org</email></author><category term="c" /><category term="rpi" /><category term="led-matrix" /><category term="guis" /><summary type="html"><![CDATA[Using a high performance layout engine when you only have 2000 pixels]]></summary></entry><entry><title type="html">Polyglot Sphinx Documentation</title><link href="https://wardbrian.github.io/blog/2025/02/polyglot-sphinx" rel="alternate" type="text/html" title="Polyglot Sphinx Documentation" /><published>2025-02-24T00:00:00-05:00</published><updated>2025-02-24T00:00:00-05:00</updated><id>https://wardbrian.github.io/blog/2025/02/polyglot-sphinx</id><content type="html" xml:base="https://wardbrian.github.io/blog/2025/02/polyglot-sphinx"><![CDATA[<h1 id="a-user-report-from-documenting-too-many-languages">A user report from documenting too many languages</h1>

<p>I like programming languages. The more of them the better. I have even had the
pleasure of working on a couple projects with code in 5 or more languages at once.</p>

<p>This is what I call fun.</p>

<p>The fun part gets a little less fun when you want to create a documentation site
for one of these projects. But, it can be done. I might even dare to say that it can be
done in a way that isn’t horrible. This is more of a family recipe than a cookbook –
I think everyone’s set-up will be just different enough to make a true tutorial impossible.</p>

<h2 id="the-project">The project</h2>

<p>While &gt;=5 languages is a lot, I expect a lot of people will have to deal with at least
2, at some point. Any sufficiently advanced Python project will eventually collect some
compiled code, and many scientific projects will start out in C++ or Fortran before
realizing the users are in Python or Julia, and they need some wrappers.</p>

<p>In my cases, I have had a C++ core and wrappers in (some subset of)
Python, R, Julia, TypeScript, and Rust.</p>

<p>Each of these languages has its own documentation style and native tools for emitting it
in various formats. But I want one unified way to generate a website which documents all of them.</p>

<h2 id="the-goal">The goal</h2>

<p>Eventually, we’d like a website that has <a href="https://roualdes.github.io/bridgestan/latest/languages.html">a “languages” page</a>.
This page will have one subpage for each language, and that
subpage will have roughly the same format:</p>

<ul>
  <li>Installation</li>
  <li>An example program</li>
  <li>The API documentation</li>
</ul>

<p>The goal is that this third section will always be generated from
the source code. Additionally, we’d like this source code to be annotated
following the standard practices for the specific language it is written in.
Doing so makes it easier for people who are familiar with a given language to
contribute, and it keeps things like built-in help systems working.</p>

<p>It is <strong>not</strong> a goal that these pages look <em>exactly</em> the same.</p>

<h2 id="my-solution">My solution</h2>

<p>I decided to start with <a href="https://www.sphinx-doc.org/">Sphinx</a>.
This tool is likely familiar to many Python users, but it is not as Python-specific
as it first appears. A crucial thing that sets Sphinx apart is that it is <em>highly</em> programmable,
both through an official extension API, and through the ability to inject whatever
code you want into the documentation build process through
<a href="https://www.sphinx-doc.org/en/master/usage/configuration.html">Sphinx’s <code class="language-plaintext highlighter-rouge">conf.py</code> file</a>.</p>

<p>Therefore, all you need to do to get a Polygot Sphinx site is the same as a polyglot human:
teach it a lot of languages.</p>

<p>The basics for a new language are:</p>

<ul>
  <li>Investigate if the language has a Sphinx extension. For example, any Doxygen-using
code can be documented with <a href="https://breathe.readthedocs.io/">breathe</a>.</li>
  <li>If not, see if you can output the documentation in a format that Sphinx can understand.
Natively this is reStructuredText, but the <a href="https://myst-parser.readthedocs.io/">myst-parser</a>
extension adds support for a pretty wide range of Markdown-like formats, which ends up being more
useful for this task.</li>
  <li>If the above don’t already exist, you can either write your own, or bail out to generating web pages
and either embedding them in the Sphinx site, or simply linking to them.</li>
</ul>

<table>
  <thead>
    <tr>
      <th>Language</th>
      <th>Native Tool</th>
      <th>Sphinx Extension</th>
      <th>Markdown Outputting Tool</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>C++</td>
      <td><a href="https://www.doxygen.nl/">Doxygen</a></td>
      <td><a href="https://breathe.readthedocs.io">breathe</a></td>
      <td>several, none that are well-regarded enough to mention</td>
    </tr>
    <tr>
      <td>Python</td>
      <td><a href="https://www.sphinx-doc.org/">Sphinx</a></td>
      <td>(builtin)</td>
      <td>similarly, several options depending on docstring style</td>
    </tr>
    <tr>
      <td>Julia</td>
      <td><a href="https://documenter.juliadocs.org">Documenter.jl</a></td>
      <td>Sphinx-Julia (broken!)</td>
      <td><a href="https://documentermarkdown.juliadocs.org">DocumenterMarkdown.jl</a></td>
    </tr>
    <tr>
      <td>TypeScript</td>
      <td><a href="https://jsdoc.app/">JSDoc</a></td>
      <td><a href="https://github.com/mozilla/sphinx-js">sphinx-js</a></td>
      <td><a href="https://github.com/jsdoc2md/jsdoc-to-markdown">jsdoc2md</a></td>
    </tr>
    <tr>
      <td>R</td>
      <td><a href="https://roxygen2.r-lib.org/">roxygen2</a></td>
      <td>-</td>
      <td><a href="https://github.com/Genentech/rd2markdown">rd2markdown</a></td>
    </tr>
    <tr>
      <td>Rust</td>
      <td><a href="https://doc.rust-lang.org/rustdoc/">rustdoc</a></td>
      <td><a href="https://gitlab.com/munir0b0t/sphinxcontrib-rust">sphinxcontrib-rust</a></td>
      <td>ish: <a href="https://github.com/webern/cargo-readme">cargo-readme</a>, <a href="https://github.com/msrd0/cargo-doc2readme">cargo-doc2readme</a></td>
    </tr>
  </tbody>
</table>

<p>Note that the final column is mostly complete – meaning that
people seeking to use Markdown-friendly (non-Sphinx) tools still have
a good chance of success, even if the exact tips I give here may not apply.</p>

<p>The rest of this work is a series of tips, tricks, and code snippets for each of the
languages I have worked with. I hope it helps!</p>

<p>The examples are all taken from either the
<a href="https://github.com/roualdes/bridgestan">BridgeStan</a> or <a href="https://github.com/WardBrian/tinystan">TinyStan</a> projects.
If you just want to see what the output looks like, you can browse the language sections of both sites:</p>

<ul>
  <li><a href="https://roualdes.github.io/bridgestan/latest/languages.html">https://roualdes.github.io/bridgestan/latest/languages.html</a></li>
  <li><a href="https://brianward.dev/tinystan/latest/languages.html">https://brianward.dev/tinystan/latest/languages.html</a></li>
</ul>

<h2 id="general-tip-good-sphinx-defaults">General tip: Good Sphinx defaults</h2>

<p>I’m not going to fully explain Sphinx and reStructuredText here. There are many
good guides out there, and plenty of examples in the wild. However, if you
are just hoping to get a site up and running, here are some good extensions:</p>

<ul>
  <li>If you don’t like any of the built-in themes, I tend to use
<a href="https://pydata-sphinx-theme.readthedocs.io/en/latest/"><code class="language-plaintext highlighter-rouge">pydata-sphinx-theme</code></a>.
In particular, it has nice support for dark mode and multiple versions of the documentation if
you want to support those.</li>
  <li>I basically always throw <a href="https://pypi.org/project/sphinx-copybutton/"><code class="language-plaintext highlighter-rouge">sphinx_copybutton</code></a>
on my extensions list. It adds a “copy” button to all code blocks.</li>
  <li>A few of the <a href="https://www.sphinx-doc.org/en/master/usage/extensions/index.html">built-in, language-agnostic extensions</a>
are also good. A few ones worth considering are <code class="language-plaintext highlighter-rouge">sphinx.ext.mathjax</code> if you want to write LaTeX,
<code class="language-plaintext highlighter-rouge">sphinx.ext.viewcode</code> and <code class="language-plaintext highlighter-rouge">sphinx.ext.linkcode</code> to provide links to the source code in documentation,
and <code class="language-plaintext highlighter-rouge">sphinx.ext.autosectionlabel</code> to automatically generate anchors for each section.</li>
</ul>

<h2 id="general-tip-making-a-language-optional">General tip: Making a language optional</h2>

<p>Any polygot project is liable to end up with developers who are only interested in one
of the languages. It’s nice if they don’t have to install all the others to
edit and build the documentation.</p>

<p>I use a common idiom for this in the couple projects I maintain. The <code class="language-plaintext highlighter-rouge">conf.py</code> fails
gracefully on missing dependencies, <em>unless</em> the build is happening in a CI environment.</p>

<p>For example:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">subprocess</span>

<span class="c1"># handles the common cases of GitHub Actions and ReadTheDocs
</span><span class="n">RUNNING_IN_CI</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"CI"</span><span class="p">)</span> <span class="ow">or</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"READTHEDOCS"</span><span class="p">)</span>
<span class="n">BASE_DIR</span> <span class="o">=</span> <span class="n">pathlib</span><span class="p">.</span><span class="n">Path</span><span class="p">(</span><span class="n">__file__</span><span class="p">).</span><span class="n">parent</span><span class="p">.</span><span class="n">parent</span>

<span class="c1"># LANGUAGE A
</span><span class="k">try</span><span class="p">:</span>
    <span class="c1"># code that imports the extension or tries to run the native tool, e.g.:
</span>    <span class="k">print</span><span class="p">(</span><span class="s">"Checking C++ doc availability"</span><span class="p">)</span>
    <span class="kn">import</span> <span class="nn">breathe</span>
    <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">([</span><span class="s">"doxygen"</span><span class="p">,</span> <span class="s">"-v"</span><span class="p">],</span> <span class="n">check</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="c1"># if we are in a CI environment, we want to know that we're missing a dependency
</span>    <span class="k">if</span> <span class="n">RUNNING_IN_CI</span><span class="p">:</span>
        <span class="k">raise</span> <span class="n">e</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># otherwise, we can just not build for this language
</span>        <span class="k">print</span><span class="p">(</span><span class="s">"Breathe/doxygen not installed, skipping C++ Doc"</span><span class="p">)</span>
        <span class="n">exclude_patterns</span> <span class="o">+=</span> <span class="p">[</span><span class="s">"languages/c-api.rst"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
    <span class="c1"># if the above check succeeded, we can now do whatever language-specific config
</span>    <span class="n">extensions</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"breathe"</span><span class="p">)</span>

<span class="c1"># LANGUAGE B ...
</span></code></pre></div></div>

<p>A would-be contributor will still need Sphinx and its Python dependencies,
but they won’t need the complete set of tools for every language supported by
the project just to build the docs.</p>

<p>This also works well if the markdown outputs of languages without Sphinx support
are checked into the repository – the build will just use (possibly stale)
checked-in versions of those pages, which is fine for a user who isn’t working on them.</p>

<h2 id="doxygen-family-c-c-fortran-etc">Doxygen family: C, C++, Fortran, etc.</h2>

<p><a href="https://www.doxygen.nl/">Doxygen</a> supports a surprising number of languages
(“C, Python, PHP, Java, C#, Objective-C, Fortran, VHDL, Splice, IDL, and Lex”, according to their homepage),
but it is the “default” tool for C and C++.</p>

<p>The <a href="https://breathe.readthedocs.io/en/latest/"><code class="language-plaintext highlighter-rouge">breathe</code> extension</a> is <em>the</em> best option for any
Doxygen-using code. It produces the nicest looking output of any of the tools I will describe in
this post, rivaling the built-in Python support.</p>

<p>I also showed the majority of the configuration you will need in the tip above.
Besides checking that the library and <code class="language-plaintext highlighter-rouge">doxygen</code> executable are available, you
will need to tell Breathe a bit about your project. Here’s the entire rest of the config for
one of my projects (still in <code class="language-plaintext highlighter-rouge">conf.py</code>, and note that because these are just variable
declarations they’re harmless to put outside the <code class="language-plaintext highlighter-rouge">try</code> block):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># output directory for generated files
</span><span class="n">breathe_projects</span> <span class="o">=</span> <span class="p">{</span><span class="s">"bridgestan"</span><span class="p">:</span> <span class="s">"./_build/cppxml/"</span><span class="p">}</span>
<span class="n">breathe_default_project</span> <span class="o">=</span> <span class="s">"bridgestan"</span>
<span class="c1"># where to find your Doxygen-commented code
</span><span class="n">breathe_projects_source</span> <span class="o">=</span> <span class="p">{</span><span class="s">"bridgestan"</span><span class="p">:</span> <span class="p">(</span><span class="s">"../src/"</span><span class="p">,</span> <span class="p">[</span><span class="s">"bridgestan.h"</span><span class="p">])}</span>
</code></pre></div></div>

<p>The actual documentation page can then use the <code class="language-plaintext highlighter-rouge">:autodoxygenfile:</code> directive
to generate the documentation for a file. For example:</p>

<pre><code class="language-rst">.. autodoxygenfile:: bridgestan.h
    :project: bridgestan
    :sections: func typedef var
</code></pre>

<p>The Breathe documentation covers a lot more options, and is worth a read if you
are using Doxygen for your project, but the above should get you pretty far.</p>

<h3 id="bonus-tip-macro-preprocessing">Bonus tip: Macro preprocessing</h3>

<p>If your C(++) code has macros, especially ones that expand to <code class="language-plaintext highlighter-rouge">__attribute</code> or other items that change
the signature of the function you’re documenting, you may find that these degrade the appearance of the
result from Doxygen. In these cases, you might get better results by enabling some <a href="https://www.doxygen.nl/manual/preprocessing.html">preprocessing
in Doxygen</a> through some extra configuration in <code class="language-plaintext highlighter-rouge">conf.py</code>
and making them expand to something more friendly, such as the empty string.</p>

<p>For example, BridgeStan has a <code class="language-plaintext highlighter-rouge">BS_PUBLIC</code> macro which is used to mark functions
as exported from a shared library (using <code class="language-plaintext highlighter-rouge">__attribute</code> and <code class="language-plaintext highlighter-rouge">__declspec</code>). The
following configuration in <code class="language-plaintext highlighter-rouge">conf.py</code> tells Doxygen to preprocess the code before
parsing, and in particular to replace <code class="language-plaintext highlighter-rouge">BS_PUBLIC</code> with nothing:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">breathe_doxygen_config_options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"ENABLE_PREPROCESSING"</span><span class="p">:</span> <span class="s">"YES"</span><span class="p">,</span>
    <span class="s">"MACRO_EXPANSION"</span><span class="p">:</span> <span class="s">"YES"</span><span class="p">,</span>
    <span class="s">"EXPAND_ONLY_PREDEF"</span><span class="p">:</span> <span class="s">"YES"</span><span class="p">,</span>
    <span class="s">"PREDEFINED"</span><span class="p">:</span> <span class="s">"BS_PUBLIC="</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="python">Python</h2>

<p>Python has built-in support in Sphinx. I hesitate to suggest that I have much
to add for people who have already used Sphinx for Python, but I will mention
a couple extra-useful extensions and options:</p>

<h3 id="intersphinx">Intersphinx</h3>

<p>Sphinx has a built-in feature called <a href="https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html">intersphinx</a>
which allows you to link to other Sphinx-built documentation frictionlessly.
Just add <code class="language-plaintext highlighter-rouge">"sphinx.ext.intersphinx"</code> to your <code class="language-plaintext highlighter-rouge">extensions</code> list in <code class="language-plaintext highlighter-rouge">conf.py</code>,
and then add a <code class="language-plaintext highlighter-rouge">intersphinx_mapping</code> dictionary to the same file. For example:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">intersphinx_mapping</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"python"</span><span class="p">:</span> <span class="p">(</span> <span class="s">"https://docs.python.org/3/"</span><span class="p">,</span> <span class="bp">None</span><span class="p">),</span>
    <span class="s">"numpy"</span><span class="p">:</span> <span class="p">(</span><span class="s">"https://numpy.org/doc/stable/"</span><span class="p">,</span> <span class="bp">None</span><span class="p">),</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, a <a href="https://www.sphinx-doc.org/en/master/usage/domains/python.html#cross-referencing-python-objects">cross-reference directive</a> like</p>

<pre><code class="language-rst">:py:func:`os.path.join`
</code></pre>

<p>will link directly to the Python documentation for <code class="language-plaintext highlighter-rouge">os.path.join()</code> on docs.python.org.</p>

<h3 id="autodoc">Autodoc</h3>

<p>Another built-in extension, <a href="https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html">autodoc</a>,
provides the Python equivalents of the <code class="language-plaintext highlighter-rouge">autodoxygenfile</code> directive I showed above.</p>

<p>For example, it lets you write blocks like</p>

<pre><code class="language-rst">.. autoclass:: bridgestan.StanModel
   :members:
</code></pre>

<p>and automatically generate documentation for that class and all its members.</p>

<p>This can save a lot of boilerplate, and is extra helpful because it can automatically pick
up new methods as you add them. There are a lot more options than just <code class="language-plaintext highlighter-rouge">autoclass</code>, so
I recommend reading more in the documentation.</p>

<h2 id="julia">Julia</h2>

<p>Julia is the first language in the “Get it to generate Markdown” category. There
is a <code class="language-plaintext highlighter-rouge">Sphinx-Julia</code> package in existence, but it was last substantially updated in 2018,
and I have been unable to get it to work with modern versions of Sphinx.</p>

<p>Luckily, Julia makes the task of generating markdown relatively easy. Julia packages
typically use the <a href="https://documenter.juliadocs.org"><code class="language-plaintext highlighter-rouge">Documenter.jl</code></a> package to
generate documentation, and there is a
<a href="https://github.com/JuliaDocs/DocumenterMarkdown.jl"><code class="language-plaintext highlighter-rouge">DocumenterMarkdown.jl</code></a>
package that can be asked to generate Markdown files which have been processed,
e.g. by expanding <code class="language-plaintext highlighter-rouge">@docs</code> sections and linking to the source code. This plays
nicely with unrecognized Markdown directives, so you can use myST directives
in the Julia documentation and they will be passed through to allow Sphinx to
render them.</p>

<p>There’s a catch, of course, which is that DocumenterMarkdown is not very actively
maintained, so it does require an older version of Documenter.jl. This has been
fine in my experience, as many of the more recent versions of Documenter.jl seem
to be focused on improving HTML output, which we don’t need. Still, it’s worth being
aware of.</p>

<p>At any rate, the config is relatively simple. You write your Julia docs as you would
for any Julia package, except you set the <code class="language-plaintext highlighter-rouge">format</code> argument to <code class="language-plaintext highlighter-rouge">Markdown</code> in the
<code class="language-plaintext highlighter-rouge">makedocs</code> call. I also copy the generated files into the Sphinx source directory.
Here’s my entire <code class="language-plaintext highlighter-rouge">make.jl</code> file:</p>

<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="n">Documenter</span><span class="x">,</span> <span class="n">BridgeStan</span>
<span class="k">using</span> <span class="n">DocumenterMarkdown</span>

<span class="n">makedocs</span><span class="x">(</span>
    <span class="n">format</span> <span class="o">=</span> <span class="n">Markdown</span><span class="x">(),</span>
    <span class="n">repo</span> <span class="o">=</span> <span class="s">"https://github.com/WardBrian/TinyStan/blob/main{path}#{line}"</span><span class="x">,</span>
<span class="x">)</span>

<span class="n">cp</span><span class="x">(</span>
    <span class="n">joinpath</span><span class="x">(</span><span class="nd">@__DIR__</span><span class="x">,</span> <span class="s">"build/julia.md"</span><span class="x">),</span>
    <span class="n">joinpath</span><span class="x">(</span><span class="nd">@__DIR__</span><span class="x">,</span> <span class="s">"../../../docs/languages/julia.md"</span><span class="x">);</span>
    <span class="n">force</span> <span class="o">=</span> <span class="nb">true</span><span class="x">,</span>
<span class="x">)</span>
<span class="n">cp</span><span class="x">(</span>
    <span class="n">joinpath</span><span class="x">(</span><span class="nd">@__DIR__</span><span class="x">,</span> <span class="s">"build/assets/Documenter.css"</span><span class="x">),</span>
    <span class="n">joinpath</span><span class="x">(</span><span class="nd">@__DIR__</span><span class="x">,</span> <span class="s">"../../../docs/_static/css/Documenter.css"</span><span class="x">);</span>
    <span class="n">force</span> <span class="o">=</span> <span class="nb">true</span><span class="x">,</span>
<span class="x">)</span>
</code></pre></div></div>

<p>Back in Sphinx’s <code class="language-plaintext highlighter-rouge">conf.py</code>, you’ll now need the <code class="language-plaintext highlighter-rouge">myst-parser</code> extension to read the Markdown,
and to make sure the Documenter css is included:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">extensions</span> <span class="o">=</span> <span class="p">[</span>
    <span class="c1"># ...
</span>    <span class="s">"myst_parser"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">suppress_warnings</span> <span class="o">=</span> <span class="p">[</span><span class="s">"myst.xref_missing"</span><span class="p">]</span> <span class="c1"># Julia doc generates raw html links
</span><span class="n">html_static_path</span> <span class="o">=</span> <span class="p">[</span><span class="s">"_static"</span><span class="p">]</span>
<span class="n">html_css_files</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s">"css/Documenter.css"</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Finally, if you want to have the Julia doc build run as part of the Sphinx build,
you can use another <code class="language-plaintext highlighter-rouge">try</code> block like the one I showed for C++ above:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">try</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Building Julia doc"</span><span class="p">)</span>
    <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span>
        <span class="p">[</span><span class="s">"julia"</span><span class="p">,</span> <span class="s">"--project=."</span><span class="p">,</span> <span class="s">"./make.jl"</span><span class="p">],</span>
        <span class="n">cwd</span><span class="o">=</span><span class="n">BASE_DIR</span> <span class="o">/</span> <span class="s">"clients"</span> <span class="o">/</span> <span class="s">"julia"</span> <span class="o">/</span> <span class="s">"docs"</span><span class="p">,</span>
        <span class="n">check</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>
<span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="c1"># fail loudly in Github Actions
</span>    <span class="k">if</span> <span class="n">RUNNING_IN_CI</span><span class="p">:</span>
        <span class="k">raise</span> <span class="n">e</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Failed to build julia docs!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="bonus-tip-helping-people-navigate-duplicated-files">Bonus tip: Helping people navigate duplicated files.</h3>

<p>If you use this method (and follow my advice to check-in the copied result in the Sphinx folder),
there is one downside: There will be two <code class="language-plaintext highlighter-rouge">julia.md</code> files in your repository. One, the actual
source, will be in the directory of your Julia package, and the other, which is overwritten on
build, will be in the Sphinx directory.</p>

<p>To help people keep this straight, I add a note to the top of the source Markdown
file in a <code class="language-plaintext highlighter-rouge">@raw html</code> block, which gets reproduced in the Sphinx output. For example:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">@raw html
</span><span class="sb">% NB: If you are reading this file in docs/languages, you are reading a generated output!
% This should be apparent due to the html tags everywhere.
% If you are reading this in julia/docs/src, you are reading the true source!
% Please only make edits in the julia/docs/src file, since the first is DELETED each re-build.</span>
<span class="p">```</span>
</code></pre></div></div>

<h2 id="typescript-and-javascript">TypeScript (and JavaScript)</h2>

<p>My advice here assumes you’ve documented your TypeScript code with JSDoc-style comments.</p>

<p>There is a <code class="language-plaintext highlighter-rouge">sphinx-js</code> <a href="https://github.com/mozilla/sphinx-js">extension from Mozilla</a> that can
read JSDoc comments, but it was marked as a public archive during the writing of this post.
There does appear to be <a href="https://github.com/pyodide/sphinx-js-fork">a fork by the Pyodide project</a>
that is still active.</p>

<p>I didn’t know about this package before now, so I actually built a different solution,
based around a tool called <a href="https://github.com/jsdoc2md/jsdoc-to-markdown"><code class="language-plaintext highlighter-rouge">jsdoc2md</code></a>.
Which I guess I will be sticking to, at least until the sphinx-js maintenance status
is more clear.</p>

<p>My existing solution is pretty similar to the Julia case, where I build a Markdown file
and copy it into the Sphinx source directory, but it requires a bit more configuration.</p>

<p>First, there are some JavaScript developer dependencies for your <code class="language-plaintext highlighter-rouge">package.json</code>.
Note that the <code class="language-plaintext highlighter-rouge">babel</code> mentions are only necessary if you are using TypeScript,
and <code class="language-plaintext highlighter-rouge">@godaddy/dmd</code> is technically optional, but I have found it to greatly improve
the quality of the rendered output.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@babel/cli"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^7.25.9"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"@babel/core"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^7.26.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"@babel/preset-env"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^7.26.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"@babel/preset-typescript"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^7.26.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"@godaddy/dmd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^1.0.4"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"jsdoc-babel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^0.5.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"jsdoc-to-markdown"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.0.5"</span><span class="p">,</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>(version numbers are just what I have installed, you can probably use newer ones)</p>

<p>And a configuration file for jsdoc2md. I have this live in a <code class="language-plaintext highlighter-rouge">doc</code> sub-folder of
the TypeScript client. Again, a lot of it is really only necessary for TS:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"includePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">".+</span><span class="se">\\</span><span class="s2">.ts(doc|x)?$"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"excludePattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">".+</span><span class="se">\\</span><span class="s2">.(test|spec).ts"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">"language-doc.hbs"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"plugins"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"plugins/markdown"</span><span class="p">,</span><span class="w"> </span><span class="s2">"node_modules/jsdoc-babel"</span><span class="p">,</span><span class="w"> </span><span class="s2">"@godaddy/dmd"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"heading-depth"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
  </span><span class="nl">"babel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"extensions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ts"</span><span class="p">,</span><span class="w"> </span><span class="s2">"tsx"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"ignore"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"**/*.(test|spec).ts"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"babelrc"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"presets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">[</span><span class="s2">"@babel/preset-env"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"targets"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"node"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}],</span><span class="w">
      </span><span class="s2">"@babel/preset-typescript"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"plugins"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The template file <code class="language-plaintext highlighter-rouge">language-doc.hbs</code> (also in this <code class="language-plaintext highlighter-rouge">typescript/doc/</code> directory)
is a <a href="https://handlebarsjs.com/">Handlebars</a>
file that gets filled with the output of the jsdoc2md command. A really barebones
example would just be ``, which includes the main template from the
<code class="language-plaintext highlighter-rouge">jsdoc-to-markdown</code> package. I use
<a href="https://github.com/WardBrian/tinystan/blob/85836d28060892e216b1e66cd100a9a3197f6fc7/clients/typescript/doc/language-doc.hbs">mine</a>
to add some installation instructions and the like. Of note is the ability to pre-(re-?)declare
some links to fix broken ones jsdoc2md generates.
<strong>My kingdom for a consistent rule of how markdown section headers get turned into html headers.</strong></p>

<p>Finally, in package.json, I provide a script which helps run it all:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jsdoc2md --template ./doc/language-doc.hbs --plugin @godaddy/dmd --heading-depth=3 --configure ./doc/jsdoc2md.json --files src/*.ts "</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Astute readers will notice that there is some duplicated config between this command and
the json file. At least for the version of jsdoc2md I was using, I couldn’t get it to
read all of the items out of the json file, so some ended up repeated. I left them in the
json file for clarity and aspirational reasons.</p>

<p>This command will write the Markdown to standard output. The rest of the config occurs in
<code class="language-plaintext highlighter-rouge">conf.py</code> to run this command and place the output:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Building JS doc"</span><span class="p">)</span>
    <span class="c1"># this allows you to set 'YARN' to 'npm run', for example
</span>    <span class="n">yarn</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">"YARN"</span><span class="p">,</span> <span class="s">"yarn"</span><span class="p">).</span><span class="n">split</span><span class="p">()</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span>
        <span class="n">yarn</span> <span class="o">+</span> <span class="p">[</span><span class="s">"--silent"</span><span class="p">,</span> <span class="s">"doc"</span><span class="p">],</span>
        <span class="n">cwd</span><span class="o">=</span><span class="n">BASE_DIR</span> <span class="o">/</span> <span class="s">"clients"</span> <span class="o">/</span> <span class="s">"typescript"</span><span class="p">,</span>
        <span class="n">check</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
        <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
        <span class="n">text</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"./languages/js.md"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">ret</span><span class="p">.</span><span class="n">stdout</span><span class="p">)</span>

<span class="c1"># you know the drill...
</span><span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">RUNNING_IN_CI</span><span class="p">:</span>
        <span class="k">raise</span> <span class="n">e</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Failed to build JS docs!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
</code></pre></div></div>

<p>Assuming you have <code class="language-plaintext highlighter-rouge">myst-parser</code> installed and listed
as an extension in <code class="language-plaintext highlighter-rouge">conf.py</code>, you should be good to go with your (Type|Java)Script docs.</p>

<h2 id="r">R</h2>

<p>R is another language in this bucket of “get it to generate Markdown” solutions, but getting
it right also requires a bit more pre- and post-processing, which makes it more interesting
than me just linking to another “something2md” package.</p>

<p>It does, however, <em>involve</em> another “something2md” package. In this case, it’s
<a href="https://github.com/Genentech/rd2markdown"><code class="language-plaintext highlighter-rouge">rd2markdown</code></a>, where <code class="language-plaintext highlighter-rouge">Rd</code> is the
R documentation format. Typically, these files themselves are generated by a package
called <code class="language-plaintext highlighter-rouge">roxygen2</code>, which processes comments in the source code. Of course, <code class="language-plaintext highlighter-rouge">roxygen2</code>’s format is
markdown-adjacent, so this ends up being a lossy process that ultimately converts
something like markdown into something that isn’t, and then back into a more processed
form of markdown. Remember when I said this was fun?</p>

<p>Anyway, while <code class="language-plaintext highlighter-rouge">rd2markdown</code> is on CRAN, the development version on GitHub is more up-to-date,
so I assume that version in the following instructions.</p>

<p>In contrast to my general flow here, I’m actually going to start by showing the <code class="language-plaintext highlighter-rouge">conf.py</code>
code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Building R doc"</span><span class="p">)</span>
    <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span>
        <span class="p">[</span><span class="s">"Rscript"</span><span class="p">,</span> <span class="s">"convert_docs.R"</span><span class="p">],</span>
        <span class="n">cwd</span><span class="o">=</span><span class="n">BASE_DIR</span> <span class="o">/</span> <span class="s">"clients"</span> <span class="o">/</span> <span class="s">"R"</span><span class="p">,</span>
        <span class="n">check</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>

<span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="c1"># fail loudly in Github Actions
</span>    <span class="k">if</span> <span class="n">RUNNING_IN_CI</span><span class="p">:</span>
        <span class="k">raise</span> <span class="n">e</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Failed to build R docs!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
</code></pre></div></div>

<p>So, at this level, it looks almost exactly like Julia. Assuming that the
<code class="language-plaintext highlighter-rouge">convert_docs.R</code> script it is calling in my R client directory isn’t too bad …</p>

<div class="language-R highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Converts R documentation (.Rd) files to markdown (.md) files for use in</span><span class="w">
</span><span class="c1"># Sphinx.</span><span class="w">

</span><span class="n">library</span><span class="p">(</span><span class="n">rd2markdown</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">roxygen2</span><span class="p">)</span><span class="w">

</span><span class="n">roxygen2</span><span class="o">::</span><span class="n">roxygenize</span><span class="p">()</span><span class="w">

</span><span class="n">files</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">list.files</span><span class="p">(</span><span class="s2">"man"</span><span class="p">,</span><span class="w"> </span><span class="n">pattern</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"*.Rd"</span><span class="p">)</span><span class="w">

</span><span class="c1"># we only want to doc exported functions, so we need</span><span class="w">
</span><span class="c1"># to read the NAMESPACE file</span><span class="w">
</span><span class="n">namespace</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">paste0</span><span class="p">(</span><span class="n">readLines</span><span class="p">(</span><span class="s2">"NAMESPACE"</span><span class="p">),</span><span class="w"> </span><span class="n">collapse</span><span class="o">=</span><span class="s2">"\n"</span><span class="p">)</span><span class="w">

</span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">f</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">files</span><span class="p">){</span><span class="w">
    </span><span class="c1"># strip off .Rd</span><span class="w">
    </span><span class="n">name</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">substr</span><span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="n">nchar</span><span class="p">(</span><span class="n">f</span><span class="p">)</span><span class="m">-3</span><span class="p">)</span><span class="w">

    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">grepl</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">namespace</span><span class="p">,</span><span class="w"> </span><span class="n">fixed</span><span class="o">=</span><span class="kc">TRUE</span><span class="p">)){</span><span class="w">
        </span><span class="n">print</span><span class="p">(</span><span class="n">paste0</span><span class="p">(</span><span class="s2">"Skipping unexported "</span><span class="p">,</span><span class="w"> </span><span class="n">name</span><span class="p">))</span><span class="w">
        </span><span class="k">next</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c1"># read .Rd file and convert to markdown</span><span class="w">
    </span><span class="n">rd</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">rd2markdown</span><span class="o">::</span><span class="n">get_rd</span><span class="p">(</span><span class="n">file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">file.path</span><span class="p">(</span><span class="s2">"."</span><span class="p">,</span><span class="w"> </span><span class="s2">"man"</span><span class="p">,</span><span class="w"> </span><span class="n">f</span><span class="p">))</span><span class="w">
    </span><span class="n">md</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">rd2markdown</span><span class="o">::</span><span class="n">rd2markdown</span><span class="p">(</span><span class="n">rd</span><span class="p">,</span><span class="w"> </span><span class="n">fragments</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">())</span><span class="w">
    </span><span class="c1"># replaces the headers with more appropriate levels for embedding</span><span class="w">
    </span><span class="c1"># hopefully one day we can just pass level=3, see</span><span class="w">
    </span><span class="c1"># https://github.com/Genentech/rd2markdown/issues/41</span><span class="w">
    </span><span class="n">md_indented</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">gsub</span><span class="p">(</span><span class="s2">"(#+)"</span><span class="p">,</span><span class="w"> </span><span class="s2">"\\1##"</span><span class="p">,</span><span class="w"> </span><span class="n">md</span><span class="p">)</span><span class="w">

    </span><span class="c1"># write it to the docs folder</span><span class="w">
    </span><span class="n">writeLines</span><span class="p">(</span><span class="n">md_indented</span><span class="p">,</span><span class="w"> </span><span class="n">file.path</span><span class="p">(</span><span class="s2">".."</span><span class="p">,</span><span class="w"> </span><span class="s2">".."</span><span class="p">,</span><span class="w"> </span><span class="s2">"docs"</span><span class="p">,</span><span class="w"> </span><span class="s2">"languages"</span><span class="p">,</span><span class="w"> </span><span class="s2">"_r"</span><span class="p">,</span><span class="w">
        </span><span class="n">paste0</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="s2">".md"</span><span class="p">)))</span><span class="w">

</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>oh.</p>

<p>I mean, it certainly isn’t horrible, but it’s a good bit hackier than the others.
This is also the result of a lot of trial and error, so it’s considerably <em>less</em>
hacky than it once was.</p>

<p>First: we run <code class="language-plaintext highlighter-rouge">roxygenize</code> to make sure our <code class="language-plaintext highlighter-rouge">.Rd</code> files are actually up to date
in our <code class="language-plaintext highlighter-rouge">man</code> directory. Then, we’d like to loop over all of them, but, in general,
you may have private/unexported functions that you don’t want to document. So,
we check if each name is in the <code class="language-plaintext highlighter-rouge">NAMESPACE</code> file, and skip it if not.</p>

<p>Finally, we read each <code class="language-plaintext highlighter-rouge">.Rd</code> file, convert it to markdown, and write the result to the <code class="language-plaintext highlighter-rouge">docs</code>
directory. Because this will be embedded in a larger document, we increase the depth
of the markdown headers. As noted in the comment, doing this manually may become
unnecessary one day.</p>

<p>Unlike the others so far (though [spoilers!] <em>like</em> Rust), I don’t try to get the top-level
doc (installation instructions, example, etc) into this system. Instead, I write those
in a standard markdown file in my Sphinx source, and include the generated API docs, e.g.:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>... additional documentation above this point ...

<span class="gu">## API Reference</span>

<span class="p">```</span><span class="nl">{include} ./_r/StanModel.md
</span>
<span class="p">```</span>

<span class="gu">### Compilation utilities</span>

<span class="p">```</span><span class="nl">{include} ./_r/compile_model.md
</span>
<span class="p">```</span>

<span class="p">```</span><span class="nl">{include} ./_r/set_bridgestan_path.md
</span>
<span class="p">```</span>
</code></pre></div></div>

<h3 id="bonus-tip-r6-classes">Bonus tip: R6 classes</h3>

<p>If you happen to have an API that uses an <a href="https://r6.r-lib.org/index.html">R6 class</a>,
you will find that the generated markdown includes a table of methods
at the top with broken links. I’ve found the easiest thing to do is
just delete this in <code class="language-plaintext highlighter-rouge">conf.py</code> after calling the above R script, e.g.:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1"># delete some broken links in the generated R docs
</span>    <span class="n">StanModel</span> <span class="o">=</span> <span class="n">pathlib</span><span class="p">.</span><span class="n">Path</span><span class="p">(</span><span class="n">__file__</span><span class="p">).</span><span class="n">parent</span> <span class="o">/</span> <span class="s">"languages"</span> <span class="o">/</span> <span class="s">"_r"</span> <span class="o">/</span> <span class="s">"StanModel.md"</span>
    <span class="n">text</span> <span class="o">=</span> <span class="n">StanModel</span><span class="p">.</span><span class="n">read_text</span><span class="p">()</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">"### Public methods"</span><span class="p">)</span>
    <span class="n">end</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">"### Method `"</span><span class="p">)</span>
    <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="p">[:</span><span class="n">start</span><span class="p">]</span> <span class="o">+</span> <span class="n">text</span><span class="p">[</span><span class="n">end</span><span class="p">:]</span>
    <span class="n">StanModel</span><span class="p">.</span><span class="n">write_text</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="rust">Rust</h2>

<p>Rust, like Julia, uses Markdown extensively in its own documentation system. However,
Rust was one of the few items I more or less gave up on, and my documentation
page was just a link to the docs.rs site which is automatically generated by
uploading a crate to crates.io.</p>

<p>However, while writing this article, I found
<a href="https://gitlab.com/munir0b0t/sphinxcontrib-rust"><code class="language-plaintext highlighter-rouge">sphinxcontrib-rust</code></a>.
This ends up being pretty similar to the doxygen and R cases. It runs a Rust program
and then produces a Markdown file which Sphinx can read. You can then include this file
as a section in a larger, hand-written document on the language if you want an
<code class="language-plaintext highlighter-rouge">autodoc</code>-like experience, or use the new Rust directives it adds to craft a more
hand-made page.</p>

<p>Finding this package actually delayed the writing of this article, because I took
some time to try it out in BridgeStan. I found a few issues, but the maintainer was
incredibly responsive and the issues have all been resolved or sufficiently worked around.</p>

<p>So, you know the drill. Here’s what I have in <code class="language-plaintext highlighter-rouge">conf.py</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Checking Rust doc availability"</span><span class="p">)</span>
    <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">([</span><span class="s">"cargo"</span><span class="p">,</span> <span class="s">"--version"</span><span class="p">],</span> <span class="n">check</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">RUNNING_IN_CI</span><span class="p">:</span>
        <span class="k">raise</span> <span class="n">e</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Rust not installed, skipping Rust Doc"</span><span class="p">)</span>
        <span class="n">exclude_patterns</span> <span class="o">+=</span> <span class="p">[</span><span class="s">"languages/rust.md"</span><span class="p">,</span> <span class="s">"languages/_rust"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
    <span class="n">extensions</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"sphinxcontrib_rust"</span><span class="p">)</span>
    <span class="c1"># minimum needed for Rust, but you may need more myst
</span>    <span class="c1"># extensions depending on your specific docstrings!
</span>    <span class="n">myst_enable_extensions</span> <span class="o">+=</span> <span class="p">[</span><span class="s">"colon_fence"</span><span class="p">,</span> <span class="s">"attrs_block"</span><span class="p">]</span>
    <span class="n">rust_crates</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"bridgestan"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">BASE_DIR</span> <span class="o">/</span> <span class="s">"rust"</span><span class="p">),</span>
    <span class="p">}</span>
    <span class="n">rust_doc_dir</span> <span class="o">=</span> <span class="s">"languages/_rust"</span>
    <span class="n">rust_rustdoc_fmt</span> <span class="o">=</span> <span class="s">"md"</span>
    <span class="n">rust_generate_mode</span> <span class="o">=</span> <span class="s">"changed"</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">RUNNING_IN_CI</span> <span class="k">else</span> <span class="s">"always"</span>
</code></pre></div></div>

<p>This will produce a <code class="language-plaintext highlighter-rouge">lib.md</code> file in the <code class="language-plaintext highlighter-rouge">languages/_rust/CRATE_NAME/</code> directory,
which contains only the API documentation for the crate.
You can then include this in another Markdown file wherever you want it to
appear, like with the R example above. Be aware of the
<a href="https://sphinxcontrib-rust.readthedocs.io/en/latest/docs/limitations.html">limitations</a>,
and it is worth checking that your docs still look reasonable under <code class="language-plaintext highlighter-rouge">cargo doc</code>.</p>

<p>Tip: You can use <code class="language-plaintext highlighter-rouge">:start-line: 2</code> in the <code class="language-plaintext highlighter-rouge">{include}</code> directive to skip the
<code class="language-plaintext highlighter-rouge"># Crate CRATE_NAME</code> line that <code class="language-plaintext highlighter-rouge">sphinxcontrib-rust</code> generates.</p>

<h2 id="general-tip-github-pages">General tip: Github Pages</h2>

<p>Assuming you want to host the output of this process as a <a href="https://pages.github.com/">GitHub Pages</a> site,
here are some bonus tips:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">sphinx.ext.githubpages</code> is a built-in extension that just generates the <code class="language-plaintext highlighter-rouge">.nojekyll</code>
file that GitHub Pages needs to serve a folder as raw html.</p>
  </li>
  <li>
    <p>I highly recommend force-pushing to the gh-pages branch in your CI job that re-builds
the documentation. This prevents your <code class="language-plaintext highlighter-rouge">.git</code> folder from growing in size due to storing
old commits of the built documentation. As a general rule, you never need this output, since
it should be reproducible from the commit that triggered the rebuild (your build is deterministic, <em>right</em>?).</p>
  </li>
  <li>
    <p>If you took my earlier advice and are using pydata-sphinx-theme, it’s relatively
simple to use their <a href="https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/version-dropdown.html">version-picker</a>
by serving the site from a subdirectory. On merges to <code class="language-plaintext highlighter-rouge">main</code>/<code class="language-plaintext highlighter-rouge">trunk</code>, you can delete and re-create
a folder called something like <code class="language-plaintext highlighter-rouge">latest</code>, or <code class="language-plaintext highlighter-rouge">development</code>, and during releases the release action
can build a copy in the directory named after the new version and update the <code class="language-plaintext highlighter-rouge">versions.json</code> file.
See BridgeStan’s CI for this in action:</p>
    <ul>
      <li>Release: <a href="https://github.com/roualdes/bridgestan/blob/main/docs/add_version.py">updating version metadata</a></li>
      <li>Release: <a href="https://github.com/roualdes/bridgestan/blob/main/.github/workflows/release.yaml#L137-L143">running docs action</a></li>
      <li><a href="https://github.com/roualdes/bridgestan/blob/main/.github/workflows/docs.yaml">Docs CI</a></li>
    </ul>
  </li>
</ul>

<h2 id="go-out-and-cook">Go out and cook</h2>

<p>Like any recipe, you will learn much more the first time you try it yourself
than you ever could from reading it. While each language ends up being finicky
in its own way, the general structure of adding a new one is pretty
streamlined in this style, and the end result is quite satisfying.</p>

<p>If you find a problem with the above, or want to suggest a better way, please
<a href="https://github.com/WardBrian/wardbrian.github.io/issues/new">open an issue</a>.
I can’t guarantee <em>support</em>, per se, but if I can I will try to keep this post as
a living document over time.</p>]]></content><author><name>Brian Ward</name><email>bward@flatironinstitute.org</email></author><category term="documentation" /><category term="tips and tricks" /><category term="programming languages" /><summary type="html"><![CDATA[A user report from documenting too many languages]]></summary></entry></feed>