///|
priv enum HelpQuery {
Overview
All
Schemas
Schema(String)
Format(@lib.DocumentFormat)
Operation(@lib.DocumentFormat?, @lib.CapabilityCommand)
}
///|
fn optional_value(matches : @argparse.Matches, name : String) -> String? {
match matches.values.get(name) {
Some([value, ..]) => Some(value)
_ => None
}
}
///|
fn edit_distance(left : String, right : String) -> Int {
let left_chars = left.to_array()
let right_chars = right.to_array()
let seed = Array::make(right_chars.length() + 1, 0)
for index in 0..<=right_chars.length() {
seed[index] = index
}
let last = for left_index in 0.. (String, Int)? {
let bounded = bounded_text(value.to_lower(), 64)
let mut nearest : String? = None
let mut best = 1_000_000
for candidate in candidates {
let distance = edit_distance(bounded, candidate)
if distance < best {
best = distance
nearest = Some(candidate)
}
}
if best <= 2 {
nearest.map(candidate => (candidate, best))
} else {
None
}
}
///|
fn nearest_suggestion(value : String, candidates : Array[String]) -> String? {
nearest_suggestion_with_distance(value, candidates).map(result => result.0)
}
///|
fn unknown_help_token(
kind : String,
value : String,
candidates : Array[String],
) -> @lib.ProtocolError {
let bounded = bounded_text(value, 80)
let suggestion = nearest_suggestion(bounded, candidates)
let message = match (suggestion, candidates) {
(Some(candidate), _) =>
"unknown \{kind} '\{bounded}'; did you mean '\{candidate}'?"
(None, []) =>
"unknown \{kind} '\{bounded}'; no \{kind} capabilities are implemented for this operation"
(None, _) =>
"unknown \{kind} '\{bounded}'; expected one of: " + candidates.join(", ")
}
let suggestions = match suggestion {
Some(candidate) => [Json::string(candidate)]
None => candidates.map(candidate => Json::string(candidate))
}
@lib.protocol_error(
"office.unknown_\{kind}",
message,
details=Json::object({
"kind": Json::string(kind),
"token": Json::string(bounded),
"suggestions": Json::array(suggestions),
}),
)
}
///|
fn format_help_tokens() -> Array[String] {
let tokens = ["all"]
for format in @lib.capability_formats() {
tokens.push(format.name)
for alternate in format.aliases {
tokens.push(alternate)
}
}
tokens
}
///|
fn operation_help_tokens(format? : @lib.DocumentFormat) -> Array[String] {
let tokens : Array[String] = []
for command in @lib.capability_commands() {
let supported = match format {
Some(selected) => command_supports_format(command, selected)
None => true
}
if supported {
tokens.push(command.name)
for alternate in command.aliases {
tokens.push(alternate)
}
}
}
tokens
}
///|
fn unknown_first_help_token(value : String) -> @lib.ProtocolError {
let formats = format_help_tokens()
let operations = operation_help_tokens()
let format_match = nearest_suggestion_with_distance(value, formats)
let operation_match = nearest_suggestion_with_distance(value, operations)
match (format_match, operation_match) {
(Some((_, format_distance)), Some((_, operation_distance))) =>
if operation_distance < format_distance {
unknown_help_token("operation", value, operations)
} else {
unknown_help_token("format", value, formats)
}
(Some(_), None) => unknown_help_token("format", value, formats)
(None, Some(_)) => unknown_help_token("operation", value, operations)
(None, None) => {
let topics = ["schema", "schemas"]
for format in formats {
topics.push(format)
}
for operation in operations {
topics.push(operation)
}
unknown_help_token("topic", value, topics)
}
}
}
///|
fn command_supports_format(
command : @lib.CapabilityCommand,
format : @lib.DocumentFormat,
) -> Bool {
command.formats.contains(format.name())
}
///|
fn resolve_help_query(
first : String?,
second : String?,
third : String?,
) -> HelpQuery raise CliFailure {
guard first is Some(first) else { return Overview }
let normalized = first.to_lower()
if normalized == "schemas" {
if second is Some(value) {
raise CliFailure(unknown_help_token("element", value, []))
}
return Schemas
}
if normalized == "schema" {
guard second is Some(id) else {
raise CliFailure(
@lib.protocol_error(
"office.invalid_arguments",
"office help schema requires one input contract id",
details=Json::object({
"expected": Json::string("office help schema "),
}),
),
)
}
if third is Some(element) {
raise CliFailure(unknown_help_token("element", element, []))
}
if !input_contract_ids().contains(id) {
raise CliFailure(unknown_help_token("schema", id, input_contract_ids()))
}
return Schema(id)
}
if normalized == "all" {
if second is Some(value) {
raise CliFailure(unknown_help_token("element", value, []))
}
return All
}
match @lib.resolve_format_alias(normalized) {
Some(format) => {
guard second is Some(operation) else { return Format(format) }
let command = match @lib.find_capability_command(operation) {
Some(command) if command_supports_format(command, format) => command
_ =>
raise CliFailure(
unknown_help_token(
"operation",
operation,
operation_help_tokens(format~),
),
)
}
if third is Some(element) {
raise CliFailure(unknown_help_token("element", element, []))
}
Operation(Some(format), command)
}
None =>
match @lib.find_capability_command(normalized) {
Some(command) => {
if second is Some(element) {
raise CliFailure(unknown_help_token("element", element, []))
}
Operation(None, command)
}
None => raise CliFailure(unknown_first_help_token(first))
}
}
}
///|
fn render_field(field : @lib.CapabilityField) -> String {
let required = if field.required { "required" } else { "optional" }
" \{field.name} (\{field.type_name}, \{required}) — \{field.description}"
}
///|
fn capability_help_text(command : @lib.CapabilityCommand) -> String {
let lines : Array[String] = [
"Command: \{command.name}",
" \{command.summary}",
" Usage: \{command.usage}",
]
if !command.formats.is_empty() {
lines.push(" Formats: " + command.formats.join(", "))
}
lines.push(" Inputs:")
for field in command.inputs {
lines.push(render_field(field))
}
lines.push(" Outputs:")
for field in command.outputs {
lines.push(render_field(field))
}
lines.push(" Output modes: " + command.output_modes.join(", "))
if !command.variants.is_empty() {
lines.push(" Variants:")
for variant in command.variants {
lines.push(" \{variant.name}: \{variant.usage}")
for field in variant.inputs {
lines.push(" " + render_field(field))
}
if !variant.constraints.is_empty() {
lines.push(" constraints: " + variant.constraints.join(", "))
}
}
}
lines.join("\n")
}
///|
fn render_format(format : @lib.DocumentFormat) -> String {
let mut declaration : @lib.CapabilityFormat? = None
for declared in @lib.capability_formats() {
if declared.name == format.name() {
declaration = Some(declared)
break
}
}
let (aliases, description, selector) = match declaration {
Some(declared) =>
(declared.aliases, declared.description, Some(declared.selector))
None => ([], "", None)
}
let heading = if aliases.is_empty() {
"Format: \{format.name()}"
} else {
"Format: \{format.name()} (aliases: \{aliases.join(", ")})"
}
let lines : Array[String] = [heading, " \{description}"]
match selector {
Some(syntax) => {
lines.push(
" Selector: \{syntax.schema} \{syntax.root} (\{syntax.status})",
)
lines.push(" \{syntax.description}")
for example in syntax.examples {
lines.push(" \{example}")
}
}
None => ()
}
lines.push(" Implemented commands:")
for command in @lib.capability_commands() {
if command_supports_format(command, format) {
lines.push(" \{command.name} — \{command.summary}")
}
}
lines.push(" Run: office help \{format.name()} identify")
lines.join("\n")
}
///|
fn render_overview(verbose : Bool) -> String {
let lines : Array[String] = [
"Office capability registry",
" Schema: " + @lib.SCHEMA_CAPABILITIES,
" Fingerprint: \{@lib.capability_fingerprint()}",
"Formats:",
]
for format in @lib.capability_formats() {
let aliases = if format.aliases.is_empty() {
""
} else {
" (aliases: \{format.aliases.join(", ")})"
}
lines.push(" \{format.name}\{aliases} — \{format.description}")
}
lines.push("Commands:")
for command in @lib.capability_commands() {
lines.push(" \{command.name} — \{command.summary}")
if verbose {
lines.push(" \{command.usage}")
lines.push(" output modes: \{command.output_modes.join(", ")}")
for variant in command.variants {
lines.push(" \{variant.usage}")
}
}
}
if verbose {
lines.push("Consumed input contracts:")
lines.push(" office help schemas [--json|--jsonl]")
lines.push(" office help schema [--json|--jsonl]")
}
lines.push(
"Use 'office help ' or 'office help '.",
)
lines.join("\n")
}
///|
fn records_for_query(query : HelpQuery) -> Array[Json] {
match query {
Overview | All => @lib.capability_records()
Schemas => [input_contract_inventory()]
Schema(id) =>
match find_input_contract(id) {
Some(record) => [record]
None => []
}
Format(format) => @lib.capability_records(format~)
Operation(Some(format), command) =>
@lib.capability_records(format~, operation=command.name)
Operation(None, command) => @lib.capability_records(operation=command.name)
}
}
///|
fn data_for_query(query : HelpQuery) -> Json {
match query {
Overview | All => @lib.capabilities_data()
Schemas => input_contract_inventory()
Schema(id) =>
match find_input_contract(id) {
Some(record) => record
None => Json::empty_object()
}
Format(format) => @lib.capabilities_data(format~)
Operation(Some(format), command) =>
@lib.capabilities_data(format~, operation=command.name)
Operation(None, command) => @lib.capabilities_data(operation=command.name)
}
}
///|
fn render_human_query(query : HelpQuery) -> String {
match query {
Overview => render_overview(false)
All => render_overview(true)
Schemas => render_input_contract_inventory()
Schema(id) =>
match find_input_contract(id) {
Some(record) => record.stringify(indent=2)
None => ""
}
Format(format) => render_format(format)
Operation(_, command) => capability_help_text(command)
}
}
///|
fn run_help(matches : @argparse.Matches) -> Unit raise CliFailure {
let query = resolve_help_query(
optional_value(matches, "first"),
optional_value(matches, "second"),
optional_value(matches, "third"),
)
let json = matches.flags.get_or_default("json", false)
let jsonl = matches.flags.get_or_default("jsonl", false)
if json && jsonl {
raise CliFailure(
@lib.protocol_error(
"office.output_mode_conflict", "--json and --jsonl are mutually exclusive",
),
)
}
if json {
println(@lib.output_success(data_for_query(query)).stringify(indent=2))
} else if jsonl {
for record in records_for_query(query) {
println(record.stringify())
}
} else {
println(render_human_query(query))
}
}
///|
fn help_command() -> @argparse.Command {
let summary = match @lib.find_capability_command("help") {
Some(command) => command.summary
None => "Show the implemented Office capability registry"
}
Command(
"help",
about=summary,
positionals=[
PositionArg("first", num_args=ValueRange(lower=0, upper=1)),
PositionArg("second", num_args=ValueRange(lower=0, upper=1)),
PositionArg("third", num_args=ValueRange(lower=0, upper=1)),
],
flags=[
FlagArg("json", long="json", about="print office.output/1 JSON"),
FlagArg("jsonl", long="jsonl", about="print one JSON record per line"),
],
)
}