atproto_codegen/plugin
Minimal plugin host.
A Plugin receives a PluginContext (the config, the decoded lexicon
docs, the lowered view, and every upstream plugin’s own output so far)
and returns the OutputFiles it contributes. An OutputFile.parts is
not a whole file: it is that plugin’s ordered contribution, one entry
per lower.CodecUnit in the module (see lower.codec_units). Plugins
that cover the same units (today: types and decode-json, one def or
synthesized union per unit) therefore produce equal-length parts lists
for the same path.
run executes the registry in dependency order (a plugin runs after
everything in its depends_on) and hands each plugin the outputs placed
so far, keyed by plugin name. assemble then merges, for a given path,
the named plugins’ parts lists by zipping them position for position
in the caller-supplied order and joining the result with a blank line:
this is what reproduces the original emitter’s interleaving of a def’s
type/fields/encoder with its decoder, and a union’s type/encoder with
its decoder, without either plugin knowing about the other’s text.
A plugin that owns an entire module instead of contributing per-unit
parts emits a WholeFile(path, content) for that path: assemble
writes its content verbatim, with no zipping and no blank-line joining.
This is for a plugin (e.g. xrpc-client) whose output is one complete
module (its own doc, imports, everything) that no other plugin
interleaves with, rather than a per-CodecUnit slice of a shared module.
Two collisions are rejected as ConflictingWholeFile(path): a path
claimed as WholeFile that also has parts from any plugin, and a path
claimed as WholeFile by more than one plugin.
Order resolution is a static, dependency-respecting placement: it is not a general topological sort with cycle diagnostics, just enough to catch an unknown dependency name or a cycle across the handful of plugins this package registers.
Types
pub type CodegenError {
UnknownDependency(plugin: String, missing: String)
UnresolvedOrder(remaining: List(String))
MismatchedSections(path: String)
ConflictingWholeFile(path: String)
PluginFailure(plugin: String, reason: String)
}
Constructors
-
UnknownDependency(plugin: String, missing: String) -
UnresolvedOrder(remaining: List(String)) -
MismatchedSections(path: String) -
ConflictingWholeFile(path: String) -
PluginFailure(plugin: String, reason: String)
pub type OutputFile {
OutputFile(path: String, parts: List(String))
WholeFile(path: String, content: String)
}
Constructors
-
OutputFile(path: String, parts: List(String)) -
WholeFile(path: String, content: String)
pub type Plugin {
Plugin(
name: String,
depends_on: List(String),
emit: fn(PluginContext) -> Result(
List(OutputFile),
CodegenError,
),
)
}
Constructors
-
Plugin( name: String, depends_on: List(String), emit: fn(PluginContext) -> Result( List(OutputFile), CodegenError, ), )
pub type PluginContext {
PluginContext(
cfg: config.Config,
docs: List(ast.LexiconDoc),
lexicons: List(lower.FlatLexicon),
upstream: dict.Dict(String, List(OutputFile)),
)
}
Constructors
-
PluginContext( cfg: config.Config, docs: List(ast.LexiconDoc), lexicons: List(lower.FlatLexicon), upstream: dict.Dict(String, List(OutputFile)), )
Values
pub fn assemble(
outputs: dict.Dict(String, List(OutputFile)),
order: List(String),
) -> Result(List(#(String, String)), CodegenError)
Merge, for every path any of order‘s plugins produced, their outputs
into the final file text. A path claimed by a WholeFile yields its
content verbatim; otherwise the plugins’ parts lists are zipped
position for position (in order) and joined the way the original
emitter joined its chunks.
pub fn run(
plugins: List(Plugin),
cfg: config.Config,
docs: List(ast.LexiconDoc),
lexicons: List(lower.FlatLexicon),
) -> Result(dict.Dict(String, List(OutputFile)), CodegenError)