I'm trying to generate a documentation site for a Python API which is created with pybind11 using MkDocs. This API has also a .cpp
API which is generated well.
This is my C++ common header code:
#include <pybind11/pybind11.h>
namespace App::Binding
{
namespace py = pybind11;
}
and the API for the above application is defined in this C++ header + .cpp
files:
H file:
#pragma once
#include "PybindCommon.h"
namespace App::Binding
{
/**
* @brief Bindings for IApp API
*/
void bindApp(py::module_& m);
} // namespace App::Binding
CPP file:
#include "IApp.h"
#include "AppBindings.h"
#include <pybind11/stl.h>
/**
* @brief Bindings for IApp API
*/
namespace App::Binding
{
// Custom deleter for unique_ptr<IApp> objects to call release() on the object when the object is GC'd in Python
struct AppDeleter
{
void operator()(IApp* app)
{
if(app) {
app->release();
}
}
};
void bindApp(py::module_& m)
{
// python constructor will be binded to a unique_ptr<IApp> object with a custom deleter on C++ side
py::class_<IApp, std::unique_ptr<IApp, AppDeleter>>(m, "App",
R"pbdoc(
The main entry point for accessing the App.
Exposes general app metadata objects for accessing specific data from the app.
)pbdoc")
.def(py::init([](const char* config_path, LibType lib_type) {
IApp* app = initApp(config_path, lib_type);
if(app == nullptr) {
throw std::runtime_error(
"Can only construct one app per process. Multi-app support planned in future versions.");
}
return std::unique_ptr<IApp, AppDeleter>(app);
}),
py::arg("config_path"),
py::arg("lib"),
R"pbdoc(
Create an app object
Args:
config_path (:obj:`str`):
Path to the configuration file for the app.
lib (:obj:`LibType`):
Type of shared-object to use the app with.
Returns:
:obj:`App`:
App object.
Raises:
RuntimeError: If an App was already previously created in the same process. Will be fixed in future versions.
)pbdoc")
.def(
"get_range",
[](const IApp& self) {
uint32_t first, last;
self.getRange(first, last);
return py::make_tuple(first, last);
},
R"pbdoc(
Get the App legal range
Returns:
:obj:`tuple`:
A tuple containing:
- first (:obj:`int`):
First range value.
- last (:obj:`int`):
Last range value.
)pbdoc")
.def("get_version",
&IApp::getVersion,
R"pbdoc(
Get the created App version
Returns:
:obj:`str`:
SHA of the App version.
)pbdoc")
.def("get_fps",
&IApp::getAppFps,
R"pbdoc(
Get the FPS of the App
Returns:
:obj:`int`:
App's system FPS.
)pbdoc")
.def("get_available_frames",
&IApp::getAvailableFrames,
R"pbdoc(
Get the available frames for the primary app sensor
Returns:
:obj:`list`:
List of available frames for the primary sensor.
)pbdoc")
.def("get_available_frame_configs",
&IApp::getAvailableFrameConfigs,
py::arg("frame"),
R"pbdoc(
Get all the available configurations for a specific frame type.
Args:
frame (:obj:`FrameType`):
Type of frame.
Returns:
:obj:`list`:
List of available configurations.
)pbdoc");
}
} // namespace App::Binding
I've been using Sphinx with Doxygen up until now to create static HTML files and generate them to a local site with the API, and my office started using MkDocs (which is of course much nicer), and now I need to somehow extract the API above using an mkdocs.yaml
file.
What I've tried so far:
- Using
mkdcostrings
plugin with a python handler where the input is the.so
created by pybind (which contains the docstrings extracted bypybind_module::doc()
). - Using Sphinx as a pre-processor with
sphinx-build -M xml
and using the output XML files as input to mkdoxy plugin. - Using the above with
sphinx-build -M json
and mkdocsrings plugin- created.fjson
files that didn't parse well by mkdocstrings. - Parsing the
.cpp
in a Python script trying to create.py
files with regular docstrings- not a good solution for using CI automation (+ hard to maintain). - I tried some more small attempts with
markdown-extensions
that didn't work because of Jinja2 syntax or{eval-rst}
issues I couldn't figure out.
This is my current mkdocs.yaml file configuration I've come up with until now:
site_name: "App Docs"
repo_url: <company_site>/app.git
repo_name: app
edit_uri: edit/master/include/
dev_addr: 0.0.0.0:38000
docs_dir: .
plugins:
- mkdoxy:
projects:
App:
src-dirs: include
doxy-cfg:
FILE_PATTERNS: "*.cpp *.h* *.md"
RECURSIVE: YES
INPUT: include
GENERATE_XML: YES
EXTRACT_ALL: YES
OPTIMIZE_OUTPUT_FOR_C: YES
pyDAST:
src-dirs: docs/build/json/python
doxy-cfg:
FILE_PATTERNS: "*.fjson"
RECURSIVE: NO
INPUT: docs/build/json/python
GENERATE_XML: YES
EXTRACT_ALL: YES
OPTIMIZE_OUTPUT_FOR_JAVA: YES
save-api: .mkdoxy
full-doc: True
debug: False
ignore-errors: False
- search
- mkdocstrings
- open-in-new-tab
- autorefs
- same-dir
- awesome-pages
- include-markdown
theme:
name: material
features:
- navigation.tabs
- navigation.tabs.sticky
- navigation.indexes
- navigation.path
- navigation.top
- navigation.tracking
palette:
- media: "(prefers-color-scheme: dark)"
scheme: default
primary: blue
accent: indigo
toggle:
icon: material/toggle-switch
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: slate
toggle:
icon: material/toggle-switch-off-outline
name: Switch to dark mode
extra_javascript:
- @main/mkdocs/javascripts/massilia-graphviz.js
markdown_extensions:
- extra
- tables
- mkdocs_graphviz
- attr_list
- mdx_truly_sane_lists
- mdx_math:
use_gitlab_delimiters: True # for $`...`$ style math
- toc:
permalink: true
- pymdownx.highlight
- pymdownx.superfences
- def_list
- admonition
- pymdownx.details
- markdown.extensions.md_in_html
- pymdownx.snippets:
check_paths: true
- pymdownx.blocks.admonition:
types:
- new
- settings
- note
- abstract
- info
- tip
- success
- question
- warning
- failure
- danger
- bug
- example
- quote
- pymdownx.blocks.details:
- pymdownx.blocks.html:
- pymdownx.blocks.definition:
- pymdownx.blocks.tab:
- pymdownx.tabbed:
alternate_style: true
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
use_directory_urls: True
nav: ...
Is someone familiar with some sort of plugin or method to combine these two?