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

pub type PluginContext {
  PluginContext(
    cfg: config.Config,
    docs: List(ast.LexiconDoc),
    lexicons: List(lower.FlatLexicon),
    upstream: dict.Dict(String, List(OutputFile)),
  )
}

Constructors

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)
Search Document