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
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,19 @@ if (MRDOCS_BUILD_TESTS)
endforeach ()
endforeach ()

#-------------------------------------------------
# Script-driven generator example
#-------------------------------------------------
add_test(
NAME mrdocs-generator-script-driven-search-index
COMMAND bash run.sh
--addons=${CMAKE_CURRENT_SOURCE_DIR}/share/mrdocs/addons
--output=${CMAKE_CURRENT_BINARY_DIR}/generator-examples/search-index/reference-output
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/generators/script-driven/search-index
)
set_property(TEST mrdocs-generator-script-driven-search-index PROPERTY
ENVIRONMENT "MRDOCS=$<TARGET_FILE:mrdocs>")

#-------------------------------------------------
# Library-usage examples
#
Expand Down
2 changes: 2 additions & 0 deletions docs/antora-playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ antora:
strip_page_wrapper: true
- source: examples/generators/data-driven
target: data-driven-generators
- source: examples/generators/script-driven
target: script-driven-generators
- source: examples
target: examples
- require: ./extensions/commands-registry-extension.js
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
** xref:extensions/corpus-extensions.adoc[Corpus Extensions]
** xref:extensions/handlebars-extensions.adoc[Handlebars Extensions]
** xref:extensions/data-driven-generators.adoc[Data-Driven Generators]
** xref:extensions/script-driven-generators.adoc[Script-Driven Generators]
** xref:extensions/antora.adoc[Antora Extensions]
** xref:extensions/as-library.adoc[Mr.Docs as a Library]
** xref:extensions/dom-reference.adoc[DOM Reference]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,6 @@ include::example$data-driven-generators/latex/simple.latex[]
----
======

To build the output structure yourself, e.g. one file per namespace or a single aggregated artifact like a search index, hand the whole emit to a script instead of rendering one page per symbol. See xref:extensions/script-driven-generators.adoc[Script-driven generators].


70 changes: 70 additions & 0 deletions docs/modules/ROOT/pages/extensions/script-driven-generators.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
= Script-driven generators

A data-driven generator renders one page per symbol from templates. When you need a different output structure, e.g. one file per namespace, or a single artifact aggregated across every symbol, such as a search index, a template generator cannot express it, because the page-per-symbol shape is fixed by the host. A script-driven generator hands the whole emit to a Lua or JavaScript script, which traverses the corpus and writes whatever files it wants. No C++ and no templates are involved.

A generator directory is script-driven when its mrdocs-generator.yml names an entry script:

[source,yaml]
----
script: generator.lua
----

The `script` key holds a path to a Lua (.lua) or JavaScript (.js) file, relative to the generator directory. Naming a script is what distinguishes the two flavors: a manifest with a `script` key is script-driven, otherwise the directory is a data-driven (template) generator. As with template generators, the directory name is the generator id you select with `--generator`.

== The `generate` entry point

The script defines a single entry point, a function named `generate`:

[source,lua]
----
function generate(corpus, output, config, params)
-- ...
end
----

`corpus.symbols` is the array of every symbol. Each symbol carries the same fields the template and helper layers see, plus a flat `_id` string suitable as a stable per-symbol URL fragment.

`output.write(relativePath, contents)` writes one file under the configured output directory, which is the path specified with `--output` on the command line, or with the `output` key in the config file; that's the same location the built-in generators write to. The path is resolved relative to that directory and may not escape it; an absolute path or one that climbs above the output directory is rejected. Parent directories are created as needed.

Because the script owns the output, it also owns what a per-page generator would otherwise do for it: the URLs it emits, and any escaping of the content it writes. The host does not apply an escape map to a script-driven generator's output.

`config` is the resolved configuration: the same object the templates receive, holding every value from the config file and the command line. See xref:configuration/reference.adoc[the configuration reference] for the available keys.

`params` is this generator's own parameters, read from the optional `params:` mapping in its mrdocs-generator.yml. A scalar value is a string (a script coerces numbers or booleans itself); nested mappings and sequences become objects and arrays. It is an empty object when the manifest declares no parameters. For example:

[source,yaml]
----
script: generator.lua
params:
title: API Reference
----

makes `params.title` available to the script.

`config` and `params` are trailing arguments, so a generator that needs neither can omit them, and use `function generate(corpus, output)`.

Both Lua and JavaScript look up `generate` as a global function, so a generator must define one; a value the script returns is not used. Requiring the named global keeps one convention across the two languages and leaves room for a script to expose more than one named entry point later.

Unlike a corpus-transform extension, whose hook is optional, a generator must define a `generate` function: selecting the generator is a request for output, so a missing entry point is an error.

== Example: a search index

A complete, runnable example lives at `examples/generators/script-driven/search-index/`. It emits a single search-index.json aggregating every symbol, an artifact no per-page generator can produce.

The manifest names the script:

.`addons/generator/search-index/mrdocs-generator.yml`
[source,yaml]
----
include::example$script-driven-generators/search-index/addons/generator/search-index/mrdocs-generator.yml[]
----

The script itself:

.`addons/generator/search-index/generate.lua`
[source,lua]
----
include::example$script-driven-generators/search-index/addons/generator/search-index/generate.lua[]
----

Select it with `--generator=search-index`; it writes search-index.json into the output directory.
2 changes: 1 addition & 1 deletion docs/mrdocs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@
},
"generator": {
"default": "html",
"description": "The generator is responsible for creating the documentation from the extracted symbols. The generator uses the extracted symbols and the templates to create the documentation. The built-in generators include `adoc`, `html`, and `xml`; data-driven generators can be added by dropping a template folder under <addon>/generator/<name>/.",
"description": "The generator is responsible for creating the documentation from the extracted symbols. The generator uses the extracted symbols and the templates to create the documentation. The built-in generators include `adoc`, `html`, and `xml`; data-driven generators can be added by dropping a template folder under <addon>/generator/<name>/; script-driven generators instead ship a Lua or JavaScript script that produces the output.",
"title": "Generator used to create the documentation",
"type": "string"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Quote a string as a JSON value.
local function json_string(s)
s = s:gsub('\\', '\\\\'):gsub('"', '\\"')
return '"' .. s .. '"'
end

function generate(corpus, output)
local entries = {}
for _, sym in ipairs(corpus.symbols) do
local name = sym.name or ""
if name ~= "" then
entries[#entries + 1] =
'{"name":' .. json_string(name) ..
',"url":' .. json_string(sym._id .. ".html") .. "}"
end
end
output.write(
"search-index.json",
"[" .. table.concat(entries, ",") .. "]")
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
script: generate.lua
9 changes: 9 additions & 0 deletions examples/generators/script-driven/search-index/mrdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
addons-supplemental:
- addons
generator: search-index
multipage: false
show-namespaces: false
warn-if-undocumented: false
source-root: .
input:
- .
2 changes: 2 additions & 0 deletions examples/generators/script-driven/search-index/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
exec "${MRDOCS:-mrdocs}" --config=mrdocs.yml "$@"
16 changes: 16 additions & 0 deletions examples/generators/script-driven/search-index/simple.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// A vector in the Euclidean plane.
struct Vector
{
/** The length (magnitude) of the vector.

@return The Euclidean length.
*/
double length() const;

/** Scale the vector componentwise.

@param sx Factor applied to the x component.
@param sy Factor applied to the y component.
*/
void scale(double sx, double sy);
};
2 changes: 1 addition & 1 deletion src/lib/ConfigOptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@
{
"name": "generator",
"brief": "Generator used to create the documentation",
"details": "The generator is responsible for creating the documentation from the extracted symbols. The generator uses the extracted symbols and the templates to create the documentation. The built-in generators include `adoc`, `html`, and `xml`; data-driven generators can be added by dropping a template folder under <addon>/generator/<name>/.",
"details": "The generator is responsible for creating the documentation from the extracted symbols. The generator uses the extracted symbols and the templates to create the documentation. The built-in generators include `adoc`, `html`, and `xml`; data-driven generators can be added by dropping a template folder under <addon>/generator/<name>/; script-driven generators instead ship a Lua or JavaScript script that produces the output.",
"type": "string",
"default": "html"
},
Expand Down
Loading
Loading