Skip to content

Commit ed2f0ce

Browse files
committed
docs: document script-driven generators
1 parent 28f2ed9 commit ed2f0ce

7 files changed

Lines changed: 134 additions & 2 deletions

File tree

docs/modules/ROOT/pages/generators.adoc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,76 @@ Unknown top-level keys are silently ignored so future schema additions stay non-
200200
If the same id appears under more than one addon root, the first one wins: that root's manifest sets the format's escape rules.
201201
Later roots can still contribute layered partials and helpers under the same id through the existing template-loading path, so a project can supplement a shared format without redefining it.
202202

203+
To add a generator that builds its output structure imperatively, rather than rendering one page per symbol from templates, see <<script-driven-generators,Script-driven generators>>.
204+
205+
[#script-driven-generators]
206+
== Script-driven generators
207+
208+
A data-driven generator renders one page per symbol from templates. When you need a different output structure - 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.
209+
210+
A generator directory is script-driven when its `mrdocs-generator.yml` names an entry script:
211+
212+
[source,yaml]
213+
----
214+
script: generator.lua
215+
----
216+
217+
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`.
218+
219+
=== The `generate` entry point
220+
221+
The script defines a single entry point:
222+
223+
[source]
224+
----
225+
generate(corpus, output)
226+
----
227+
228+
`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.
229+
230+
`output.write(relativePath, contents)` writes one file. The path is resolved under the output 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.
231+
232+
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.
233+
234+
In Lua, `generate` may be the value the script returns or a global function; in JavaScript it is a global function:
235+
236+
[source,lua]
237+
----
238+
return function(corpus, output)
239+
-- ...
240+
end
241+
----
242+
243+
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.
244+
245+
=== Example: a search index
246+
247+
This generator emits a single search-index.json aggregating every symbol, an artifact no per-page generator can produce:
248+
249+
[source,lua]
250+
----
251+
-- Quote a string as a JSON value.
252+
local function json_string(s)
253+
s = s:gsub('\\', '\\\\'):gsub('"', '\\"')
254+
return '"' .. s .. '"'
255+
end
256+
257+
return function(corpus, output)
258+
local entries = {}
259+
for _, sym in ipairs(corpus.symbols) do
260+
local name = sym.name or ""
261+
if name ~= "" then
262+
entries[#entries + 1] =
263+
'{"name":' .. json_string(name) ..
264+
',"url":' .. json_string(sym._id .. ".html") .. "}"
265+
end
266+
end
267+
output.write(
268+
"search-index.json",
269+
"[" .. table.concat(entries, ",") .. "]")
270+
end
271+
----
272+
203273
== Stylesheet Options
204274

205275
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):

docs/mrdocs.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@
252252
},
253253
"generator": {
254254
"default": "adoc",
255-
"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>/.",
255+
"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.",
256256
"title": "Generator used to create the documentation",
257257
"type": "string"
258258
},

src/lib/ConfigOptions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@
397397
{
398398
"name": "generator",
399399
"brief": "Generator used to create the documentation",
400-
"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>/.",
400+
"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.",
401401
"type": "string",
402402
"default": "adoc"
403403
},
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- A script-driven generator.
2+
--
3+
-- Where a per-page generator writes one file per symbol, this one emits
4+
-- a single search-index.json aggregating every symbol. That aggregated
5+
-- shape is exactly what the per-page generators cannot produce, and the
6+
-- reason a generator owns its emit loop.
7+
--
8+
-- The entry point is `generate(corpus, output)`:
9+
-- `corpus.symbols` is the array of symbols, each with a `name` and
10+
-- a flat `_id` usable as a stable per-symbol URL fragment.
11+
-- `output.write(relativePath, contents)` writes one file under the
12+
-- output directory.
13+
14+
-- Quote a string as a JSON value, escaping the two characters that a
15+
-- bare double-quoted JSON string may not contain literally.
16+
local function json_string(s)
17+
s = s:gsub('\\', '\\\\'):gsub('"', '\\"')
18+
return '"' .. s .. '"'
19+
end
20+
21+
return function(corpus, output)
22+
local entries = {}
23+
for _, sym in ipairs(corpus.symbols) do
24+
local name = sym.name or ""
25+
if name ~= "" then
26+
entries[#entries + 1] =
27+
' {"name":' .. json_string(name) ..
28+
',"url":' .. json_string(sym._id .. ".html") .. "}"
29+
end
30+
end
31+
output.write(
32+
"search-index.json",
33+
"[\n" .. table.concat(entries, ",\n") .. "\n]\n")
34+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# A script-driven generator. The script named here owns the whole
2+
# emit: it walks the corpus and writes whatever files it wants through
3+
# the output object. Naming a script is what marks this directory as a
4+
# script-driven generator rather than a data-driven (template) one.
5+
script: generator.lua
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
addons-supplemental:
2+
- addons
3+
generator: search-index
4+
multipage: false
5+
show-namespaces: false
6+
warn-if-undocumented: false
7+
source-root: .
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// A small input for the search-index example generator. The generator
2+
// walks every symbol and emits a single aggregated search-index.json,
3+
// so a handful of distinct names is enough to show the aggregation.
4+
5+
/// Add two integers.
6+
int add(int a, int b);
7+
8+
/// Subtract two integers.
9+
int subtract(int a, int b);
10+
11+
/// A point in the plane.
12+
struct Point
13+
{
14+
int x;
15+
int y;
16+
};

0 commit comments

Comments
 (0)