///|
fn option_def_to_json(opt : OptionDef) -> Json {
let m : Map[String, Json] = {
"description": ToJson::to_json(opt.description),
"required": ToJson::to_json(opt.required),
"global": ToJson::to_json(opt.global),
}
match opt.kind {
StringOpt(default) => {
m["type"] = Json::string("string")
if default is Some(value) {
m["default"] = ToJson::to_json(value)
}
}
IntOpt(default) => {
m["type"] = Json::string("int")
if default is Some(value) {
m["default"] = ToJson::to_json(value)
}
}
BoolOpt => {
m["type"] = Json::string("bool")
m["style"] = Json::string("flag")
}
}
if opt.short is Some(short) {
m["short"] = ToJson::to_json(short.to_string())
}
Json::object(m)
}
///|
fn positional_def_to_json(
pos : PositionalDef,
multiple : Bool,
index : Int32,
) -> Json {
Json::object({
"description": ToJson::to_json(pos.description),
"required": ToJson::to_json(pos.required),
"index": ToJson::to_json(index),
"min_args": ToJson::to_json(if pos.required { 1 } else { 0 }),
"max_args": if multiple {
Json::null()
} else {
ToJson::to_json(1)
},
})
}
///|
fn generate_example(
path : String,
cmd : CommandDef,
globals_only~ : Bool = false,
) -> String {
let parts = [path]
for opt in cmd.options {
if opt.required && (!globals_only || opt.global) {
parts.push("--\{opt.name} <\{opt.name}>")
}
}
for pos in cmd.positionals {
if pos.required && !globals_only {
parts.push("<\{pos.name}>")
}
}
parts.join(" ")
}
///|
fn command_def_to_json(cmd : CommandDef, path : String) -> Json {
let m : Map[String, Json] = {
"description": ToJson::to_json(cmd.description),
}
if !cmd.options.is_empty() {
let options : Map[String, Json] = Map([])
for opt in cmd.options {
options[opt.name] = option_def_to_json(opt)
}
m["options"] = Json::object(options)
}
if !cmd.positionals.is_empty() {
let positionals : Map[String, Json] = Map([])
for i, pos in cmd.positionals {
positionals[pos.name] = positional_def_to_json(
pos,
positional_is_multiple(pos, i, cmd.positionals.length()),
i,
)
}
m["positionals"] = Json::object(positionals)
}
if !cmd.examples.is_empty() {
m["examples"] = ToJson::to_json(cmd.examples)
} else {
m["examples"] = ToJson::to_json([generate_example(path, cmd)])
}
if !cmd.subcommands.is_empty() {
let commands : Map[String, Json] = Map([])
for sub in cmd.subcommands {
commands[sub.name] = command_def_to_json(
sub,
"\{generate_example(path, cmd, globals_only=true)} \{sub.name}",
)
}
m["commands"] = Json::object(commands)
}
Json::object(m)
}
///|
/// Exports normalized definitions with typed defaults, global metadata and root
/// positionals/examples. Only explicitly global options are inherited at runtime.
pub fn CliApp::render_schema_json(self : CliApp) -> Json {
guard command_def_to_json(self.root, self.root.name) is Json::Object(fields) else {
abort("object")
}
let m = fields.copy()
m["name"] = ToJson::to_json(self.root.name)
m["version"] = ToJson::to_json(self.version)
Json::object(m)
}
///|
/// Returns the application schema as JSON text.
pub fn CliApp::render_schema(self : CliApp) -> String {
self.render_schema_json().stringify()
}