Skip to content

Commit c331b3d

Browse files
committed
feat: declare output generators from extension scripts
An extension script now defines an output generator with `register_generator(id, fn)`, alongside any `register_transform` it declares, rather than a generator directory shipping a mrdocs-generator.yml that names a script. Both hooks receive one `ctx` object: `ctx.corpus` and `ctx.config` for a transform, and additionally `ctx.output` for a generator. This replaces the positional `generate(corpus, output, config, params)` entry point and the separate manifest-script discovery pass. A registered generator is a `dom::Function` the corpus owns, because the generator registry is a process-global that is never cleared. The build populates the corpus with the registered generators while extensions run; `GenerateAction` then resolves the requested generator from the corpus before falling back to the registry. A single language-agnostic runner invokes the function, so the two per-language generator runners collapse into one path. The manifest now carries only the data-driven generator fields, its escape rules and the parent it extends; the `script` and `params` keys are gone. The search-index example moves under addons/extensions and declares its generator with `register_generator`.
1 parent 1f02213 commit c331b3d

42 files changed

Lines changed: 601 additions & 1049 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ if (MRDOCS_BUILD_TESTS)
483483
"${PROJECT_BINARY_DIR}/src"
484484
)
485485
target_link_libraries(mrdocs-test PUBLIC mrdocs-core)
486+
target_link_libraries(mrdocs-test PRIVATE Lua::lua)
486487
if (MRDOCS_CLANG)
487488
target_compile_options(mrdocs-test PRIVATE -Wno-covered-switch-default)
488489
endif ()

docs/mrdocs.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@
282282
"default": [
283283
"html"
284284
],
285-
"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`, `xml`, and `noop` (which extracts but writes no output, useful for checking extraction warnings); 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. This option accepts a single generator (`generator: xml`), a list (`generator: [xml, adoc]`), or a comma-separated string (`generator: \"xml,adoc\"`); when more than one is given the documentation is produced once per generator.",
285+
"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`, `xml`, and `noop` (which extracts but writes no output, useful for checking extraction warnings); data-driven generators can be added by dropping a template folder under <addon>/generator/<name>/; a script-driven generator is declared by an extension with `register_generator` and produces the output itself. This option accepts a single generator (`generator: xml`), a list (`generator: [xml, adoc]`), or a comma-separated string (`generator: \"xml,adoc\"`); when more than one is given the documentation is produced once per generator.",
286286
"title": "Generator(s) used to create the documentation"
287287
},
288288
"global-namespace-index": {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-- Declare a "search-index" generator: a script-defined generator that
2+
-- aggregates every symbol into a single search-index.json, the kind of
3+
-- artifact the per-page generators cannot produce.
4+
--
5+
-- register_generator(id, fn) declares it next to any register_transform a
6+
-- script might also declare; selecting "generator: <id>" runs fn with one
7+
-- ctx. ctx.corpus.symbols is every symbol (each tagged with a flat _id so
8+
-- the generator can form stable per-symbol URLs) and ctx.output.write
9+
-- emits files under the output directory.
10+
11+
-- Quote a string as a JSON value.
12+
local function json_string(s)
13+
s = s:gsub('\\', '\\\\'):gsub('"', '\\"')
14+
return '"' .. s .. '"'
15+
end
16+
17+
register_generator("search-index", function(ctx)
18+
local entries = {}
19+
for _, sym in ipairs(ctx.corpus.symbols) do
20+
local name = sym.name or ""
21+
if name ~= "" then
22+
entries[#entries + 1] =
23+
'{"name":' .. json_string(name) ..
24+
',"url":' .. json_string(sym._id .. ".html") .. "}"
25+
end
26+
end
27+
ctx.output.write(
28+
"search-index.json",
29+
"[" .. table.concat(entries, ",") .. "]")
30+
end)

examples/generators/script-driven/search-index/addons/generator/search-index/generate.lua

Lines changed: 0 additions & 20 deletions
This file was deleted.

examples/generators/script-driven/search-index/addons/generator/search-index/mrdocs-generator.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/lib/ConfigOptions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@
443443
{
444444
"name": "generator",
445445
"brief": "Generator(s) used to create the documentation",
446-
"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`, `xml`, and `noop` (which extracts but writes no output, useful for checking extraction warnings); 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. This option accepts a single generator (`generator: xml`), a list (`generator: [xml, adoc]`), or a comma-separated string (`generator: \"xml,adoc\"`); when more than one is given the documentation is produced once per generator.",
446+
"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`, `xml`, and `noop` (which extracts but writes no output, useful for checking extraction warnings); data-driven generators can be added by dropping a template folder under <addon>/generator/<name>/; a script-driven generator is declared by an extension with `register_generator` and produces the output itself. This option accepts a single generator (`generator: xml`), a list (`generator: [xml, adoc]`), or a comma-separated string (`generator: \"xml,adoc\"`); when more than one is given the documentation is produced once per generator.",
447447
"type": "string-list",
448448
"default": ["html"]
449449
},

src/lib/CorpusImpl.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,4 +1102,36 @@ CorpusImpl::finalize()
11021102
}
11031103
}
11041104

1105+
void
1106+
CorpusImpl::
1107+
registerScriptGenerator(std::string id, dom::Function fn)
1108+
{
1109+
if (findScriptGenerator(id) == nullptr)
1110+
{
1111+
scriptGenerators_.emplace_back(std::move(id), std::move(fn));
1112+
}
1113+
}
1114+
1115+
dom::Function const*
1116+
CorpusImpl::
1117+
findScriptGenerator(std::string_view id) const noexcept
1118+
{
1119+
dom::Function const* result = nullptr;
1120+
for (auto const& entry : scriptGenerators_)
1121+
{
1122+
if (result == nullptr && std::string_view(entry.first) == id)
1123+
{
1124+
result = &entry.second;
1125+
}
1126+
}
1127+
return result;
1128+
}
1129+
1130+
void
1131+
CorpusImpl::
1132+
keepScriptVmAlive(std::shared_ptr<void> keepAlive)
1133+
{
1134+
scriptVmKeepAlives_.push_back(std::move(keepAlive));
1135+
}
1136+
11051137
} // mrdocs

src/lib/CorpusImpl.hpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
#include <lib/Support/Debug.hpp>
2121
#include <mrdocs/ADT/UnorderedStringMap.hpp>
2222
#include <mrdocs/Corpus.hpp>
23+
#include <mrdocs/Dom.hpp>
2324
#include <mrdocs/Metadata.hpp>
2425
#include <mrdocs/Support/Error.hpp>
2526
#include <clang/Tooling/CompilationDatabase.h>
2627
#include <functional>
2728
#include <map>
29+
#include <memory>
2830
#include <mutex>
2931
#include <set>
3032
#include <string>
33+
#include <string_view>
34+
#include <utility>
35+
#include <vector>
3136

3237
namespace mrdocs {
3338

@@ -56,6 +61,22 @@ class CorpusImpl final : public Corpus
5661
// The value is another map from the name to the Info.
5762
std::map<SymbolID, UnorderedStringMap<Symbol const*>> lookupCache_;
5863

64+
// Output generators an extension script defined via
65+
// `register_generator(id, fn)`. Each `fn` is a `dom::Function` that
66+
// stays runnable until this corpus is destroyed (after extensions run,
67+
// when a generator is selected). Its scripting VM is kept alive either
68+
// by the function itself or by a matching entry in
69+
// `scriptVmKeepAlives_`. First registration of an id wins; later ones
70+
// are ignored.
71+
std::vector<std::pair<std::string, dom::Function>> scriptGenerators_;
72+
73+
// Strong references to scripting VMs that back `scriptGenerators_` but
74+
// are only weakly held by the generator functions themselves (the
75+
// JavaScript backend works this way). Keeping a reference here lets
76+
// such a generator outlive the extension run that defined it, up to
77+
// this corpus's destruction.
78+
std::vector<std::shared_ptr<void>> scriptVmKeepAlives_;
79+
5980
friend class Corpus;
6081
friend class BaseMembersFinalizer;
6182
friend class OverloadsFinalizer;
@@ -194,6 +215,29 @@ class CorpusImpl final : public Corpus
194215
void
195216
finalize();
196217

218+
/** Register a script-defined output generator.
219+
220+
Called from an extension's `register_generator(id, fn)`. The first
221+
registration of a given id wins; later ones are ignored.
222+
*/
223+
void
224+
registerScriptGenerator(std::string id, dom::Function fn);
225+
226+
/** Return the script-defined generator with this id, or `nullptr`.
227+
*/
228+
dom::Function const*
229+
findScriptGenerator(std::string_view id) const noexcept;
230+
231+
/** Keep a scripting VM alive for the lifetime of this corpus.
232+
233+
A generator registered via `register_generator` may hold only a
234+
weak reference to the VM that defined it. The extension binding
235+
hands the VM over here so it outlives the extension run and stays
236+
usable when the generator is selected.
237+
*/
238+
void
239+
keepScriptVmAlive(std::shared_ptr<void> keepAlive);
240+
197241
private:
198242
/** Return the Info with the specified symbol ID.
199243

src/lib/Extensions/CorpusDom.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,13 @@ buildCorpusDom(CorpusImpl& corpus)
151151
return dom::Value(std::move(corpusObj));
152152
}
153153

154+
dom::Value
155+
buildTransformContext(CorpusImpl& corpus)
156+
{
157+
dom::Object ctx;
158+
ctx.set("corpus", buildCorpusDom(corpus));
159+
ctx.set("config", dom::Value(corpus.config.object()));
160+
return dom::Value(std::move(ctx));
161+
}
162+
154163
} // mrdocs

src/lib/Extensions/CorpusDom.hpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ namespace mrdocs {
1515

1616
class CorpusImpl;
1717

18-
/** Build the `corpus` argument passed to each registered corpus
19-
transform.
18+
/** Build the `ctx.corpus` object seen by extension scripts.
2019
2120
The returned value is a small object:
2221
@@ -33,6 +32,18 @@ class CorpusImpl;
3332
dom::Value
3433
buildCorpusDom(CorpusImpl& corpus);
3534

35+
/** Build the `ctx` argument passed to each registered corpus transform.
36+
37+
A transform receives one object so new capabilities can be added
38+
without changing its signature:
39+
40+
- `ctx.corpus` -- the navigable corpus (see @ref buildCorpusDom) the
41+
transform reads and mutates in place.
42+
- `ctx.config` -- the generation configuration.
43+
*/
44+
dom::Value
45+
buildTransformContext(CorpusImpl& corpus);
46+
3647
} // mrdocs
3748

3849
#endif

0 commit comments

Comments
 (0)