Skip to content
This repository was archived by the owner on Aug 29, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ zero_sized_map_values = "warn"

[workspace.dependencies]
# Vendor
clap = { version = "4.6.6", default-features = false, features = ["std"] }
# Temporary until clap-rs/clap#6494 is merged and released.
clap = { git = "https://github.com/zerosnacks/clap", rev = "2883a7f13718bb4c3cf0456cf831176d20a2ae14", version = "4.6.6", default-features = false, features = ["std"] }
Comment on lines +165 to +166

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: remove

proc-macro-crate = "3.5.0"
proc-macro2 = "1.0.107"
quote = "1.0.47"
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ deployctl deploy --environment production api

The command path and canonical invocation contract come from Clap. Global argument scope, positional
order, canonical option spellings, value arity, lexical defaults and possible values, conflicts,
argument-group cardinality, repeatability, delimiters, value terminators, required `=` syntax,
required `--` syntax, and exclusivity are reflected from the built command model. The `output` field is the JSON Schema of the successful Rust result.
overrides, conditional requirements, argument groups, repeatability, delimiters, value terminators,
required `=` syntax, required `--` syntax, and exclusivity are reflected from the built command
model. The `output` field is the JSON Schema of the successful Rust result.

This gives agents a canonical process-style invocation contract without making rendered Clap help
part of the wire format. Clap remains authoritative for parser-specific validation that cannot be
Expand Down Expand Up @@ -288,7 +289,7 @@ When Rust code already knows which command it wants to inspect, lower-level look

Paths accept Clap aliases, while returned paths are always canonical.

Generated contracts include canonical invocation metadata such as names, global argument scope and canonical ownership, positional order, value arity, lexical defaults and possible values, repeatability, conflicts, argument-group cardinality, and token-level syntax requirements. They do not replace Clap's argument parser: Clap remains authoritative for parser-specific validation that cannot be reflected structurally.
Generated contracts include canonical invocation metadata such as names, global argument scope and canonical ownership, positional order, value arity, lexical defaults and possible values, repeatability, conflicts, overrides, conditional requirements, argument-group constraints, and token-level syntax requirements. They do not replace Clap's argument parser: Clap remains authoritative for parser-specific validation that cannot be reflected structurally.

## Application-defined extensions

Expand Down
104 changes: 88 additions & 16 deletions SPECIFICATION.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,15 +437,15 @@ Apache License 2.0
Apache License 2.0

#### Used by
- [clap]( https://github.com/clap-rs/clap ) 4.6.6
- [clap_builder]( https://github.com/clap-rs/clap ) 4.6.6
- [clap_derive]( https://github.com/clap-rs/clap ) 4.6.4
- [clap_lex]( https://github.com/clap-rs/clap ) 1.1.0
- [anstream]( https://github.com/rust-cli/anstyle.git ) 1.0.0
- [anstyle-parse]( https://github.com/rust-cli/anstyle.git ) 1.0.0
- [anstyle-query]( https://github.com/rust-cli/anstyle.git ) 1.1.5
- [anstyle-wincon]( https://github.com/rust-cli/anstyle.git ) 3.0.11
- [anstyle]( https://github.com/rust-cli/anstyle.git ) 1.0.14
- [clap]( https://github.com/clap-rs/clap ) 4.6.6
- [clap_builder]( https://github.com/clap-rs/clap ) 4.6.6
- [clap_derive]( https://github.com/clap-rs/clap ) 4.6.4
- [clap_lex]( https://github.com/clap-rs/clap ) 1.1.0
- [colorchoice]( https://github.com/rust-cli/anstyle.git ) 1.0.5
- [escargot]( https://github.com/crate-ci/escargot.git ) 0.5.15
- [is_terminal_polyfill]( https://github.com/polyfill-rs/is_terminal_polyfill ) 1.70.2
Expand Down
76 changes: 76 additions & 0 deletions crates/clap_schema/examples/relationship_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Reflection of advanced invocation relationships and group constraints.

use std::convert::Infallible;

use clap::{Arg, ArgAction, ArgGroup, Command};
use clap_schema::{ContractBuilder, schema_handler};

/// Typed command used to register the example handler contract.
struct CreateCommand;

/// Example handler whose successful output is intentionally empty.
#[schema_handler(CreateCommand)]
const fn create(_command: CreateCommand) -> Result<(), Infallible> {
Ok(())
}

/// Build a command that exercises reflected argument relationships and groups.
fn cli() -> Command {
Command::new("fixture").subcommand(
Command::new("create")
.about("Create a resource")
.arg(Arg::new("mode").long("mode"))
.arg(Arg::new("format").long("format"))
.arg(Arg::new("source").long("source"))
.arg(Arg::new("auth").long("auth"))
.arg(Arg::new("input").long("input"))
.arg(Arg::new("stdin").long("stdin").action(ArgAction::SetTrue))
.arg(Arg::new("file").long("file"))
.arg(Arg::new("host").long("host"))
.arg(Arg::new("port").long("port"))
.arg(Arg::new("legacy").long("legacy"))
.arg(
Arg::new("count")
.long("count")
.value_parser(clap::value_parser!(i64))
.allow_negative_numbers(true),
)
.arg(
Arg::new("config")
.long("config")
.num_args(0..=1)
.default_value("fallback")
.default_missing_value("default-missing")
.default_value_if("mode", "auto", Some("generated"))
.overrides_with("legacy")
.requires("selector")
.requires_if("special", "input")
.required_if_eq_any([("format", "json"), ("mode", "strict")])
.required_if_eq_all([("source", "remote"), ("auth", "token")])
.required_unless_present_any(["stdin", "file"])
.required_unless_present_all(["host", "port"]),
)
.group(ArgGroup::new("selector").args(["mode", "format"]).multiple(true))
.group(ArgGroup::new("irrelevant").args(["source", "host"]).multiple(true))
.group(
ArgGroup::new("transport")
.args(["stdin", "file"])
.required(true)
.multiple(true)
.requires("auth")
.conflicts_with("legacy"),
),
)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let contract = ContractBuilder::new(cli()).command::<CreateCommand>(["create"]).build()?;

let command = contract.command(&["create"])?;
println!("{}", serde_json::to_string_pretty(&command)?);

// Keep the example handler an ordinary callable Rust function as well.
create(CreateCommand)?;

Ok(())
}
Loading
Loading