Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
* xref:usage.adoc[Getting Started]
* xref:config-file.adoc[]
* xref:commands.adoc[Documenting the Code]
* xref:addons.adoc[]
* xref:generators.adoc[]
* xref:extensions.adoc[]
* xref:design-notes.adoc[]
* xref:reference:index.adoc[Library Reference]
* Contribute
Expand Down
58 changes: 58 additions & 0 deletions docs/modules/ROOT/pages/addons.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
= Addons

Mr.Docs is customised through *addons*: directory trees that hold custom Handlebars templates, helper scripts and extension scripts.
This page describes addons as a whole; what goes in each subdirectory is covered on xref:generators.adoc[Generators] and xref:extensions.adoc[Extensions].

== Default and replacement addons

Mr.Docs ships a built-in addon at `share/mrdocs/addons/`.
It contains the default Handlebars templates, helpers and any baseline scripts.

The `addons` configuration option replaces it:

[source,yaml]
----
addons: /path/to/custom/addons
----

The replacement is wholesale: your addon should provide everything Mr.Docs needs for the generators you intend to use.

== Supplemental addons

The more common pattern is to *layer* a small addon on top of the built-in one rather than replace it entirely.
That is what `addons-supplemental` is for:

[source,yaml]
----
addons-supplemental:
- addons
- /path/to/another
----

Each supplemental addon is layered on top of the base addon in the order listed.

== Layout

[source,text]
----
<addons>/
|-- generator/
| |-- common/{partials,helpers}/ # shared across output formats
| `-- <fmt>/{layouts,partials,helpers}/ # per-format (html, adoc, ...)
`-- extensions/
|-- *.js
`-- *.lua
----

* `generator/` -- Handlebars templates and helpers for the output stage. Detailed in xref:generators.adoc[Generators].
* `extensions/` -- scripts that run after corpus construction and can mutate symbols before any generator sees them. Detailed in xref:extensions.adoc[Extensions].

== Discovery

Mr.Docs visits addons in this order:

. The base addon (the built-in `share/mrdocs/addons/` or whatever `addons` points at).
. Each entry of `addons-supplemental`, in the order listed.

For templates and helpers, the rule is *last wins*: a file in a supplemental addon overrides a same-named file in earlier addons.
For extensions, every script across every addon is collected and run in alphabetical order by full path; there is no override semantic.
128 changes: 128 additions & 0 deletions docs/modules/ROOT/pages/extensions.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
= Extensions

Extensions let you transform the corpus of extracted symbols before any generator runs.
A typical use case is rewriting metadata across many symbols at once: backfilling briefs from a naming convention, tagging symbols by group, or marking generated code as "see below" in the output.

Extensions are user-supplied scripts written in JavaScript or Lua.
They run between extraction (turning C++ source into a corpus of symbols) and rendering (turning the corpus into output files), so any change they make is visible to every generator.

== File layout

Extension scripts live under `<addons>/extensions/`, with the `.lua` or `.js` extension.
See xref:addons.adoc[Addons] for where addon roots come from (`addons`, `addons-supplemental`) and how scripts across multiple roots are aggregated -- scripts run in alphabetical order by full path, with the two languages interleaved.

== The `transform_corpus` hook

A script extends Mr.Docs by exposing a function named `transform_corpus(corpus)`.
Mr.Docs calls it once with a flat read-only view of the corpus.
The script inspects symbols and calls mutation functions on the pre-registered `mrdocs` object to apply changes.

A script that does not define `transform_corpus` is silently ignored, so an extension file can be empty during development without breaking the build.

[source,javascript]
----
// <addons>/extensions/rename.js
function transform_corpus(corpus) {
for (var i = 0; i < corpus.symbols.length; ++i) {
var sym = corpus.symbols[i];
if (sym.kind === "function") {
mrdocs.set(sym.id, "name", "renamed_" + sym.name);
}
}
}
----

[source,lua]
----
-- <addons>/extensions/rename.lua
function transform_corpus(corpus)
local i = 0
while true do
local sym = corpus.symbols[i]
if sym == nil then break end
if sym.kind == "function" then
mrdocs.set(sym.id, "name", "renamed_" .. sym.name)
end
i = i + 1
end
end
----

== The `corpus` argument

The `corpus` argument has a single field today:

* `corpus.symbols` -- an array containing every symbol Mr.Docs extracted.

That is the entire shape of `corpus`.
There is no `corpus.namespaces`, no `corpus.config`, no `corpus.lookup`; scripts that need such queries walk `corpus.symbols` and filter.

Each entry in `corpus.symbols` is the same lazy DOM view that Mr.Docs's built-in Handlebars generators see, with every described member of the underlying symbol type.
The fields you'll reach for most often are `id` (pass back to `mrdocs.set` to identify the symbol to act on), `kind`, and `name`; deeper navigation (`doc`, `loc`, `params`, `bases`, ...) works exactly as in templates.
For the full set, see the Handlebars/templates documentation.

In JavaScript, iterate with `corpus.symbols.length` and `corpus.symbols[i]`, or with `for (var s of corpus.symbols)`.

In Lua, iterate by integer index starting at `0`; `corpus.symbols[i]` returns `nil` past the last symbol, so a simple `while` loop terminates correctly.

== The `mrdocs` API

Mutations go through the pre-registered `mrdocs` global.

=== `mrdocs.set(symbol_id, field, value)`

Assign one allowlisted field of a symbol. The function dispatches through reflection, but the set of fields scripts may write is intentionally curated: extensions cannot, for example, change a symbol's `kind`, re-parent it, or rewrite its structural collections, because doing so would break invariants the rest of the corpus relies on.

* `symbol_id`: the `id` value read from a symbol in `corpus.symbols`.
* `field`: one of the allowlisted names below (camelCase, matching the read view).
* `value`: the new value. Supported value types are strings, booleans, enumerator names (kebab-case strings), `null` (to clear an optional field), arrays (assigned element-wise to a `vector<T>` field), objects (assigned key-wise to a described struct field), and objects with a `kind` selector for a polymorphic base (the `kind` picks the concrete derived class registered through `MRDOCS_DESCRIBE_KINDS`, and remaining keys are forwarded to that class).

The currently allowlisted fields are:

|===
|Field |Type |Description

|`name`
|string
|The unqualified symbol name.

|`access`
|enum
|Access specifier - one of `public`, `protected`, `private`, `none`.

|`extraction`
|enum
|Extraction mode - one of `regular`, `see-below`, `implementation-defined`, `dependency`.

|`isCopyFromInherited`
|bool
|Whether the symbol was generated by base-member inheritance.

|`loc`
|struct
|Source location information.

|`doc`
|optional struct
|The full doc-comment tree. Pass `null` to clear, or a partial object to overwrite individual fields (brief, returns, params, ...). Brief text is rewritten by passing `{ brief: { children: [{ kind: "text", text: "..." }] } }`.

|`returnType`
|polymorphic `Type`
|A function's return type. The `kind` selector picks a concrete `Type` variant; remaining keys are forwarded to that variant. `TypeKind` is the one polymorphic base whose `kind` values come from `toString(TypeKind)` (e.g., `lvalue-reference`) rather than from the kebab-case of the enumerator (e.g., `l-value-reference` as seen in the XML writer).
|===

The setter validates its arguments and raises an error on misuse: unknown symbol id, field not on the allowlist, type mismatch (for example, a non-string passed to `name`), an enumerator name that does not exist on the field's enum, or a `kind` tag that does not name a derived class registered for the polymorphic base. An uncaught error inside an extension aborts the build with the script's path and the error message.

The allowlist grows as concrete use cases come up. The type machinery covers strings, booleans, described enums, `Optional<T>`, `vector<T>`, described structs, and `Polymorphic<T>` for any base whose hierarchy was registered with `MRDOCS_DESCRIBE_KINDS`.

== Lifecycle

Extensions run between corpus finalization and the first generator invocation.
The order is:

. Mr.Docs walks the source files and extracts a corpus of symbols.
. Built-in finalizers post-process the corpus (for example, sorting members and resolving inheritance).
. Extensions run, in alphabetical order by full path.
. The selected generator renders the (possibly mutated) corpus.

Because step 3 happens before step 4, an extension that mutates a symbol is visible to every output format, not just one.
59 changes: 52 additions & 7 deletions docs/modules/ROOT/pages/generators.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,8 @@ Mr.Docs attempts to support various alternatives for customizing the output form
The `adoc` and `html` generators use https://handlebarsjs.com/[Handlebars,window=_blank] templates.
These templates can be customized to change the output format and style of the documentation.

The templates used to generate the documentation are located in the `share/mrdocs/addons/generator` directory.
Users can create a copy of these files and provide their own `addons` directory via the `addons` option in the configuration file or via the command line.

[source,yaml]
----
addons: /path/to/custom/addons
----
The templates used to generate the documentation live in the built-in addon under `share/mrdocs/addons/generator`.
You can replace or supplement that addon with your own; xref:addons.adoc[Addons] covers the lookup paths (`addons`, `addons-supplemental`) and how files from multiple addons combine.

Each symbol goes through a main layout template in the `<addons>/generator/<generator>/layouts/single-symbol.<generator>.hbs` directory.
This template is a simple entry point that renders the partial relative to the symbol kind.
Expand Down Expand Up @@ -111,6 +106,56 @@ The layout template can include other partial templates to render the symbol dat

The Document Object Model (DOM) for each symbol includes all information about the symbol.One advantage of custom templates over post-processing XML files is the ability to access symbols as a graph.If symbol `A` refers to symbol `B`, some properties of symbol `B` are likely to be required in the documentation of `A`.All templates and generators can access a reference to `B` by searching the symbol tree or simply by accessing the elements `A` refers to.All references to other symbols are resolved in the templates.

[#custom-helpers]
=== Custom Helpers

Beyond the built-in helpers, an addon can register its own Handlebars helpers in JavaScript or Lua.
Drop a script alongside your partials and Mr.Docs picks it up automatically:

* `<addons>/generator/common/helpers/*.{js,lua}`: helpers available to every output format.
* `<addons>/generator/<generator>/helpers/*.{js,lua}`: helpers available only to the matching format (`html`, `adoc`, ...). A format-specific helper overrides a common one with the same name.

The file's stem (with the `.js` or `.lua` extension stripped) becomes the helper name.
Templates invoke it the same way they invoke a built-in helper:

[source,handlebars]
----
{{my_helper symbol.name}}
----

==== Helper resolution

Each script is expected to expose a single function, the helper:

* Return the function from the chunk (recommended):
+
[source,javascript]
----
return function(name) {
return "[" + name + "]";
};
----
+
[source,lua]
----
return function(name)
return "[" .. name .. "]"
end
----

* Or define a global with the same stem as the file (for example, `my_helper.lua` defining `function my_helper(name) ... end`).

==== Utility files

A file whose stem starts with an underscore (for example, `_utils.js`) is loaded first and is *not* registered as a helper.
Use these files to define globals that several helpers share, so a single utility script can set up state once instead of every helper duplicating it.

==== Arguments and the Handlebars options object

Mr.Docs strips Handlebars' trailing options object before forwarding arguments to the helper.
Helpers receive the positional arguments only and don't have to filter the options out themselves.
This also avoids expensive marshalling of symbol contexts, which contain circular references.

== Stylesheet Options

The HTML and AsciiDoc generators ship a bundled stylesheet that is inlined by default. You can replace or layer styles with the following options (available in config files and on the CLI):
Expand Down
3 changes: 2 additions & 1 deletion docs/mrdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ exclude-symbols:
# Reflection facilities.
- 'mrdocs::describe'
- 'mrdocs::describe::**'
# Symbols injected by MRDOCS_DESCRIBE_STRUCT/ENUM.
# Symbols injected by MRDOCS_DESCRIBE_STRUCT, etc.
- '**mrdocs_base_descriptor_fn'
- '**mrdocs_member_descriptor_fn'
- '**mrdocs_enum_descriptor_fn'
- '**mrdocs_kind_descriptor_fn'
multipage: true
generator: adoc
cmake: '-D MRDOCS_DOCUMENTATION_BUILD=ON'
Expand Down
Loading
Loading