diff --git a/documentation/assets/BreatheFlowChart.svg b/documentation/assets/BreatheFlowChart.svg
new file mode 100644
index 000000000..58bb35222
--- /dev/null
+++ b/documentation/assets/BreatheFlowChart.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/documentation/assets/BreatheFlowChart_DarkMode.svg b/documentation/assets/BreatheFlowChart_DarkMode.svg
new file mode 100644
index 000000000..77d09cf54
--- /dev/null
+++ b/documentation/assets/BreatheFlowChart_DarkMode.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/documentation/source/codeguide.rst b/documentation/source/codeguide.rst
index 658de4279..9c5179f99 100644
--- a/documentation/source/codeguide.rst
+++ b/documentation/source/codeguide.rst
@@ -4,6 +4,7 @@
How It Works
============
+
There are three main sections to Breathe: parser, finders and renderers.
Briefly:
@@ -21,11 +22,36 @@ Briefly:
object hierarchies rendering the objects, their children, their children's
children and so on. Found in ``breathe.renderer``.
+The following flow chart shows how the different components of Breathe transform
+data. The shaded region indicates which components are part of Breathe.
+
+.. image:: ../assets/BreatheFlowChart.svg
+ :width: 500
+ :alt: A flow chart showing that the initial input format is code. Doxgyen converts
+ code to XML. The Breathe parser converts XML to a hierarchy of python objects.
+ The Breathe Filter identifies which of these objects need to be rendered. The
+ Breathe Renderer converts these objects into reStructuredText (RST) nodes.
+ Finally, the RST node objects are passed to Sphinx to be turned into actual
+ HTML or LaTeX documents.
+ :class: only-light
+ :align: center
+
+.. image:: ../assets/BreatheFlowChart_DarkMode.svg
+ :width: 500
+ :alt: A flow chart showing that the initial input format is code. Doxgyen converts
+ code to XML. The Breathe parser converts XML to a hierarchy of python objects.
+ The Breathe Filter identifies which of these objects need to be rendered. The
+ Breathe Renderer converts these objects into reStructuredText (RST) nodes.
+ Finally, the RST node objects are passed to Sphinx to be turned into actual
+ HTML or LaTeX documents.
+ :class: only-dark
+ :align: center
+
Parser
------
-The parsers job is to parse the doxygen xml output and create a hierarchy of
+The parser's job is to parse the doxygen xml output and create a hierarchy of
Python objects to represent the xml data.
Doxygen XML Output
@@ -230,3 +256,138 @@ code, the use of them is not something I've found very well documented and my
code largely operates on a basis of trial and error.
One day I'm sure I'll be enlightened, until then expect fairly naive code.
+
+Testing
+-------
+
+Tests for Breathe can be found in the ``tests`` directory. They can be run by
+running ``make test`` in your terminal (assuming that you have pytest installed).
+The bulk of Breathe's test suite is in ``tests/test_renderer.py``, and this is
+where any renderer-related tests should be added. This documentation will focus
+on how to write more renderer tests, as this is the most common region of the code
+to add new features to and perhaps the hardest to test.
+
+Creating Python Doxygen Nodes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As indicated in the diagram at the top of this page, the renderer is expecting to
+be run after the parser has created a hierarchy of python objects. Thus, there is
+a lot of set-up that would usually happen before the renderer is invoked. For ease
+of testing, it is often expedient to skip straight to the step where you have a
+hierarchy of Python objects representing some hypothetical XML that doxygen could
+have produced.
+
+``test_renderer.py`` contains a number of classes designed to assist with this
+process. For just about any node that could show up in the XML produced by doxygen,
+there is a class that quickly instantiates it in Python. For example, if you want
+to test the rendering of a member definition, you can use the ``WrappedMemebrDef``
+class. Figuring out how nodes fit together can be challenging; until you're
+comfortable with the type of XML produced by doxygen, the easiest process is likely:
+
+#. Write C++ code containing the behavior you would like to test.
+#. Run Doxygen on it, which will produce an XML file (likely inside a directory
+ called xml within your doxygen output directory)
+#. Re-build the relevant part of the xml file in Python using the ``Wrapped*``
+ classes.
+
+For example, lets say you have a struct representing a cat.
+
+Your C++ might look something like this (inspired by Sy Brand's
+`blog post `_):
+
+.. code-block:: cpp
+
+ /**
+ A fluffy feline
+ */
+ struct cat {
+ /**
+ Make this cat look super cute
+ */
+ void make_cute();
+ };
+
+Running Doxygen on this might give you XML something like this:
+
+.. code-block:: xml
+
+
+
+
+ cat
+ test_cpp.hpp
+
+
+ void
+ void cat::make_cute
+ ()
+ make_cute
+ cat::make_cute
+
+
+
+ Make this cat look super cute
+
+
+
+
+
+
+
+
+
+ A fluffy feline
+
+
+
+ catmake_cute
+
+
+
+
+There's a lot here. For now, let's just say we're testing something related to
+member function definitions, and we only need to test that part of the
+hierarchy. We can load the ``memberdef`` part of this XML into a
+``WrappedMemberDef`` object as follows:
+
+.. code-block:: python
+
+ member_def = WrappedMemberDef(
+ kind="function", # From "kind" in open memberdef tag
+ definition="void cat::make_cute", # From tag
+ type="void", # From tag
+ name="make_cute", # From tag
+ argstring="()", # From tag
+ virt="non-virtual", # From "virt" in open memberdef tag
+ )
+
+As you can see, all of the arguments to the constructor are pulled directly out
+of the XML, either from options on the original memberdef or tags nested under
+it. There are a lot more optional arguments that can be provided to specify
+additional details of the memberdef.
+
+More advanced hierarchies can be represented by nesting nodes inside each
+other. For example, if our function took arguments, it would have ````
+tags nested within it. We could represent these as a list of ``WrappedParam``
+objects passed into the ``param`` keyword argument.
+
+To test that the node renders correctly, you can use the ``render`` function
+provided in ``test_renderer.py``:
+
+.. code-block:: python
+
+ # Render the node and grab its description
+ signature = find_node(render(app, member_def), "desc_signature")
+ # You can now examine the contents of signature.astext() and assert that
+ # they are as expected
+
+
+Mocks
+~~~~~
+
+If you want to do more elaborate tests, it is useful to be aware of the various
+Mock objects provided in ``test_renderer.py``. Because the renderer is
+expecting to be executing in the context of a full Sphinx run, there are a lot
+of objects that it is expecting to have access to. For example, rendering some
+nodes requires making reference to a context object. The ``MockContext`` class
+can serve as a stand-in.
\ No newline at end of file