Sphinx is a well know really powerful documentation engine used extensively within the Python community. Sphinx typically is used to generate a set of static HTML pages from a selection of source files writing in reStructuredTEXT.
reStructuredTEXT for those who are unfamiliar with it is similar to Markdown however it adds the concept of nesting. Sphinx refers to these nested objects as directives each one can take some options and child content. Within our documentation, we have many cases where we have used custom directives to drive unique UI and logical components. Each such directive can take some custom config that is used to manages its rendering. As an example below is the source for our HMAC dynamic value.
There the are 4 different custom directives in use:
dv
(dynamic value) this expects 2 child components that subclass our custom tab
directive.dvvis
is the directive that manages the visual tab (seen on the left below), this expects a figure.dvspec
is a more complex tab component that is rendered on the right below.
The power of this system is the descriptions for each of these directives can, in turn, contain custom directives and the parent directive does not need to know about unless it needs to modify or parse it. The figure
directives description here contains a link item, the figure render method does not need to consider this possibly, it just considers the description as a child of the directive and this will be rendered as it normally would, this lets us add any custom content here if needed.
Above we have looked at a few custom directives, but how do we set up these Sphinx extensions.
You will also need to add some functions to support the rendering of TabFrameNode
if you need to also render the classic HTML version provided by Sphinx by default. For our React solution we create a component that maps to this TabFrameNode
class and is rendered when the React app encounters this node in the AST.
To allow us to render these docs in React, we need to pass the AST that Sphinx uses before it renders this to a HTML page. This is done in a custom build script. The script produces a JSON dump of the AST for each file, these are uploaded to an Amazon S3 bucket and then served through AWS AWS CloudFront (a CDN service) to the client. As the user navigates the docs, the single page app pulls the needed JSON ASTs from CloudFront as and when needed.
Rendering the AST in React is quite a simple process when saving the AST to JSON the corresponding Python class
and module
is saved to the JSON object for each Node
in the tree. In the React app a mapping is set up that maps the module.class
to a component.
Every component renderers what it needs to, then typically renders out the children. In our case we make every component inherit from a base class Node
component.
Since the page’s content is directly managed as a function of this AST, we can easily add and remove things from our docs page without needing to deploy or touch our front-end code base. Due to this we are able to view different versions of our docs by adding a ?version={versionid}
as a URL param to the page or even in development mode we can add ?debug=True
to force the page to load the content from a local server. As an example of this, here is a custom version of the docs I have deployed just to illustrate this blog article. View this example at:
The source for this page can be found here.
I hope you found this article interesting, I will be leaving the Paw team and focusing on developing other pro-level developer tools for macOS over the coming year.