use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::crate_name::{crate_name, FoundCrate};
use crate::model::{
rendered_path, to_kebab, type_name, AdmonitionKind, ArgGroup, ArgGroupMember, Cli,
ConditionalDefault, Dispatch, DoubleDash, ExampleDecl, Field, HeadingDecl, Kind, SchemaSource,
Shape, Subcommands, ValueEnum, Variant, ViewDecl,
};
fn admonitions(field: &Field) -> TokenStream {
let entries = field.admonitions.iter().map(|admonition| {
let text = &admonition.text;
let kind = match admonition.kind {
AdmonitionKind::Note => quote!(usage_argv::spec::AdmonitionKind::Note),
AdmonitionKind::Warning => quote!(usage_argv::spec::AdmonitionKind::Warning),
};
quote!(usage_argv::spec::AdmonitionMeta { kind: #kind, text: #text })
});
quote!(&[#(#entries),*])
}
fn built_value(cli: &Cli, sub_build: &TokenStream, fields: &[TokenStream]) -> TokenStream {
if cli.unit {
debug_assert!(cli.fields.is_empty());
quote!(Self)
} else {
let field_reads = cli.fields.iter().map(|field| &field.ident);
quote! {
{
let __usage_built = Self {
#sub_build
#(#fields),*
};
#(let _ = &__usage_built.#field_reads;)*
__usage_built
}
}
}
}
fn validated_value(cli: &Cli, built: &TokenStream) -> TokenStream {
let Some(validate_with) = &cli.validate_with else {
return built.clone();
};
quote! {{
let __usage_built = #built;
#validate_with(&__usage_built)
.map_err(usage_argv::ValidationError::into_parse_error)?;
__usage_built
}}
}
fn runtime_path() -> TokenStream {
match crate_name("usage-argv") {
Ok(FoundCrate::Name(name)) => {
let runtime = format_ident!("{name}");
quote!(::#runtime)
}
_ => match crate_name("usage-rs") {
Ok(FoundCrate::Itself) => quote!(::usage_rs::argv),
Ok(FoundCrate::Name(name)) => {
let facade = format_ident!("{name}");
quote!(::#facade::argv)
}
_ => quote!(::usage_argv),
},
}
}
fn derive_path() -> TokenStream {
match crate_name("usage-derive") {
Ok(FoundCrate::Name(name)) => {
let derive = format_ident!("{name}");
quote!(::#derive)
}
_ => match crate_name("usage-rs") {
Ok(FoundCrate::Itself) => quote!(::usage_rs),
Ok(FoundCrate::Name(name)) => {
let facade = format_ident!("{name}");
quote!(::#facade)
}
_ => quote!(::usage_derive),
},
}
}
fn schemars_path() -> TokenStream {
match crate_name("schemars") {
Ok(FoundCrate::Itself) => quote!(::schemars),
Ok(FoundCrate::Name(name)) => {
let schemars = format_ident!("{}", name.replace('-', "_"));
quote!(::#schemars)
}
_ => quote!(::schemars),
}
}
fn validation_path() -> TokenStream {
match crate_name("usage-validation") {
Ok(FoundCrate::Itself) => quote!(::usage_validation),
Ok(FoundCrate::Name(name)) => {
let validation = format_ident!("{}", name.replace('-', "_"));
quote!(::#validation)
}
_ => match crate_name("usage-rs") {
Ok(FoundCrate::Itself) => quote!(::usage_rs::validation),
Ok(FoundCrate::Name(name)) => {
let facade = format_ident!("{}", name.replace('-', "_"));
quote!(::#facade::validation)
}
_ => quote!(::usage_validation),
},
}
}
pub fn emit(cli: &Cli) -> TokenStream {
let ident = &cli.ident;
let runtime = runtime_path();
let dispatch = emit_command_dispatch(cli, &runtime);
let validation = validation_path();
let validation_import = cli
.fields
.iter()
.any(|field| field.validate.is_some())
.then(|| quote!(use #validation as usage_validation;));
let flags: Vec<&Field> = cli
.fields
.iter()
.filter(|f| matches!(f.kind, Kind::Flag { .. }))
.collect();
let args: Vec<&Field> = cli
.fields
.iter()
.filter(|f| matches!(f.kind, Kind::Arg { .. }))
.collect();
let unknown_flags = unknown_flags_tokens(cli);
let default_subcommand = option_str(cli.default_subcommand.as_deref());
let multicall = cli.multicall;
let no_binary_name = cli.no_binary_name;
let arg_required_else_help = cli.arg_required_else_help;
let dont_delimit_trailing_values = cli.dont_delimit_trailing_values;
let args_override_self = cli.args_override_self;
let subcommand_negates_reqs = cli.subcommand_negates_reqs;
let args_conflicts_with_subcommands = cli.args_conflicts_with_subcommands;
let subcommand_precedence_over_arg = cli.subcommand_precedence_over_arg;
let allow_missing_positional = cli.allow_missing_positional;
let disable_help_flag = cli.disable_help_flag;
let disable_help_subcommand = cli.disable_help_subcommand;
let disable_version_flag = cli.disable_version_flag;
let subcommand_help_heading = option_str(cli.subcommand_help_heading.as_deref());
let subcommand_value_name = option_str(cli.subcommand_value_name.as_deref());
let next_line_help = cli.next_line_help;
let flatten_help = cli.flatten_help;
let term_width = option_usize(cli.term_width);
let max_term_width = option_usize(cli.max_term_width);
let usage = option_str(cli.usage.as_deref());
let help_template = option_str(cli.help_template.as_deref());
let restart_token = option_str(cli.restart_token.as_deref());
let mount = option_str(cli.mount.as_deref());
let OutputTokens {
decls: output_schema_decls,
outputs,
select,
exit_codes,
} = output_tokens(cli);
let flatten_checks = flatten_checks(cli);
let subcommand_required = cli.fields.iter().any(|f| {
matches!(
f.kind,
Kind::Subcommand {
optional: false,
..
}
)
});
let before_help = option_expr(cli.before_help.as_ref());
let before_long_help = option_expr(cli.before_long_help.as_ref());
let after_help = option_expr(cli.after_help.as_ref());
let after_long_help = option_expr(cli.after_long_help.as_ref());
let examples = examples_table(&cli.examples);
let headings = headings_table(&cli.headings);
let root_key = key_ident("COMMAND", None);
let keys = key_consts(&cli.fingerprint, flags.len(), args.len());
let flag_tables = flags.iter().enumerate().map(|(i, f)| flag_table(i, f));
let arg_tables = args.iter().enumerate().map(|(i, f)| arg_table(i, f));
let flag_metas = flags
.iter()
.enumerate()
.map(|(i, f)| flag_meta(cli, i, f, &cli.ident));
let arg_metas = args
.iter()
.enumerate()
.map(|(i, f)| arg_meta(cli, i, f, &cli.ident));
let clause_field = cli.fields.iter().find_map(|field| {
let Kind::Clause { ty, separator } = &field.kind else {
return None;
};
Some((field, ty, separator))
});
let clause_table = clause_field
.map(|(field, ty, separator)| {
let name = proc_macro2::Literal::string(&field.name);
let separator = separator.as_ref().map_or_else(
|| quote!(::core::option::Option::None),
|separator| {
let separator = proc_macro2::Literal::byte_string(separator.as_bytes());
quote!(::core::option::Option::Some(#separator))
},
);
quote!(::core::option::Option::Some(usage_argv::Clause {
key: 0,
name: #name,
separator: #separator,
flags: <#ty as usage_argv::spec::CommandArgs>::COMMAND.flags,
args: <#ty as usage_argv::spec::CommandArgs>::COMMAND.args,
}))
})
.unwrap_or_else(|| quote!(::core::option::Option::None));
let clause_meta = clause_field
.map(|(field, ty, separator)| {
let name = proc_macro2::Literal::string(&field.name);
let separator = option_str(separator.as_deref());
quote!(::core::option::Option::Some(usage_argv::spec::ClauseMeta {
name: #name,
separator: #separator,
help: ::core::option::Option::None,
long_help: ::core::option::Option::None,
flags: <#ty as usage_argv::spec::CommandArgs>::META.flags,
args: <#ty as usage_argv::spec::CommandArgs>::META.args,
canonical_selector: <#ty as usage_argv::spec::CommandArgs>::canonical_selector,
}))
})
.unwrap_or_else(|| quote!(::core::option::Option::None));
let implicit_clause_assertions = clause_field
.filter(|(_, _, separator)| separator.is_none())
.map(|(_, ty, _)| {
quote! {
const _: () = {
const INNER: &usage_argv::Command<'static> =
<#ty as usage_argv::spec::CommandArgs>::COMMAND;
assert!(
INNER.args.len() == 1,
"an implicit clause needs exactly one positional argument",
);
assert!(
INNER.args[0].required && !INNER.args[0].var,
"an implicit clause positional must be required and non-variadic",
);
assert!(
INNER.subcommands.is_empty(),
"an implicit clause cannot contain subcommands",
);
assert!(
INNER.args[0].sigil.is_none(),
"an implicit clause positional cannot use a sigil",
);
};
}
});
let tables = tables(cli);
let table_decls = &tables.decls;
let meta_table_decls = &tables.meta_decls;
let (group_meta_decl, group_meta_table_ref) = group_meta_table(cli);
let flag_table_ref = &tables.flags;
let arg_table_ref = &tables.args;
let flag_meta_table_ref = &tables.flag_metas;
let arg_meta_table_ref = &tables.arg_metas;
let flatten_group_table_ref = &tables.flatten_groups;
let name = &cli.name;
let bin = option_str(cli.bin.as_deref());
let version = match &cli.version {
Some(tokens) => quote!(::core::option::Option::Some(#tokens)),
None => quote!(::core::option::Option::None),
};
let author = option_expr(cli.author.as_ref());
let license = option_expr(cli.license.as_ref());
let repository = option_expr(cli.repository.as_ref());
let source_code_link_template = option_expr(cli.source_code_link_template.as_ref());
let about = cli
.about_attr
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(|| option_str(cli.about.as_deref()));
let long_about = cli
.long_about_attr
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(|| option_str(cli.long_about.as_deref()));
let deprecated = option_str(cli.deprecated.as_deref());
let deprecated_warn_at = option_str(cli.deprecated_warn_at.as_deref());
let deprecated_remove_at = option_str(cli.deprecated_remove_at.as_deref());
let surface = option_str(cli.surface.as_deref());
let available_if = &cli.available_if;
let parts = subcommand_parts(cli);
let sub_commands = parts
.as_ref()
.map(|p| p.commands.clone())
.unwrap_or_default();
let sub_default = parts
.as_ref()
.map(|p| p.default.clone())
.unwrap_or_default();
let sub_external = parts
.as_ref()
.map(|p| p.external.clone())
.unwrap_or_default();
let sub_metas = parts.as_ref().map(|p| p.metas.clone()).unwrap_or_default();
let sub_build = parts.as_ref().map(|p| p.build.clone()).unwrap_or_default();
let sub_default_view_build = parts
.as_ref()
.map(|p| p.default_view_build.clone())
.unwrap_or_default();
let partial = partial_struct(cli);
let argument_lookup = argument_lookup_functions(cli);
let deprecations = deprecations_fn(cli);
let defaults = partial_defaults(cli);
let merge = merge_fn(cli);
let resolves = cli.fields.iter().any(|f| f.setting.is_some()) || cli.settings;
let config = crate::config::config_path();
let config_extra = match &cli.config {
Some(ty) => quote! {
__usage_kdl.push_str(&#config::spec_kdl_with(
<#ty>::SETTINGS_PROPS,
<#ty>::SETTINGS_SPEC,
));
},
None => TokenStream::new(),
};
let (spec_endpoint, spec_endpoint_intercept) = spec_endpoint_fns(cli);
let parse_preamble = quote! {
let __usage_all: ::std::vec::Vec<::std::ffi::OsString> =
::std::env::args_os().collect();
let __usage_all_refs: ::std::vec::Vec<&::std::ffi::OsStr> =
__usage_all.iter().map(|a| a.as_os_str()).collect();
#spec_endpoint_intercept
let __usage_raw: ::std::vec::Vec<::std::ffi::OsString> =
if let ::std::option::Option::Some((__usage_argv0, __usage_words)) =
__usage_all_refs.split_first()
{
if let ::std::option::Option::Some(__usage_view) =
usage_argv::spec::view_for_program(&SPEC, __usage_argv0)
{
if __usage_words.first().is_some_and(|word| {
usage_argv::is_version_arg(SPEC.root.cmd, word)
})
{
__usage_words
.iter()
.map(|word| (*word).to_os_string())
.collect()
} else {
__usage_view
.root
.split_ascii_whitespace()
.map(::std::ffi::OsString::from)
.chain(
__usage_words
.iter()
.map(|word| (*word).to_os_string()),
)
.collect()
}
} else if SPEC.multicall {
let mut __usage_words: ::std::vec::Vec<::std::ffi::OsString> =
__usage_words
.iter()
.map(|word| (*word).to_os_string())
.collect();
if let ::std::option::Option::Some(__usage_word) =
__usage_argv0.to_str().and_then(|s| {
usage_argv::multicall_applet(s, SPEC.name, SPEC.bin)
})
{
__usage_words.insert(
0,
::std::ffi::OsString::from(__usage_word),
);
}
__usage_words
} else {
__usage_words
.iter()
.map(|word| (*word).to_os_string())
.collect()
}
} else {
::std::vec::Vec::new()
};
let __usage_argv: ::std::vec::Vec<&::std::ffi::OsStr> =
__usage_raw.iter().map(|a| a.as_os_str()).collect();
let __usage_selected_view = __usage_all
.first()
.and_then(|argv0| usage_argv::spec::view_for_program(&SPEC, argv0));
};
let parts = settings(cli);
let settings_given = parts.as_ref().filter(|_| resolves).map(|s| s.given.clone());
let settings_bindings = parts
.as_ref()
.filter(|_| resolves)
.map(|s| s.bindings.clone());
let settings_layer = resolves.then(|| settings_layer(&config));
let settings_guard = (!resolves).then(|| settings_guard(cli)).flatten();
let settings_binding_forward = settings_bindings.as_ref().map(|_| {
quote! {
pub const SETTINGS_BINDINGS: &'static [(&'static str, &'static str)] =
SETTINGS_BINDINGS;
}
});
let settings_parse = settings_bindings.as_ref().map(|_| {
let sub_build = sub_build.clone();
let field_finals: Vec<_> = cli
.fields
.iter()
.filter(|f| !matches!(f.kind, Kind::Subcommand { .. }))
.map(|field| field_final(field, None))
.collect();
let built = validated_value(cli, &built_value(cli, &sub_build, &field_finals));
quote! {
pub fn parse_from_with_settings<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<
(Self, #config::CliLayer),
usage_argv::Error<'static, 'v>,
> {
Self::__usage_parse_from_with_settings(argv, ::std::option::Option::None)
}
pub fn parse_from_with_settings_and_warnings<'v>(
argv: &[&'v ::std::ffi::OsStr],
warnings: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) -> ::std::result::Result<
(Self, #config::CliLayer),
usage_argv::Error<'static, 'v>,
> {
Self::__usage_parse_from_with_settings(
argv,
::std::option::Option::Some(warnings),
)
}
#[doc(hidden)]
fn __usage_parse_from_with_settings<'v>(
argv: &[&'v ::std::ffi::OsStr],
__usage_warnings: ::std::option::Option<
&mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
>,
) -> ::std::result::Result<
(Self, #config::CliLayer),
usage_argv::Error<'static, 'v>,
> {
#defaults
read_argv_into(Self::command(), argv, &mut partial)?;
let __usage_settings = settings_layer(&partial);
check(&mut partial)?;
if let ::std::option::Option::Some(__usage_out) = __usage_warnings {
Self::__usage_deprecations(&partial, __usage_out);
}
let __usage_built = #built;
::std::result::Result::Ok((__usage_built, __usage_settings))
}
}
});
let apply = apply_fn(cli);
let post = post_binding(cli);
let (completion, completion_intercept) = completion_fns(cli);
let embedded_spec_request = cli.spec_endpoint.then(|| {
quote! {
if let ::std::option::Option::Some(__usage_answer) =
Self::spec_request(&__usage_refs)
{
return usage_argv::embedded::Outcome::Exit(usage_argv::embedded::Exit {
text: __usage_answer,
stderr: false,
code: 0,
});
}
}
});
let embedded_completion_request = cli.completion.then(|| {
quote! {
if let ::std::option::Option::Some(__usage_answer) = Self::completion_request(argv) {
return usage_argv::embedded::Outcome::Exit(usage_argv::embedded::Exit {
text: __usage_answer,
stderr: false,
code: 0,
});
}
}
});
let spec_extra = spec_extra_append(cli);
let field_finals: Vec<_> = cli
.fields
.iter()
.filter(|f| !matches!(f.kind, Kind::Subcommand { .. }))
.map(|field| field_final(field, None))
.collect();
let built = validated_value(cli, &built_value(cli, &sub_build, &field_finals));
let built_for_view = if cli.views.is_empty() {
built.clone()
} else {
let default_view_omitter = quote!(usage_argv::spec::DefaultViewOmitter);
let view_field_finals: Vec<_> = cli
.fields
.iter()
.filter(|f| !matches!(f.kind, Kind::Subcommand { .. }))
.map(|field| field_final(field, Some(&default_view_omitter)))
.collect();
let view_sub_build = if cli
.views
.iter()
.all(|view| view.root.split_ascii_whitespace().count() == 1)
{
&sub_build
} else {
&sub_default_view_build
};
validated_value(cli, &built_value(cli, view_sub_build, &view_field_finals))
};
let update_clone_bound = cli
.validate_with
.as_ref()
.map(|_| quote!(where Self: ::std::clone::Clone));
let update_merge = if let Some(validate_with) = &cli.validate_with {
quote! {
let mut __usage_candidate = self.clone();
merge(partial, &mut __usage_candidate)?;
#validate_with(&__usage_candidate)
.map_err(usage_argv::ValidationError::into_parse_error)?;
*self = __usage_candidate;
::std::result::Result::Ok(())
}
} else {
quote!(merge(partial, self))
};
let parse_into = cli.try_into.as_ref().map(|target| {
quote! {
pub fn parse_into_from<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<#target, usage_argv::Error<'static, 'v>> {
Self::parse_from(argv).and_then(Self::__usage_finalize)
}
pub fn parse_into_from_with_warnings<'v>(
argv: &[&'v ::std::ffi::OsStr],
warnings: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) -> ::std::result::Result<#target, usage_argv::Error<'static, 'v>> {
Self::parse_from_with_warnings(argv, warnings).and_then(Self::__usage_finalize)
}
pub fn parse_into_from_argv<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<#target, usage_argv::Error<'static, 'v>> {
Self::parse_from_argv(argv).and_then(Self::__usage_finalize)
}
pub fn try_parse_into_from<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<#target, usage_argv::Error<'static, 'v>> {
Self::try_parse_from(argv).and_then(Self::__usage_finalize)
}
#[doc(hidden)]
fn __usage_finalize<'v>(
parsed: Self,
) -> ::std::result::Result<#target, usage_argv::Error<'static, 'v>> {
<#target as ::std::convert::TryFrom<Self>>::try_from(parsed)
.map_err(usage_argv::ValidationError::into_parse_error)
}
pub fn parse_into() -> #target {
#completion_intercept
#parse_preamble
let mut __usage_warnings = ::std::vec::Vec::new();
let __usage_result = Self::__usage_parse_from_argv(
&__usage_all_refs,
::std::option::Option::Some(&mut __usage_warnings),
).and_then(Self::__usage_finalize);
match __usage_result {
::std::result::Result::Ok(finalized) => {
if !__usage_warnings.is_empty() {
::std::eprint!(
"{}",
usage_argv::render_warnings(&__usage_warnings),
);
}
finalized
}
::std::result::Result::Err(error) => {
Self::__usage_exit_on_error(
error,
&__usage_all_refs,
&__usage_argv,
__usage_selected_view,
)
}
}
}
}
});
let settings_parse_entry = settings_bindings.as_ref().map(|_| {
quote! {
pub fn parse_from_argv_with_settings<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<
(Self, #config::CliLayer),
usage_argv::Error<'static, 'v>,
> {
Self::__usage_parse_from_argv_with_settings(
argv,
::std::option::Option::None,
)
}
pub fn parse_from_argv_with_settings_and_warnings<'v>(
argv: &[&'v ::std::ffi::OsStr],
warnings: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) -> ::std::result::Result<
(Self, #config::CliLayer),
usage_argv::Error<'static, 'v>,
> {
Self::__usage_parse_from_argv_with_settings(
argv,
::std::option::Option::Some(warnings),
)
}
#[doc(hidden)]
fn __usage_parse_from_argv_with_settings<'v>(
argv: &[&'v ::std::ffi::OsStr],
__usage_warnings: ::std::option::Option<
&mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
>,
) -> ::std::result::Result<
(Self, #config::CliLayer),
usage_argv::Error<'static, 'v>,
> {
let ::std::option::Option::Some((__usage_argv0, __usage_words)) =
argv.split_first()
else {
return Self::__usage_parse_from_with_settings(&[], __usage_warnings);
};
if let ::std::option::Option::Some(__usage_view) =
usage_argv::spec::view_for_program(&SPEC, __usage_argv0)
{
let mut __usage_rewritten = ::std::vec::Vec::with_capacity(
__usage_words.len()
+ __usage_view.root.split_ascii_whitespace().count(),
);
__usage_rewritten.extend(
__usage_view.root
.split_ascii_whitespace()
.map(::std::ffi::OsStr::new),
);
__usage_rewritten.extend_from_slice(__usage_words);
#defaults
read_argv_into_view(
Self::command(),
&__usage_rewritten,
&mut partial,
::std::option::Option::Some(__usage_view),
)?;
let __usage_settings = settings_layer(&partial);
check_with_view(
&mut partial,
::std::option::Option::Some(__usage_view),
)?;
if let ::std::option::Option::Some(__usage_out) = __usage_warnings {
Self::__usage_deprecations(&partial, __usage_out);
}
return ::std::result::Result::Ok((#built_for_view, __usage_settings));
}
if SPEC.multicall {
if let ::std::option::Option::Some(__usage_word) =
__usage_argv0.to_str().and_then(|s| {
usage_argv::multicall_applet(s, SPEC.name, SPEC.bin)
})
{
let mut __usage_rewritten =
::std::vec::Vec::with_capacity(argv.len());
__usage_rewritten.push(::std::ffi::OsStr::new(__usage_word));
__usage_rewritten.extend_from_slice(__usage_words);
return Self::__usage_parse_from_with_settings(
&__usage_rewritten,
__usage_warnings,
);
}
}
Self::__usage_parse_from_with_settings(__usage_words, __usage_warnings)
}
pub fn parse_with_settings() -> (Self, #config::CliLayer) {
#completion_intercept
#parse_preamble
let mut __usage_warnings = ::std::vec::Vec::new();
match Self::__usage_parse_from_argv_with_settings(
&__usage_all_refs,
::std::option::Option::Some(&mut __usage_warnings),
) {
::std::result::Result::Ok(__usage_parsed) => {
if !__usage_warnings.is_empty() {
::std::eprint!(
"{}",
usage_argv::render_warnings(&__usage_warnings),
);
}
__usage_parsed
}
::std::result::Result::Err(e) => Self::__usage_exit_on_error(
e,
&__usage_all_refs,
&__usage_argv,
__usage_selected_view,
),
}
}
}
});
let min_usage_version = option_str(cli.min_usage_version.as_deref());
let views: Vec<_> = cli
.views
.iter()
.map(|view| {
let id = &view.id;
let name = &view.name;
let bin = &view.bin;
let root = &view.root;
let all_globals = view.all_globals;
let globals = &view.globals;
quote! {
usage_argv::spec::ViewMeta {
id: #id,
name: #name,
bin: #bin,
root: #root,
all_globals: #all_globals,
globals: &[#(#globals),*],
}
}
})
.collect();
let has_version = cli.version.is_some() || cli.long_version.is_some();
let runtime_version = cli
.runtime_version
.as_ref()
.or(cli.version.as_ref())
.or(cli.runtime_long_version.as_ref())
.or(cli.long_version.as_ref())
.cloned()
.unwrap_or_else(|| quote!(""));
let long_version = cli
.long_version
.as_ref()
.map(|tokens| quote!(::core::option::Option::Some(#tokens)))
.unwrap_or_else(|| quote!(::core::option::Option::None));
let runtime_long_version = cli
.runtime_long_version
.as_ref()
.or(cli.long_version.as_ref())
.cloned();
let output_long_version = runtime_long_version
.clone()
.unwrap_or_else(|| runtime_version.clone());
let runtime_name_decl = cli.runtime_name.as_ref().map(|runtime_name| {
quote! {
let __usage_runtime_name: &'static str = #runtime_name;
}
});
let runtime_bin_decl = cli.runtime_bin.as_ref().map(|runtime_bin| {
quote! {
let __usage_runtime_bin: &'static str = #runtime_bin;
}
});
let runtime_version_decl = cli.runtime_version.as_ref().map(|_| {
quote! {
let __usage_runtime_version =
::std::string::ToString::to_string(&(#runtime_version));
}
});
let runtime_long_version_decl = runtime_long_version.as_ref().map(|value| {
quote! {
let __usage_runtime_long_version =
::std::string::ToString::to_string(&(#value));
}
});
let effective_name = if cli.runtime_name.is_some() {
quote!(__usage_runtime_name)
} else {
quote!(Self::spec().name)
};
let effective_bin = if cli.runtime_bin.is_some() {
quote!(::std::option::Option::Some(__usage_runtime_bin))
} else {
quote!(Self::spec().bin)
};
let effective_version = if cli.runtime_version.is_some() {
quote!(::std::option::Option::Some(
__usage_runtime_version.as_str()
))
} else {
quote!(Self::spec().version)
};
let has_runtime_identity = cli.runtime_name.is_some() || cli.runtime_bin.is_some();
let effective_long_version = if runtime_long_version.is_some() {
quote!(::std::option::Option::Some(
__usage_runtime_long_version.as_str()
))
} else {
quote!(Self::spec().long_version)
};
let effective_spec = if has_runtime_identity
|| cli.runtime_version.is_some()
|| runtime_long_version.is_some()
{
quote! {
#runtime_name_decl
#runtime_bin_decl
#runtime_version_decl
#runtime_long_version_decl
let __usage_runtime_spec = usage_argv::spec::Spec {
name: #effective_name,
bin: #effective_bin,
version: #effective_version,
long_version: #effective_long_version,
..*Self::spec()
};
let __usage_spec = &__usage_runtime_spec;
}
} else {
quote! {
let __usage_spec = Self::spec();
}
};
let embedded_outcome_into = cli.try_into.as_ref().map(|target| {
quote! {
pub fn embedded_outcome_into(
argv: &[::std::ffi::OsString],
) -> usage_argv::embedded::Outcome<#target> {
let __usage_refs: ::std::vec::Vec<&::std::ffi::OsStr> =
argv.iter().map(|arg| arg.as_os_str()).collect();
#embedded_spec_request
#embedded_completion_request
#effective_spec
usage_argv::embedded::outcome(
__usage_spec,
Self::command(),
&__usage_refs,
Self::parse_into_from,
)
}
}
});
let page_of = |style: TokenStream| {
quote! {
let __usage_page = match __usage_selected_view {
::std::option::Option::Some(view) => usage_argv::help::page_view(
__usage_spec,
Self::command(),
&__usage_all_refs,
cmd,
view,
__usage_want,
#style,
),
::std::option::Option::None => usage_argv::help::page(
__usage_spec,
Self::command(),
&__usage_argv,
cmd,
__usage_want,
#style,
),
};
}
};
let render_page = page_of(quote!(usage_argv::help::Style::auto()));
let render_page_stderr = page_of(quote!(usage_argv::help::Style::auto_stderr()));
let runtime_program = cli
.runtime_bin
.as_ref()
.or(cli.runtime_name.as_ref())
.cloned();
let runtime_program_for_version = runtime_program
.as_ref()
.map(|program| {
quote! {
let __usage_bin: &'static str = #program;
}
})
.unwrap_or_else(|| {
quote! {
let __usage_bin = Self::spec().bin.unwrap_or(Self::spec().name);
}
});
let runtime_name_view = cli.runtime_name.as_ref().map(|name| quote!(.name(#name)));
let runtime_bin_view = cli.runtime_bin.as_ref().map(|bin| quote!(.bin(#bin)));
let runtime_app = has_runtime_identity.then(|| {
quote! {
pub fn runtime_app() -> usage_argv::spec::SpecView<'static> {
Self::app() #runtime_name_view #runtime_bin_view
}
}
});
quote! {
#[doc(hidden)]
#[allow(
non_upper_case_globals,
non_snake_case,
unused_imports,
// Fires when a metadata struct happens to be fully specified. `..EMPTY`
// is kept on purpose: it is what lets usage-argv gain a metadata field
// without breaking every crate that derives.
clippy::needless_update
)]
const _: () = {
use #runtime as usage_argv;
#validation_import
#flatten_checks
#keys
#(#flag_tables)*
#(#arg_tables)*
#table_decls
pub static ROOT: usage_argv::Command = usage_argv::Command {
version: #has_version,
unknown_flags: #unknown_flags,
arg_required_else_help: #arg_required_else_help,
subcommand_negates_reqs: #subcommand_negates_reqs,
args_conflicts_with_subcommands: #args_conflicts_with_subcommands,
subcommand_precedence_over_arg: #subcommand_precedence_over_arg,
allow_missing_positional: #allow_missing_positional,
disable_help_flag: #disable_help_flag,
disable_help_subcommand: #disable_help_subcommand,
disable_version_flag: #disable_version_flag,
dont_delimit_trailing_values: #dont_delimit_trailing_values,
name: #name,
key: #root_key,
flags: #flag_table_ref,
args: #arg_table_ref,
clause: #clause_table,
#sub_commands
#sub_default
#sub_external
..usage_argv::Command::EMPTY
};
const _: () = assert!(
!#multicall || !ROOT.subcommands.is_empty(),
"`multicall` needs at least one named subcommand to select",
);
#implicit_clause_assertions
const _: () = usage_argv::assert_clause_flag_spellings(&ROOT);
#(#flag_metas)*
#(#arg_metas)*
#meta_table_decls
#group_meta_decl
#(#output_schema_decls)*
pub static ROOT_META: usage_argv::spec::CommandMeta = usage_argv::spec::CommandMeta {
cmd: &ROOT,
outputs: #outputs,
select: #select,
exit_codes: #exit_codes,
about: #about,
long_about: #long_about,
deprecated: #deprecated,
deprecated_warn_at: #deprecated_warn_at,
deprecated_remove_at: #deprecated_remove_at,
surface: #surface,
available_if: &[#(#available_if),*],
restart_token: #restart_token,
subcommand_required: #subcommand_required,
subcommand_help_heading: #subcommand_help_heading,
subcommand_value_name: #subcommand_value_name,
next_line_help: #next_line_help,
flatten_help: #flatten_help,
term_width: #term_width,
max_term_width: #max_term_width,
args_override_self: #args_override_self,
mount: #mount,
before_help: #before_help,
before_long_help: #before_long_help,
after_help: #after_help,
after_long_help: #after_long_help,
examples: #examples,
headings: #headings,
flags: #flag_meta_table_ref,
args: #arg_meta_table_ref,
clause: #clause_meta,
groups: #group_meta_table_ref,
flatten_groups: #flatten_group_table_ref,
#sub_metas
..usage_argv::spec::CommandMeta::EMPTY
};
pub fn __usage_text(value: &[u8]) -> ::std::vec::Vec<u8> {
value.to_vec()
}
pub fn __usage_value_text(
value: ::std::option::Option<&[u8]>,
) -> ::std::vec::Vec<u8> {
value.map(__usage_text).unwrap_or_default()
}
#partial
#apply
#argument_lookup
#deprecations
fn check_with_view_standing<'t, 'v>(
partial: &mut Partial,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
__usage_standing: ::std::option::Option<&#ident>,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
partial.__usage_view = __usage_view;
let _ = (&partial, __usage_standing.is_some());
let args_override_self = #args_override_self;
#post
::std::result::Result::Ok(())
}
fn check_with_view<'t, 'v>(
partial: &mut Partial,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_view_standing(partial, __usage_view, ::std::option::Option::None)
}
pub fn check<'t, 'v>(
partial: &mut Partial,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_view(partial, ::std::option::Option::None)
}
pub fn check_update<'t, 'v>(
partial: &mut Partial,
__usage_standing: &#ident,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_view_standing(
partial,
::std::option::Option::None,
::std::option::Option::Some(__usage_standing),
)
}
#merge
pub fn read_argv<'v>(
command: &'static usage_argv::Command<'static>,
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<Partial, usage_argv::Error<'static, 'v>> {
#defaults
read_argv_into(command, argv, &mut partial)?;
::std::result::Result::Ok(partial)
}
pub fn read_argv_into<'v>(
command: &'static usage_argv::Command<'static>,
argv: &[&'v ::std::ffi::OsStr],
partial: &mut Partial,
) -> ::std::result::Result<(), usage_argv::Error<'static, 'v>> {
read_argv_into_view(command, argv, partial, ::std::option::Option::None)
}
pub fn read_argv_into_view<'v>(
command: &'static usage_argv::Command<'static>,
argv: &[&'v ::std::ffi::OsStr],
partial: &mut Partial,
view: ::std::option::Option<&'static usage_argv::spec::ViewMeta<'static>>,
) -> ::std::result::Result<(), usage_argv::Error<'static, 'v>> {
let mut __usage_parser = usage_argv::Parser::new(command, argv);
if let ::std::option::Option::Some(view) = view {
__usage_parser = __usage_parser.with_view(view);
}
while let ::std::option::Option::Some(__usage_event) =
__usage_parser.next_event()
{
let __usage_event = __usage_event?;
if let usage_argv::Event::Flag { flag, .. } = &__usage_event {
if usage_argv::is_help_flag(flag) {
if flag.action == usage_argv::ArgAction::HelpAll {
return ::std::result::Result::Err(usage_argv::Error::HelpAll {
cmd: __usage_parser.command(),
});
}
let long = match flag.action {
usage_argv::ArgAction::HelpShort => false,
usage_argv::ArgAction::HelpLong => true,
usage_argv::ArgAction::Help => !flag.longs.is_empty(),
_ => false,
};
return ::std::result::Result::Err(usage_argv::Error::Help {
cmd: __usage_parser.command(),
long,
});
}
if usage_argv::is_version_flag(flag) {
return ::std::result::Result::Err(
usage_argv::Error::Version {
long: !flag.longs.is_empty(),
},
);
}
}
apply(partial, &__usage_event);
}
if __usage_parser.command().arg_required_else_help
&& __usage_parser.command_start() == argv.len()
{
return ::std::result::Result::Err(usage_argv::Error::MissingArgsHelp {
cmd: __usage_parser.command(),
});
}
::std::result::Result::Ok(())
}
pub fn read<'v>(
command: &'static usage_argv::Command<'static>,
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<Partial, usage_argv::Error<'static, 'v>> {
#defaults
read_into(command, argv, &mut partial)?;
::std::result::Result::Ok(partial)
}
pub fn read_into<'v>(
command: &'static usage_argv::Command<'static>,
argv: &[&'v ::std::ffi::OsStr],
partial: &mut Partial,
) -> ::std::result::Result<(), usage_argv::Error<'static, 'v>> {
read_argv_into(command, argv, partial)?;
check(partial)?;
::std::result::Result::Ok(())
}
#settings_given
#settings_bindings
#settings_layer
#settings_guard
pub static SPEC: usage_argv::spec::Spec = usage_argv::spec::Spec {
name: #name,
bin: #bin,
version: #version,
long_version: #long_version,
author: #author,
license: #license,
repository: #repository,
source_code_link_template: #source_code_link_template,
min_usage_version: #min_usage_version,
about: #about,
long_about: #long_about,
usage: #usage,
help_template: #help_template,
default_subcommand: #default_subcommand,
multicall: #multicall,
views: &[#(#views),*],
root: &ROOT_META,
};
impl #ident {
pub fn command() -> &'static usage_argv::Command<'static> {
&ROOT
}
pub fn spec() -> &'static usage_argv::spec::Spec<'static> {
&SPEC
}
pub fn app() -> usage_argv::spec::SpecView<'static> {
Self::spec().view()
}
#runtime_app
pub fn render_help(
cmd: &usage_argv::Command<'_>,
long: bool,
) -> ::std::option::Option<::std::string::String> {
#effective_spec
usage_argv::help::render(__usage_spec, cmd, long)
}
pub fn render_failure<'v>(
argv: &[&'v ::std::ffi::OsStr],
error: &usage_argv::Error<'static, 'v>,
) -> ::std::string::String {
#effective_spec
usage_argv::render_failure(__usage_spec, argv, error)
}
pub fn to_kdl() -> ::std::string::String {
#[allow(unused_mut)]
let mut __usage_kdl = SPEC.to_kdl();
#config_extra
#spec_extra
__usage_kdl
}
#spec_endpoint
pub fn embedded_outcome(
argv: &[::std::ffi::OsString],
) -> usage_argv::embedded::Outcome<Self> {
let __usage_refs: ::std::vec::Vec<&::std::ffi::OsStr> =
argv.iter().map(|arg| arg.as_os_str()).collect();
#embedded_spec_request
#embedded_completion_request
#effective_spec
usage_argv::embedded::outcome(
__usage_spec,
Self::command(),
&__usage_refs,
Self::parse_from,
)
}
#embedded_outcome_into
#settings_binding_forward
#settings_parse
#parse_into
pub fn parse_from<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
Self::__usage_parse_from(argv, ::std::option::Option::None)
}
pub fn parse_from_with_warnings<'v>(
argv: &[&'v ::std::ffi::OsStr],
warnings: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
Self::__usage_parse_from(argv, ::std::option::Option::Some(warnings))
}
#[doc(hidden)]
fn __usage_parse_from<'v>(
argv: &[&'v ::std::ffi::OsStr],
__usage_warnings: ::std::option::Option<
&mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
>,
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
#defaults
read_into(Self::command(), argv, &mut partial)?;
if let ::std::option::Option::Some(__usage_out) = __usage_warnings {
Self::__usage_deprecations(&partial, __usage_out);
}
::std::result::Result::Ok(#built)
}
#[doc(hidden)]
fn __usage_deprecations(
partial: &Partial,
out: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) {
let mut __usage_found = ::std::vec::Vec::new();
deprecations(partial, &mut __usage_found);
if !__usage_found.is_empty() {
#effective_spec
usage_argv::warn::retain_reached(
&mut __usage_found,
__usage_spec.version,
);
out.append(&mut __usage_found);
}
}
pub fn parse_from_argv<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
Self::__usage_parse_from_argv(argv, ::std::option::Option::None)
}
pub fn parse_from_argv_with_warnings<'v>(
argv: &[&'v ::std::ffi::OsStr],
warnings: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
Self::__usage_parse_from_argv(argv, ::std::option::Option::Some(warnings))
}
#[doc(hidden)]
fn __usage_parse_from_argv<'v>(
argv: &[&'v ::std::ffi::OsStr],
__usage_warnings: ::std::option::Option<
&mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
>,
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
let ::std::option::Option::Some((__usage_argv0, __usage_words)) =
argv.split_first()
else {
return Self::__usage_parse_from(&[], __usage_warnings);
};
if let ::std::option::Option::Some(__usage_view) =
usage_argv::spec::view_for_program(&SPEC, __usage_argv0)
{
let mut __usage_rewritten = ::std::vec::Vec::with_capacity(
__usage_words.len()
+ __usage_view.root.split_ascii_whitespace().count(),
);
__usage_rewritten.extend(
__usage_view.root
.split_ascii_whitespace()
.map(::std::ffi::OsStr::new),
);
__usage_rewritten.extend_from_slice(__usage_words);
#defaults
read_argv_into_view(
Self::command(),
&__usage_rewritten,
&mut partial,
::std::option::Option::Some(__usage_view),
)?;
check_with_view(
&mut partial,
::std::option::Option::Some(__usage_view),
)?;
if let ::std::option::Option::Some(__usage_out) = __usage_warnings {
Self::__usage_deprecations(&partial, __usage_out);
}
return ::std::result::Result::Ok(#built_for_view);
}
if SPEC.multicall {
if let ::std::option::Option::Some(__usage_word) =
__usage_argv0.to_str().and_then(|s| {
usage_argv::multicall_applet(s, SPEC.name, SPEC.bin)
})
{
let mut __usage_rewritten =
::std::vec::Vec::with_capacity(argv.len());
__usage_rewritten.push(::std::ffi::OsStr::new(__usage_word));
__usage_rewritten.extend_from_slice(__usage_words);
return Self::__usage_parse_from(
&__usage_rewritten,
__usage_warnings,
);
}
}
Self::__usage_parse_from(__usage_words, __usage_warnings)
}
pub fn try_update_from<'v>(
&mut self,
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<(), usage_argv::Error<'static, 'v>>
#update_clone_bound
{
#defaults
read_argv_into(Self::command(), argv, &mut partial)?;
check_update(&mut partial, self)?;
#update_merge
}
pub fn try_update_from_argv<'v>(
&mut self,
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<(), usage_argv::Error<'static, 'v>>
#update_clone_bound
{
let ::std::option::Option::Some((__usage_argv0, __usage_words)) =
argv.split_first()
else {
return self.try_update_from(&[]);
};
if let ::std::option::Option::Some(__usage_view) =
usage_argv::spec::view_for_program(&SPEC, __usage_argv0)
{
let mut __usage_rewritten = ::std::vec::Vec::with_capacity(
__usage_words.len()
+ __usage_view.root.split_ascii_whitespace().count(),
);
__usage_rewritten.extend(
__usage_view.root
.split_ascii_whitespace()
.map(::std::ffi::OsStr::new),
);
__usage_rewritten.extend_from_slice(__usage_words);
return self.try_update_from(&__usage_rewritten);
}
if SPEC.multicall {
if let ::std::option::Option::Some(__usage_word) =
__usage_argv0.to_str().and_then(|s| {
usage_argv::multicall_applet(s, SPEC.name, SPEC.bin)
})
{
let mut __usage_rewritten =
::std::vec::Vec::with_capacity(argv.len());
__usage_rewritten.push(::std::ffi::OsStr::new(__usage_word));
__usage_rewritten.extend_from_slice(__usage_words);
return self.try_update_from(&__usage_rewritten);
}
}
self.try_update_from(__usage_words)
}
pub fn update_from<'v>(&mut self, argv: &[&'v ::std::ffi::OsStr])
#update_clone_bound
{
if let ::std::result::Result::Err(e) = self.try_update_from(argv) {
Self::__usage_exit_on_error(
e,
argv,
argv,
::std::option::Option::None,
);
}
}
pub fn update_from_argv<'v>(&mut self, argv: &[&'v ::std::ffi::OsStr])
#update_clone_bound
{
if let ::std::result::Result::Err(e) = self.try_update_from_argv(argv) {
let __usage_words =
argv.split_first().map_or(argv, |(_, rest)| rest);
Self::__usage_exit_on_error(
e,
argv,
__usage_words,
::std::option::Option::None,
);
}
}
pub fn try_parse_from<'v>(
argv: &[&'v ::std::ffi::OsStr],
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
Self::__usage_try_parse_from(argv, ::std::option::Option::None)
}
pub fn try_parse_from_with_warnings<'v>(
argv: &[&'v ::std::ffi::OsStr],
warnings: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
Self::__usage_try_parse_from(argv, ::std::option::Option::Some(warnings))
}
#[doc(hidden)]
fn __usage_try_parse_from<'v>(
argv: &[&'v ::std::ffi::OsStr],
__usage_warnings: ::std::option::Option<
&mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
>,
) -> ::std::result::Result<Self, usage_argv::Error<'static, 'v>> {
if #no_binary_name {
Self::__usage_parse_from(argv, __usage_warnings)
} else {
Self::__usage_parse_from_argv(argv, __usage_warnings)
}
}
#completion
pub fn parse() -> Self {
#completion_intercept
#parse_preamble
let mut __usage_warnings = ::std::vec::Vec::new();
match Self::__usage_parse_from_argv(
&__usage_all_refs,
::std::option::Option::Some(&mut __usage_warnings),
) {
::std::result::Result::Ok(parsed) => {
if !__usage_warnings.is_empty() {
::std::eprint!(
"{}",
usage_argv::render_warnings(&__usage_warnings),
);
}
parsed
}
::std::result::Result::Err(e) => Self::__usage_exit_on_error(
e,
&__usage_all_refs,
&__usage_argv,
__usage_selected_view,
),
}
}
#settings_parse_entry
fn __usage_exit_on_error<'v>(
__usage_error: usage_argv::Error<'static, 'v>,
__usage_all_refs: &[&'v ::std::ffi::OsStr],
__usage_argv: &[&'v ::std::ffi::OsStr],
__usage_selected_view: ::std::option::Option<
&usage_argv::spec::ViewMeta<'static>,
>,
) -> ! {
match __usage_error {
usage_argv::Error::Version { long } => {
#runtime_program_for_version
let __usage_bin = __usage_selected_view
.map(|view| view.bin)
.unwrap_or(__usage_bin);
let __usage_version = if long {
#output_long_version
} else {
#runtime_version
};
::std::println!("{__usage_bin} {__usage_version}");
usage_argv::__usage_process_exit(0);
}
usage_argv::Error::Help { cmd, long } => {
#effective_spec
let __usage_want = if long {
usage_argv::help::Page::Long
} else {
usage_argv::help::Page::Short
};
#render_page
match __usage_page {
::std::option::Option::Some(page) => {
::std::print!("{page}");
usage_argv::__usage_process_exit(0);
}
::std::option::Option::None => usage_argv::__usage_process_exit(0),
}
}
usage_argv::Error::MissingArgsHelp { cmd } => {
#effective_spec
let __usage_want = usage_argv::help::Page::Short;
#render_page_stderr
match __usage_page {
::std::option::Option::Some(page) => {
::std::eprint!("{page}");
usage_argv::__usage_process_exit(2);
}
::std::option::Option::None => usage_argv::__usage_process_exit(2),
}
}
usage_argv::Error::HelpAll { cmd } => {
#effective_spec
let __usage_want = usage_argv::help::Page::All;
#render_page
match __usage_page {
::std::option::Option::Some(page) => {
::std::print!("{page}");
usage_argv::__usage_process_exit(0);
}
::std::option::Option::None => usage_argv::__usage_process_exit(0),
}
}
e => {
#effective_spec
let __usage_failure = match __usage_selected_view {
::std::option::Option::Some(view) => {
usage_argv::render_failure_view(
__usage_spec,
&__usage_all_refs,
&e,
view,
)
}
::std::option::Option::None => {
usage_argv::render_failure(
__usage_spec,
&__usage_argv,
&e,
)
}
};
::std::eprint!(
"{}",
__usage_failure
);
usage_argv::__usage_process_exit(2);
}
}
}
}
};
#dispatch
}
}
fn flatten_checks(cli: &Cli) -> TokenStream {
let shape_checks = cli.fields.iter().filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
Some(quote! {
const _: () = ::core::assert!(
<#ty as usage_argv::spec::CommandArgs>::COMMAND.subcommands.is_empty(),
"a flattened group cannot declare subcommands: flatten joins flags and \
arguments into the parent's tables and leaves subcommands behind, so the \
command would require one that no word could select. Declare the \
`subcommand` field on the command's own struct instead."
);
})
});
let command = if cli.composable {
quote!(COMMAND)
} else {
quote!(ROOT)
};
let relationship_checks = cli.fields.iter().flat_map(|field| {
field
.overrides
.iter()
.filter(|selector| cli.field_for_selector(selector).is_none())
.map(|selector| {
quote! {
const _: () = ::core::assert!(
usage_argv::spec::flag_selector_count(&#command, #selector) == 1,
::core::concat!(
"`overrides = ",
#selector,
"` must name exactly one flag after flattened groups are composed",
),
);
}
})
});
quote!(#(#shape_checks)* #(#relationship_checks)*)
}
fn unknown_flags_tokens(cli: &Cli) -> TokenStream {
match cli.unknown_flags.as_deref() {
Some("error") => quote!(::core::option::Option::Some(
usage_argv::UnknownFlags::Error
)),
Some(_) => quote!(::core::option::Option::Some(
usage_argv::UnknownFlags::Value
)),
None => quote!(::core::option::Option::None),
}
}
fn spec_extra_append(cli: &Cli) -> TokenStream {
let Some(path) = cli.spec_extra.as_deref() else {
return TokenStream::new();
};
quote! {
{
const __USAGE_SPEC_EXTRA: &str = ::core::include_str!(::core::concat!(
::core::env!("CARGO_MANIFEST_DIR"),
"/",
#path,
));
let __usage_extra = __USAGE_SPEC_EXTRA.trim();
if !__usage_extra.is_empty() {
__usage_kdl = ::std::format!(
"{}\n{}\n",
__usage_kdl.trim_end(),
__usage_extra,
);
}
}
}
}
fn spec_endpoint_fns(cli: &Cli) -> (TokenStream, TokenStream) {
if !cli.spec_endpoint {
return (TokenStream::new(), TokenStream::new());
}
let functions = quote! {
pub fn spec_request(
argv: &[&::std::ffi::OsStr],
) -> ::std::option::Option<::std::string::String> {
if usage_argv::is_spec_request(Self::command(), argv) {
::std::option::Option::Some(Self::to_kdl())
} else {
::std::option::Option::None
}
}
};
let intercept = quote! {
if let ::std::option::Option::Some(__usage_answer) =
Self::spec_request(__usage_all_refs.get(1..).unwrap_or(&[]))
{
::std::print!("{__usage_answer}");
usage_argv::__usage_process_exit(0);
}
};
(functions, intercept)
}
fn completion_fns(cli: &Cli) -> (TokenStream, TokenStream) {
if !cli.completion {
return (TokenStream::new(), TokenStream::new());
}
let completion_program = cli
.runtime_bin
.as_ref()
.or(cli.runtime_name.as_ref())
.map(|program| {
quote! {
let __usage_runtime_program: &'static str = #program;
__usage_runtime_program.to_string()
}
})
.unwrap_or_else(|| {
quote! {
let spec = Self::spec();
spec.bin.unwrap_or(spec.name).to_string()
}
});
let functions = quote! {
usage_argv::__usage_needs_complete_feature!();
pub fn completion_script(
shell: usage_argv::complete::Shell,
) -> ::std::string::String {
let __usage_program = { #completion_program };
usage_argv::script::script(&__usage_program, shell)
}
pub fn completion_script_for_alias(
alias: &str,
shell: usage_argv::complete::Shell,
) -> ::std::string::String {
let __usage_program = { #completion_program };
usage_argv::script::script_for(&__usage_program, alias, shell)
}
pub fn completion_install_plan(
shell: usage_argv::complete::Shell,
env: &usage_argv::install::Env,
) -> ::std::result::Result<
usage_argv::install::Plan,
usage_argv::install::Error,
> {
let __usage_program = { #completion_program };
usage_argv::install::plan(&__usage_program, shell, env)
}
pub fn completion_install_plan_for_alias(
alias: &str,
shell: usage_argv::complete::Shell,
env: &usage_argv::install::Env,
) -> ::std::result::Result<
usage_argv::install::Plan,
usage_argv::install::Error,
> {
let __usage_program = { #completion_program };
usage_argv::install::plan_for(&__usage_program, alias, shell, env)
}
pub fn install_completion(
shell: usage_argv::complete::Shell,
env: &usage_argv::install::Env,
on_foreign: usage_argv::install::OnForeign,
) -> ::std::result::Result<
usage_argv::install::Installed,
usage_argv::install::Error,
> {
let __usage_program = { #completion_program };
usage_argv::install::install(&__usage_program, shell, env, on_foreign)
}
pub fn install_completion_for_alias(
alias: &str,
shell: usage_argv::complete::Shell,
env: &usage_argv::install::Env,
on_foreign: usage_argv::install::OnForeign,
) -> ::std::result::Result<
usage_argv::install::Installed,
usage_argv::install::Error,
> {
let __usage_program = { #completion_program };
usage_argv::install::install_for(&__usage_program, alias, shell, env, on_foreign)
}
pub fn completion_script_for(
view: &str,
shell: usage_argv::complete::Shell,
) -> ::std::option::Option<::std::string::String> {
Self::spec()
.views
.iter()
.find(|declared| declared.id == view)
.map(|declared| usage_argv::script::script(declared.bin, shell))
}
pub fn completion_request(
argv: &[::std::ffi::OsString],
) -> ::std::option::Option<::std::string::String> {
let first = argv.first()?.to_str()?;
if first != "__complete_word__" {
return ::std::option::Option::None;
}
let mut shell = usage_argv::complete::Shell::Bash;
let mut line = ::std::string::String::new();
let mut cursor = ::std::option::Option::None;
let mut candidates_for: ::std::option::Option<::std::string::String> =
::std::option::Option::None;
let mut words: ::std::option::Option<::std::vec::Vec<::std::string::String>> =
::std::option::Option::None;
let mut rest = argv[1..].iter();
while let ::std::option::Option::Some(arg) = rest.next() {
match arg.to_str().unwrap_or_default() {
"--shell" => {
if let ::std::option::Option::Some(name) = rest.next() {
if let ::std::option::Option::Some(found) =
usage_argv::complete::Shell::from_name(
&name.to_string_lossy(),
)
{
shell = found;
}
}
}
"--line" => {
if let ::std::option::Option::Some(value) = rest.next() {
line = value.to_string_lossy().into_owned();
}
}
"--cursor" => {
cursor = rest
.next()
.and_then(|value| value.to_str().and_then(|v| v.parse().ok()));
}
"--candidates" => {
candidates_for = rest.next().map(|v| v.to_string_lossy().into_owned());
}
"--words" => {
words = ::std::option::Option::Some(
rest.map(|word| word.to_string_lossy().into_owned()).collect(),
);
break;
}
_ => {}
}
}
let mut split = match words {
::std::option::Option::Some(mut words) => {
if words.is_empty() {
words.push(::std::string::String::new());
}
let cword = words.len() - 1;
let prefix = words[cword].clone();
usage_argv::complete::Split { words, cword, prefix }
}
::std::option::Option::None => {
let cursor = cursor.unwrap_or(line.len());
usage_argv::complete::split(&line, cursor, shell)
}
};
let __usage_selected_view = split.words.first().and_then(|__usage_program| {
usage_argv::spec::view_for_program(
Self::spec(),
::std::ffi::OsStr::new(__usage_program),
)
});
if let ::std::option::Option::Some(name) = candidates_for {
let position = match __usage_selected_view {
::std::option::Option::Some(view) =>
usage_argv::complete::walk_view(
Self::spec().root.cmd,
split.argv(),
view,
),
::std::option::Option::None =>
usage_argv::complete::walk(Self::spec().root.cmd, split.argv()),
};
let __usage_words = split.argv();
let __usage_path: ::std::vec::Vec<(
&usage_argv::Command<'_>,
&[::std::string::String],
)> = position
.path
.iter()
.map(|(cmd, start)| (*cmd, __usage_words.get(*start..).unwrap_or(&[])))
.collect();
let ctx = usage_argv::complete::CompleteCtx {
words: &split.words,
cword: split.cword,
prefix: &split.prefix,
command_words: __usage_words
.get(position.command_start..)
.unwrap_or(&[]),
command_path: &__usage_path,
};
let found = match __usage_selected_view {
::std::option::Option::Some(view) =>
usage_argv::complete::for_name_view(Self::spec(), &name, &ctx, view)
.unwrap_or_default(),
::std::option::Option::None =>
usage_argv::complete::for_name(Self::spec(), &name, &ctx)
.unwrap_or_default(),
};
let answer = usage_argv::complete::Completions {
candidates: found,
files: ::std::option::Option::None,
};
return ::std::option::Option::Some(usage_argv::complete::render(&answer, shell));
}
let answer = match __usage_selected_view {
::std::option::Option::Some(view) =>
usage_argv::complete::complete_view(Self::spec(), &split, view),
::std::option::Option::None =>
usage_argv::complete::complete(Self::spec(), &split),
};
::std::option::Option::Some(usage_argv::complete::render(&answer, shell))
}
};
let intercept = quote! {
{
let __usage_args: ::std::vec::Vec<::std::ffi::OsString> =
::std::env::args_os().skip(1).collect();
if let ::std::option::Option::Some(answer) = Self::completion_request(&__usage_args) {
::std::print!("{answer}");
usage_argv::__usage_process_exit(0);
}
}
};
(functions, intercept)
}
fn flag_table(i: usize, field: &Field) -> TokenStream {
let name = format_ident!("FLAG_{i}");
let binding_type_name = format_ident!("FLAG_{i}_BINDING_TYPE");
let key = key_ident("FLAG", Some(i));
let field_name = &field.name;
let binding_key = binding_hash(field);
let (binding_type_fn, binding_type) = match field.value_ty.as_ref() {
Some(ty) => (
quote! {
fn #binding_type_name() -> &'static str {
::core::any::type_name::<#ty>()
}
},
quote!(::core::option::Option::Some(usage_argv::BindingType(#binding_type_name))),
),
None => (quote!(), quote!(::core::option::Option::None)),
};
let Kind::Flag {
longs,
hidden_longs: _,
shorts,
negate,
global,
variadic,
} = &field.kind
else {
unreachable!("filtered by the caller");
};
let shorts: Vec<u8> = shorts.iter().map(|c| *c as u8).collect();
let negate = option_str(negate.as_deref());
let takes_value = field.takes_value();
let var_max = match field.value_var_max.filter(|_| *variadic) {
Some(max) => {
let max = u32::try_from(max).unwrap_or(u32::MAX);
quote!(::std::option::Option::Some(#max))
}
None => quote!(::std::option::Option::None),
};
let table_delimiter = table_delimiter(field);
let allow_hyphen_values = field.allow_hyphen_values;
let allow_negative_numbers = field.allow_negative_numbers;
let value_terminator = match field.value_terminator.as_deref() {
Some(value) => quote!(::core::option::Option::Some(#value.as_bytes())),
None => quote!(::core::option::Option::None),
};
let require_equals = field.require_equals;
let bool_value = field.bool_value;
let value_optional = field.optional_value_type || field.default_missing.is_some();
let default_missing = match field.default_missing.as_deref() {
Some(value) => quote!(::core::option::Option::Some(#value.as_bytes())),
None => quote!(::core::option::Option::None),
};
let action = match field.action {
crate::model::ArgAction::Set => quote!(usage_argv::ArgAction::Set),
crate::model::ArgAction::Help => quote!(usage_argv::ArgAction::Help),
crate::model::ArgAction::HelpShort => quote!(usage_argv::ArgAction::HelpShort),
crate::model::ArgAction::HelpLong => quote!(usage_argv::ArgAction::HelpLong),
crate::model::ArgAction::HelpAll => quote!(usage_argv::ArgAction::HelpAll),
crate::model::ArgAction::Version => quote!(usage_argv::ArgAction::Version),
};
quote! {
#binding_type_fn
pub static #name: usage_argv::Flag = usage_argv::Flag {
key: #key,
binding_key: #binding_key,
binding_type: #binding_type,
name: #field_name,
longs: &[#(#longs),*],
shorts: &[#(#shorts),*],
negate: #negate,
takes_value: #takes_value,
variadic: #variadic,
var_max: #var_max,
delimiter: #table_delimiter,
allow_hyphen_values: #allow_hyphen_values,
allow_negative_numbers: #allow_negative_numbers,
value_terminator: #value_terminator,
require_equals: #require_equals,
value_optional: #value_optional,
bool_value: #bool_value,
default_missing: #default_missing,
global: #global,
action: #action,
};
}
}
fn binding_hash(field: &Field) -> u64 {
let shape = match field.shape {
Shape::Bool => "bool",
Shape::Count => "count",
Shape::Optional => "optional",
Shape::Required => "required",
Shape::Many => "many",
};
let mut hash = 0xcbf2_9ce4_8422_2325u64;
hash_binding_part(&mut hash, b"shape", Some(shape.as_bytes()));
hash_binding_part(
&mut hash,
b"bool-value",
Some(&[u8::from(field.bool_value)]),
);
hash_binding_part(
&mut hash,
b"optional-value",
Some(&[u8::from(field.optional_value_type)]),
);
let variadic = matches!(field.kind, Kind::Flag { variadic: true, .. });
hash_binding_part(&mut hash, b"variadic", Some(&[u8::from(variadic)]));
hash_binding_part(
&mut hash,
b"repeatable",
Some(&[u8::from(field.repeatable)]),
);
hash_binding_usize(&mut hash, b"var-min", field.var_min);
hash_binding_usize(&mut hash, b"var-max", field.var_max);
hash_binding_usize(&mut hash, b"value-var-min", field.value_var_min);
hash_binding_usize(&mut hash, b"value-var-max", field.value_var_max);
hash_binding_part(
&mut hash,
b"value-enum",
Some(&[u8::from(field.value_enum)]),
);
hash_binding_part(
&mut hash,
b"open-choices",
Some(&[u8::from(field.allow_unknown_choices)]),
);
for choice in &field.choices {
hash_binding_part(&mut hash, b"choice", Some(choice.as_bytes()));
}
hash_binding_part(&mut hash, b"choices-end", None);
hash_binding_part(
&mut hash,
b"validate",
field.validate.as_deref().map(str::as_bytes),
);
hash_binding_part(
&mut hash,
b"validate-error",
field.validate_error.as_deref().map(str::as_bytes),
);
let delimiter = field.delimiter.map(|value| value.to_string());
hash_binding_part(
&mut hash,
b"delimiter",
delimiter.as_deref().map(str::as_bytes),
);
hash
}
fn hash_binding_usize(hash: &mut u64, label: &[u8], value: Option<usize>) {
let value = value.map(|value| (value as u64).to_le_bytes());
hash_binding_part(hash, label, value.as_ref().map(|value| value.as_slice()));
}
fn hash_binding_part(hash: &mut u64, label: &[u8], value: Option<&[u8]>) {
for byte in (label.len() as u64)
.to_le_bytes()
.into_iter()
.chain(label.iter().copied())
{
*hash ^= u64::from(byte);
*hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
let (present, value) = match value {
Some(value) => (1u8, value),
None => (0u8, &[][..]),
};
for byte in [present]
.into_iter()
.chain((value.len() as u64).to_le_bytes())
.chain(value.iter().copied())
{
*hash ^= u64::from(byte);
*hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
}
fn table_delimiter(field: &Field) -> TokenStream {
match field.delimiter.filter(char::is_ascii).map(|d| d as u8) {
Some(byte) => quote!(::std::option::Option::Some(#byte)),
None => quote!(::std::option::Option::None),
}
}
fn arg_table(i: usize, field: &Field) -> TokenStream {
let name = format_ident!("ARG_{i}");
let key = key_ident("ARG", Some(i));
let field_name = &field.name;
let var = field.shape == Shape::Many;
let required =
(field.shape == Shape::Required || field.required_collection) && !field.has_default();
let Kind::Arg { double_dash, sigil } = &field.kind else {
unreachable!("filtered by the caller");
};
let sigil = match sigil.as_deref() {
Some(value) => quote!(::core::option::Option::Some(#value.as_bytes())),
None => quote!(::core::option::Option::None),
};
let double_dash = match double_dash {
DoubleDash::Optional => quote!(usage_argv::DoubleDash::Optional),
DoubleDash::Required => quote!(usage_argv::DoubleDash::Required),
DoubleDash::Preserve => quote!(usage_argv::DoubleDash::Preserve),
DoubleDash::Automatic => quote!(usage_argv::DoubleDash::Automatic),
};
let var_max = match field.var_max.filter(|_| var) {
Some(max) => {
let max = u32::try_from(max).unwrap_or(u32::MAX);
quote!(::std::option::Option::Some(#max))
}
None => quote!(::std::option::Option::None),
};
let table_delimiter = table_delimiter(field);
let allow_negative_numbers = field.allow_negative_numbers;
let value_terminator = match field.value_terminator.as_deref() {
Some(value) => quote!(::core::option::Option::Some(#value.as_bytes())),
None => quote!(::core::option::Option::None),
};
quote! {
pub static #name: usage_argv::Arg = usage_argv::Arg {
key: #key,
sigil: #sigil,
required: #required,
name: #field_name,
var: #var,
var_max: #var_max,
delimiter: #table_delimiter,
allow_negative_numbers: #allow_negative_numbers,
value_terminator: #value_terminator,
double_dash: #double_dash,
};
}
}
fn completer_tokens(
i: usize,
field: &Field,
kind: &str,
owner: &syn::Ident,
) -> (TokenStream, TokenStream) {
let Some(path) = &field.complete else {
return (TokenStream::new(), quote!(::std::option::Option::None));
};
let wrapper = format_ident!("__usage_complete_{kind}_{i}");
let completer_path = path;
let decl = quote! {
fn #wrapper(
ctx: &usage_argv::complete::CompleteCtx<'_>,
) -> ::std::vec::Vec<usage_argv::complete::Candidate<'static>> {
let __usage_declaration =
<#owner as usage_argv::spec::CommandArgs>::COMMAND;
let (__usage_command, __usage_words) = ctx
.command_for(__usage_declaration)
.unwrap_or((__usage_declaration, ctx.command_words));
let __usage_owned: ::std::vec::Vec<::std::ffi::OsString> = __usage_words
.iter()
.map(::std::ffi::OsString::from)
.collect();
let __usage_argv: ::std::vec::Vec<&::std::ffi::OsStr> =
__usage_owned.iter().map(|a| a.as_os_str()).collect();
let mut partial = <#owner as usage_argv::spec::CommandArgs>::start();
let mut parser = usage_argv::Parser::new(
__usage_command,
&__usage_argv,
);
while let ::std::option::Option::Some(event) = parser.next_event() {
match event {
::std::result::Result::Ok(event) => {
let _ = <#owner as usage_argv::spec::CommandArgs>::apply(
&mut partial,
&event,
);
}
::std::result::Result::Err(_) => break,
}
}
#completer_path(&partial, ctx)
}
};
(decl, quote!(::std::option::Option::Some(#wrapper)))
}
fn flag_meta(cli: &Cli, i: usize, field: &Field, owner: &syn::Ident) -> TokenStream {
let name = format_ident!("FLAG_META_{i}");
let table = format_ident!("FLAG_{i}");
let help = option_str(field.help.as_deref());
let long_help = option_str(field.long_help.as_deref());
let admonitions = admonitions(field);
let env = option_str(field.env.as_deref());
let env_fallback = &field.env_fallback;
let deprecated_env = &field.deprecated_env;
let help_heading = option_str(field.help_heading.as_deref());
let surface = option_str(field.surface.as_deref());
let available_if = &field.available_if;
let display_order = option_usize(field.display_order);
let deprecated = option_str(field.deprecated.as_deref());
let deprecated_warn_at = option_str(field.deprecated_warn_at.as_deref());
let deprecated_remove_at = option_str(field.deprecated_remove_at.as_deref());
let value_name = option_str(field.value_name.as_deref());
let value_names = &field.value_names;
let complete_type = option_str(field.complete_type.as_deref());
let defaults = &field.default;
let default = quote!(&[#(#defaults),*]);
let hide = field.hide;
let hide_default_value = field.hide_default_value;
let hide_env = field.hide_env;
let hide_env_values = field.hide_env_values;
let hide_possible_values = field.hide_possible_values;
let hide_short_help = field.hide_short_help;
let hide_long_help = field.hide_long_help;
let count = field.shape == Shape::Count;
let repeatable = field.repeatable;
let hidden_longs = match &field.kind {
Kind::Flag { hidden_longs, .. } => hidden_longs,
_ => unreachable!("flag metadata is generated only for flags"),
};
let required =
(field.shape == Shape::Required || field.required_collection) && !field.has_default();
let value_optional = field.value_optional;
let (choices, accepted_choices, choice_aliases, choice_details, ignore_case) =
choices_tokens(cli, field);
let allow_unknown_choices = field.allow_unknown_choices;
let validate = option_str(field.validate.as_deref());
let validate_error = option_str(field.validate_error.as_deref());
let (var_min, var_max) = bounds_tokens(field);
let (value_var_min, value_var_max) = value_bounds_tokens(field);
let overrides: Vec<String> = field
.overrides
.iter()
.map(|selector| {
cli.field_for_selector(selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| selector.clone())
})
.collect();
let conflicts: Vec<String> = field
.conflicts
.iter()
.map(|selector| {
cli.field_for_selector(selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| selector.clone())
})
.collect();
let canonical = |selector: &String| {
cli.field_for_selector(selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| selector.clone())
};
let requires: Vec<String> = field.requires.iter().map(canonical).collect();
let requires_if = field.requires_if.iter().map(|condition| {
let value = &condition.value;
let requires = canonical(&condition.requires);
quote!(usage_argv::spec::RequiresIf { value: #value, requires: #requires })
});
let default_if = field.default_if.iter().map(|condition| {
let selector = canonical(&condition.selector);
let value = &condition.value;
let when = match &condition.when {
Some(when) => quote!(::core::option::Option::Some(#when)),
None => quote!(::core::option::Option::None),
};
quote!(usage_argv::spec::DefaultIf { selector: #selector, when: #when, value: #value })
});
let exclusive = field.exclusive;
let delimiter = match field.delimiter {
Some(c) => quote!(::std::option::Option::Some(#c)),
None => quote!(::std::option::Option::None),
};
let required_if: Vec<String> = field
.required_if
.iter()
.map(|selector| {
cli.field_for_selector(selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| selector.clone())
})
.collect();
let required_if_eq = field.required_if_eq.iter().map(|condition| {
let selector = cli
.field_for_selector(&condition.selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| condition.selector.clone());
let value = &condition.value;
quote!(usage_argv::spec::RequiredIfEq { selector: #selector, value: #value })
});
let required_if_eq_all = field.required_if_eq_all.iter().map(|condition| {
let selector = cli
.field_for_selector(&condition.selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| condition.selector.clone());
let value = &condition.value;
quote!(usage_argv::spec::RequiredIfEq { selector: #selector, value: #value })
});
let required_unless: Vec<String> = field.required_unless.iter().map(canonical).collect();
let required_unless_all: Vec<String> =
field.required_unless_all.iter().map(canonical).collect();
let (completer_decl, completer) = completer_tokens(i, field, "flag", owner);
let effect = field
.effect
.clone()
.unwrap_or_else(|| quote!(::core::option::Option::None));
quote! {
#completer_decl
pub static #name: usage_argv::spec::FlagMeta = usage_argv::spec::FlagMeta {
effect: #effect,
complete: #completer,
complete_type: #complete_type,
flag: &#table,
display_order: #display_order,
help: #help,
long_help: #long_help,
admonitions: #admonitions,
deprecated: #deprecated,
deprecated_warn_at: #deprecated_warn_at,
deprecated_remove_at: #deprecated_remove_at,
env: #env,
env_fallback: &[#(#env_fallback),*],
deprecated_env: &[#(#deprecated_env),*],
default: #default,
help_heading: #help_heading,
surface: #surface,
available_if: &[#(#available_if),*],
value_name: #value_name,
value_names: &[#(#value_names),*],
hide: #hide,
hide_default_value: #hide_default_value,
hide_env: #hide_env,
hide_env_values: #hide_env_values,
hide_possible_values: #hide_possible_values,
hide_short_help: #hide_short_help,
hide_long_help: #hide_long_help,
count: #count,
repeatable: #repeatable,
hidden_shorts: &[],
hidden_longs: &[#(#hidden_longs),*],
required: #required,
value_optional: #value_optional,
accepted_choices: #accepted_choices,
choices: #choices,
choice_aliases: #choice_aliases,
choice_details: #choice_details,
ignore_case: #ignore_case,
allow_unknown_choices: #allow_unknown_choices,
validate: #validate,
validate_error: #validate_error,
var_min: #var_min,
var_max: #var_max,
value_var_min: #value_var_min,
value_var_max: #value_var_max,
overrides: &[#(#overrides),*],
conflicts: &[#(#conflicts),*],
requires: &[#(#requires),*],
requires_if: &[#(#requires_if),*],
default_if: &[#(#default_if),*],
exclusive: #exclusive,
delimiter: #delimiter,
required_if: &[#(#required_if),*],
required_if_eq: &[#(#required_if_eq),*],
required_if_eq_all: &[#(#required_if_eq_all),*],
required_unless: &[#(#required_unless),*],
required_unless_all: &[#(#required_unless_all),*],
..usage_argv::spec::FlagMeta::EMPTY
};
}
}
fn arg_meta(cli: &Cli, i: usize, field: &Field, owner: &syn::Ident) -> TokenStream {
let name = format_ident!("ARG_META_{i}");
let table = format_ident!("ARG_{i}");
let help = option_str(field.help.as_deref());
let long_help = option_str(field.long_help.as_deref());
let admonitions = admonitions(field);
let env = option_str(field.env.as_deref());
let env_fallback = &field.env_fallback;
let deprecated_env = &field.deprecated_env;
let help_heading = option_str(field.help_heading.as_deref());
let surface = option_str(field.surface.as_deref());
let available_if = &field.available_if;
let display_order = option_usize(field.display_order);
let complete_type = option_str(field.complete_type.as_deref());
let value_names = &field.value_names;
let defaults = &field.default;
let default = quote!(&[#(#defaults),*]);
let hide = field.hide;
let hide_default_value = field.hide_default_value;
let hide_env = field.hide_env;
let hide_env_values = field.hide_env_values;
let hide_possible_values = field.hide_possible_values;
let hide_short_help = field.hide_short_help;
let hide_long_help = field.hide_long_help;
let conflicts: Vec<String> = field
.conflicts
.iter()
.map(|selector| {
cli.field_for_selector(selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| selector.clone())
})
.collect();
let canonical = |selector: &String| {
cli.field_for_selector(selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| selector.clone())
};
let requires: Vec<String> = field.requires.iter().map(canonical).collect();
let required_if: Vec<String> = field.required_if.iter().map(canonical).collect();
let required_if_eq = field.required_if_eq.iter().map(|condition| {
let selector = canonical(&condition.selector);
let value = &condition.value;
quote!(usage_argv::spec::RequiredIfEq { selector: #selector, value: #value })
});
let required_if_eq_all = field.required_if_eq_all.iter().map(|condition| {
let selector = canonical(&condition.selector);
let value = &condition.value;
quote!(usage_argv::spec::RequiredIfEq { selector: #selector, value: #value })
});
let required_unless: Vec<String> = field.required_unless.iter().map(canonical).collect();
let required_unless_all: Vec<String> =
field.required_unless_all.iter().map(canonical).collect();
let required =
(field.shape == Shape::Required || field.required_collection) && !field.has_default();
let (choices, accepted_choices, choice_aliases, choice_details, ignore_case) =
choices_tokens(cli, field);
let allow_unknown_choices = field.allow_unknown_choices;
let validate = option_str(field.validate.as_deref());
let validate_error = option_str(field.validate_error.as_deref());
let (var_min, var_max) = if matches!(field.kind, Kind::Flag { .. }) {
value_bounds_tokens(field)
} else {
bounds_tokens(field)
};
let delimiter = match field.delimiter {
Some(c) => quote!(::std::option::Option::Some(#c)),
None => quote!(::std::option::Option::None),
};
let (completer_decl, completer) = completer_tokens(i, field, "arg", owner);
quote! {
#completer_decl
pub static #name: usage_argv::spec::ArgMeta = usage_argv::spec::ArgMeta {
complete: #completer,
complete_type: #complete_type,
arg: &#table,
display_order: #display_order,
value_names: &[#(#value_names),*],
help: #help,
long_help: #long_help,
admonitions: #admonitions,
env: #env,
env_fallback: &[#(#env_fallback),*],
deprecated_env: &[#(#deprecated_env),*],
default: #default,
help_heading: #help_heading,
surface: #surface,
available_if: &[#(#available_if),*],
hide: #hide,
hide_default_value: #hide_default_value,
hide_env: #hide_env,
hide_env_values: #hide_env_values,
hide_possible_values: #hide_possible_values,
hide_short_help: #hide_short_help,
hide_long_help: #hide_long_help,
conflicts: &[#(#conflicts),*],
requires: &[#(#requires),*],
required_if: &[#(#required_if),*],
required_if_eq: &[#(#required_if_eq),*],
required_if_eq_all: &[#(#required_if_eq_all),*],
required_unless: &[#(#required_unless),*],
required_unless_all: &[#(#required_unless_all),*],
required: #required,
accepted_choices: #accepted_choices,
choices: #choices,
choice_aliases: #choice_aliases,
choice_details: #choice_details,
ignore_case: #ignore_case,
allow_unknown_choices: #allow_unknown_choices,
validate: #validate,
validate_error: #validate_error,
var_min: #var_min,
var_max: #var_max,
delimiter: #delimiter,
..usage_argv::spec::ArgMeta::EMPTY
};
}
}
fn choices_tokens(
cli: &Cli,
field: &Field,
) -> (
TokenStream,
TokenStream,
TokenStream,
TokenStream,
TokenStream,
) {
if let (true, Some(ty)) = (field.value_enum, field.value_ty.as_ref()) {
return (
quote!(<#ty as usage_argv::spec::ValueEnum>::CHOICES),
quote!(<#ty as usage_argv::spec::ValueEnum>::ACCEPTED_CHOICES),
quote!(<#ty as usage_argv::spec::ValueEnum>::ALIASES),
quote!(<#ty as usage_argv::spec::ValueEnum>::DETAILS),
quote!(<#ty as usage_argv::spec::ValueEnum>::IGNORE_CASE),
);
}
if let Some(outputs) = selector_choices(cli, field) {
let values: Vec<&str> = outputs
.iter()
.filter(|o| !o.hide)
.map(|o| o.name.as_str())
.collect();
let details: Vec<TokenStream> = outputs
.iter()
.filter(|o| !o.hide && o.help.is_some())
.map(|o| {
let value = &o.name;
let help = o.help.as_deref().expect("filtered");
quote! {
usage_argv::spec::ChoiceMeta {
value: #value,
help: ::std::option::Option::Some(#help),
hide: false,
aliases: &[],
}
}
})
.collect();
let choices = quote!(&[#(#values),*]);
return (
choices.clone(),
choices,
quote!(&[]),
quote!(&[#(#details),*]),
quote!(false),
);
}
let choices = &field.choices;
let choices = quote!(&[#(#choices),*]);
(
choices.clone(),
choices,
quote!(&[]),
quote!(&[]),
quote!(false),
)
}
fn selector_choices<'a>(cli: &'a Cli, field: &Field) -> Option<&'a [crate::model::OutputDecl]> {
let select = cli.select.as_deref()?;
if cli.outputs.is_empty() || !field.choices.is_empty() {
return None;
}
let selected = Cli::selector_for_field(field).is_some_and(|s| s == select);
let by_field = cli
.field_for_selector(select)
.is_some_and(|f| f.ident == field.ident);
(selected || by_field).then_some(cli.outputs.as_slice())
}
fn bounds_tokens(field: &Field) -> (TokenStream, TokenStream) {
let render = |bound: Option<usize>| match bound {
Some(n) => quote!(::std::option::Option::Some(#n)),
None => quote!(::std::option::Option::None),
};
(render(field.var_min), render(field.var_max))
}
fn value_bounds_tokens(field: &Field) -> (TokenStream, TokenStream) {
let render = |bound: Option<usize>| match bound {
Some(n) => quote!(::std::option::Option::Some(#n)),
None => quote!(::std::option::Option::None),
};
(render(field.value_var_min), render(field.value_var_max))
}
const KIND_FLAG: u64 = 0;
const KIND_ARG: u64 = 1 << 30;
const KIND_COMMAND: u64 = 2 << 30;
fn declaration_hash(fingerprint: &str) -> u32 {
let mut hash: u32 = 0x811c_9dc5;
for byte in fingerprint.as_bytes() {
hash ^= *byte as u32;
hash = hash.wrapping_mul(0x0100_0193);
}
hash
}
fn key_consts(fingerprint: &str, flags: usize, args: usize) -> TokenStream {
let declaration = declaration_hash(fingerprint);
let command = key_ident("COMMAND", None);
let flag_keys = (0..flags).map(|i| {
let name = key_ident("FLAG", Some(i));
let index = i as u64;
quote!(const #name: u64 = __USAGE_KEY_BASE | #KIND_FLAG | #index;)
});
let arg_keys = (0..args).map(|i| {
let name = key_ident("ARG", Some(i));
let index = i as u64;
quote!(const #name: u64 = __USAGE_KEY_BASE | #KIND_ARG | #index;)
});
quote! {
const __USAGE_KEY_BASE: u64 =
usage_argv::key_base(::core::module_path!(), #declaration);
const #command: u64 = __USAGE_KEY_BASE | #KIND_COMMAND;
#(#flag_keys)*
#(#arg_keys)*
}
}
fn key_ident(kind: &str, index: Option<usize>) -> proc_macro2::Ident {
match index {
Some(i) => format_ident!("__USAGE_KEY_{kind}_{i}"),
None => format_ident!("__USAGE_KEY_{kind}"),
}
}
struct Tables {
decls: TokenStream,
meta_decls: TokenStream,
flags: TokenStream,
args: TokenStream,
flag_metas: TokenStream,
arg_metas: TokenStream,
flatten_groups: TokenStream,
}
fn tables(cli: &Cli) -> Tables {
let mut flag_groups: Vec<TokenStream> = Vec::new();
let mut arg_groups: Vec<TokenStream> = Vec::new();
let mut flag_meta_groups: Vec<TokenStream> = Vec::new();
let mut arg_meta_groups: Vec<TokenStream> = Vec::new();
let mut own_flags: Vec<usize> = Vec::new();
let mut own_args: Vec<usize> = Vec::new();
let (mut flag_at, mut arg_at) = (0usize, 0usize);
let mut flattened = false;
let mut flatten_groups: Vec<TokenStream> = Vec::new();
let mut flag_offset: Vec<TokenStream> = Vec::new();
let mut own_since_flatten = 0usize;
fn flush_flags(
own: &mut Vec<usize>,
groups: &mut Vec<TokenStream>,
metas: &mut Vec<TokenStream>,
) {
if own.is_empty() {
return;
}
let refs = own.iter().map(|i| {
let name = format_ident!("FLAG_{i}");
quote!(&#name)
});
groups.push(quote!(&[#(#refs),*]));
let meta_refs = own.iter().map(|i| format_ident!("FLAG_META_{i}"));
metas.push(quote!(&[#(#meta_refs),*]));
own.clear();
}
fn flush_args(
own: &mut Vec<usize>,
groups: &mut Vec<TokenStream>,
metas: &mut Vec<TokenStream>,
) {
if own.is_empty() {
return;
}
let refs = own.iter().map(|i| {
let name = format_ident!("ARG_{i}");
quote!(&#name)
});
groups.push(quote!(&[#(#refs),*]));
let meta_refs = own.iter().map(|i| format_ident!("ARG_META_{i}"));
metas.push(quote!(&[#(#meta_refs),*]));
own.clear();
}
for field in &cli.fields {
match &field.kind {
Kind::Flag { .. } => {
own_flags.push(flag_at);
flag_at += 1;
own_since_flatten += 1;
}
Kind::Arg { .. } => {
own_args.push(arg_at);
arg_at += 1;
}
Kind::Flatten { ty, help_heading } => {
flattened = true;
if own_since_flatten > 0 {
flag_offset.push(quote!(#own_since_flatten));
own_since_flatten = 0;
}
let name = flagset_name(ty);
let terms = flag_offset.iter();
let help_heading = option_str(help_heading.as_deref());
flatten_groups.push(quote! {
usage_argv::spec::FlattenGroup {
name: #name,
start: 0 #(+ #terms)*,
meta: <#ty as usage_argv::spec::CommandArgs>::META,
help_heading: #help_heading,
}
});
flag_offset
.push(quote!(<#ty as usage_argv::spec::CommandArgs>::COMMAND.flags.len()));
flush_flags(&mut own_flags, &mut flag_groups, &mut flag_meta_groups);
flush_args(&mut own_args, &mut arg_groups, &mut arg_meta_groups);
flag_groups.push(quote!(<#ty as usage_argv::spec::CommandArgs>::COMMAND.flags));
arg_groups.push(quote!(<#ty as usage_argv::spec::CommandArgs>::COMMAND.args));
flag_meta_groups.push(quote!(<#ty as usage_argv::spec::CommandArgs>::META.flags));
arg_meta_groups.push(quote!(<#ty as usage_argv::spec::CommandArgs>::META.args));
}
Kind::ArgGroup { ty, .. } => {
flattened = true;
if own_since_flatten > 0 {
flag_offset.push(quote!(#own_since_flatten));
own_since_flatten = 0;
}
flag_offset.push(quote!(<#ty as usage_argv::spec::ArgGroup>::FLAGS.len()));
flush_flags(&mut own_flags, &mut flag_groups, &mut flag_meta_groups);
flag_groups.push(quote!(<#ty as usage_argv::spec::ArgGroup>::FLAGS));
flag_meta_groups.push(quote!(<#ty as usage_argv::spec::ArgGroup>::FLAG_METAS));
}
Kind::Clause { ty, .. } => {
flattened = true;
if own_since_flatten > 0 {
flag_offset.push(quote!(#own_since_flatten));
own_since_flatten = 0;
}
flag_offset
.push(quote!(<#ty as usage_argv::spec::CommandArgs>::COMMAND.flags.len()));
flush_flags(&mut own_flags, &mut flag_groups, &mut flag_meta_groups);
flag_groups.push(quote!(<#ty as usage_argv::spec::CommandArgs>::COMMAND.flags));
flag_meta_groups.push(quote!(<#ty as usage_argv::spec::CommandArgs>::META.flags));
}
Kind::Subcommand { .. } | Kind::Skip => {}
}
}
flush_flags(&mut own_flags, &mut flag_groups, &mut flag_meta_groups);
flush_args(&mut own_args, &mut arg_groups, &mut arg_meta_groups);
if !flattened {
let flag_refs = (0..flag_at).map(|i| {
let name = format_ident!("FLAG_{i}");
quote!(&#name)
});
let arg_refs = (0..arg_at).map(|i| {
let name = format_ident!("ARG_{i}");
quote!(&#name)
});
let flag_meta_refs = (0..flag_at).map(|i| format_ident!("FLAG_META_{i}"));
let arg_meta_refs = (0..arg_at).map(|i| format_ident!("ARG_META_{i}"));
return Tables {
decls: TokenStream::new(),
meta_decls: TokenStream::new(),
flags: quote!(&[#(#flag_refs),*]),
args: quote!(&[#(#arg_refs),*]),
flag_metas: quote!(&[#(#flag_meta_refs),*]),
arg_metas: quote!(&[#(#arg_meta_refs),*]),
flatten_groups: quote!(&[]),
};
}
Tables {
decls: quote! {
const FLAG_GROUPS: &[&[&usage_argv::Flag<'static>]] = &[#(#flag_groups),*];
const ARG_GROUPS: &[&[&usage_argv::Arg<'static>]] = &[#(#arg_groups),*];
static FLAGS: [&usage_argv::Flag<'static>;
usage_argv::table_len(FLAG_GROUPS)] =
usage_argv::concat_flags(FLAG_GROUPS);
static ARGS: [&usage_argv::Arg<'static>; usage_argv::table_len(ARG_GROUPS)] =
usage_argv::concat_args(ARG_GROUPS);
},
meta_decls: quote! {
const FLAG_META_GROUPS: &[&[usage_argv::spec::FlagMeta<'static>]] =
&[#(#flag_meta_groups),*];
const ARG_META_GROUPS: &[&[usage_argv::spec::ArgMeta<'static>]] =
&[#(#arg_meta_groups),*];
static FLAG_METAS: [usage_argv::spec::FlagMeta<'static>;
usage_argv::table_len(FLAG_META_GROUPS)] =
usage_argv::spec::concat_flag_metas(FLAG_META_GROUPS);
static ARG_METAS: [usage_argv::spec::ArgMeta<'static>;
usage_argv::table_len(ARG_META_GROUPS)] =
usage_argv::spec::concat_arg_metas(ARG_META_GROUPS);
const FLATTEN_GROUPS: &[usage_argv::spec::FlattenGroup<'static>] =
&[#(#flatten_groups),*];
},
flags: quote!(&FLAGS),
args: quote!(&ARGS),
flag_metas: quote!(&FLAG_METAS),
arg_metas: quote!(&ARG_METAS),
flatten_groups: quote!(FLATTEN_GROUPS),
}
}
fn flagset_name(ty: &syn::Type) -> String {
let rendered = type_name(ty);
let last = rendered
.rsplit("::")
.next()
.unwrap_or(&rendered)
.trim()
.to_string();
to_kebab(&last)
}
fn flag_arm(cli: &Cli, i: usize, field: &Field) -> TokenStream {
let key = key_ident("FLAG", Some(i));
let ident = &field.ident;
let given = format_ident!("__given_{}", ident);
let direct = mirrored_global_ident(field).map(|mirrored| quote!(partial.#mirrored = false;));
let displaced = displacements(cli, field);
let undisplaced = is_displaceable(cli, field).then(|| {
let overridden = format_ident!("__overridden_{}", ident);
quote!(partial.#overridden = false;)
});
let duplicate = rejects_duplicate(cli, field).then(|| {
let duplicated = format_ident!("__duplicated_{}", ident);
if has_negate(field) {
let negated = format_ident!("__negated_{}", ident);
let (guard, mark) = if duplicates_per_level(cli, field) {
let here = format_ident!("__here_{}", ident);
(quote!(partial.#here), quote!(partial.#here = true;))
} else {
(quote!(partial.#given), TokenStream::new())
};
quote! {
if #guard {
partial.#duplicated |= partial.#negated == negated;
}
#mark
partial.#negated = negated;
}
} else if duplicates_per_level(cli, field) {
let here = format_ident!("__here_{}", ident);
quote! {
if partial.#here {
partial.#duplicated = true;
}
partial.#here = true;
}
} else {
quote! {
if partial.#given {
partial.#duplicated = true;
}
}
}
});
let body = flag_binding_body(field);
let remember_invalid_choice = remember_invalid_flag_choice(field);
let table = format_ident!("FLAG_{i}");
quote! {
#key if ::core::ptr::eq(*flag, &#table) => {
#duplicate
#remember_invalid_choice
#body
partial.#given = true;
#direct
#undisplaced
#(#displaced)*
true
}
}
}
fn tracks_invalid_choice(field: &Field) -> bool {
(!field.choices.is_empty() || field.value_enum) && !field.allow_unknown_choices
}
fn tracks_deprecated_env(field: &Field) -> bool {
!field.deprecated_env.is_empty()
}
fn deprecated_env_ident(field: &Field) -> proc_macro2::Ident {
format_ident!("__deprecated_env_{}", field.ident)
}
fn tracks_deprecated(field: &Field) -> bool {
field.deprecated.is_some()
|| field.deprecated_warn_at.is_some()
|| field.deprecated_remove_at.is_some()
}
fn accepted_choices(field: &Field) -> TokenStream {
match (field.value_enum, field.value_ty.as_ref()) {
(true, Some(ty)) => quote!(<#ty as usage_argv::spec::ValueEnum>::ACCEPTED_CHOICES),
_ => {
let choices = &field.choices;
quote!(&[#(#choices),*])
}
}
}
fn choice_ignore_case(field: &Field) -> TokenStream {
match (field.value_enum, field.value_ty.as_ref()) {
(true, Some(ty)) => quote!(<#ty as usage_argv::spec::ValueEnum>::IGNORE_CASE),
_ => quote!(false),
}
}
fn remember_invalid_flag_choice(field: &Field) -> TokenStream {
if !tracks_invalid_choice(field) {
return TokenStream::new();
}
let invalid = format_ident!("__invalid_choice_{}", field.ident);
let accepted = accepted_choices(field);
let ignore_case = choice_ignore_case(field);
let reset = (!matches!(field.shape, Shape::Many)).then(|| quote!(partial.#invalid = false;));
let check = quote! {
if let ::std::result::Result::Ok(__usage_choice_text) =
::std::str::from_utf8(__usage_choice_value)
{
partial.#invalid |= !usage_argv::spec::choice_matches(
#accepted,
__usage_choice_text,
#ignore_case,
);
}
};
match field.delimiter {
Some(delimiter) => {
let byte =
u8::try_from(u32::from(delimiter)).expect("the model rejects non-ASCII delimiters");
quote! {
#reset
if let ::std::option::Option::Some(__usage_choice_value) = value {
for __usage_choice_value in
__usage_choice_value.split(|byte| *byte == #byte)
{
#check
}
}
}
}
None => quote! {
#reset
if let ::std::option::Option::Some(__usage_choice_value) = value {
#check
}
},
}
}
fn flag_binding_body(field: &Field) -> TokenStream {
let ident = &field.ident;
match field.shape {
Shape::Bool if field.bool_value => quote! {
partial.#ident = match value {
Some(b"true") => !negated,
Some(b"false") => negated,
Some(_) => return false,
None => !negated,
};
},
Shape::Bool => quote!(partial.#ident = !negated;),
Shape::Count => quote!(partial.#ident = partial.#ident.saturating_add(1);),
Shape::Optional if field.optional_value_type => quote! {
partial.#ident = value.map(__usage_text);
},
Shape::Optional => quote! {
partial.#ident = ::std::option::Option::Some(__usage_value_text(value));
},
Shape::Required => quote!(partial.#ident = __usage_value_text(value);),
Shape::Many => match field.delimiter {
Some(delimiter) => {
let byte = u8::try_from(u32::from(delimiter))
.expect("the model rejects non-ASCII delimiters");
quote! {
let value = __usage_value_text(value);
for part in value.split(|b| *b == #byte) {
partial.#ident.push(part.to_vec());
}
}
}
None => quote!(partial.#ident.push(__usage_value_text(value));),
},
}
}
fn mirrored_global_ident(field: &Field) -> Option<proc_macro2::Ident> {
matches!(field.kind, Kind::Flag { global: true, .. })
.then(|| format_ident!("__mirrored_{}", field.ident))
}
fn policy_given(field: &Field) -> TokenStream {
let given = format_ident!("__given_{}", field.ident);
match mirrored_global_ident(field) {
Some(mirrored) => quote!(partial.#given && !partial.#mirrored),
None => quote!(partial.#given),
}
}
fn semantic_given(field: &Field) -> TokenStream {
let given = format_ident!("__given_{}", field.ident);
quote!(partial.#given)
}
fn view_policy_given(field: &Field) -> TokenStream {
let given = policy_given(field);
let active = view_field_active(field);
quote!((#active) && (#given))
}
fn standing_ident(field: &Field) -> proc_macro2::Ident {
format_ident!("__usage_standing_{}", field.ident)
}
fn standing_presence(field: &Field) -> Option<TokenStream> {
let ident = &field.ident;
match &field.kind {
Kind::Skip | Kind::Flatten { .. } => None,
Kind::Clause { .. } => Some(quote!(!__usage_s.#ident.is_empty())),
Kind::ArgGroup { multiple: true, .. } => Some(quote!(!__usage_s.#ident.is_empty())),
Kind::ArgGroup { optional, .. } | Kind::Subcommand { optional, .. } => Some(if *optional {
quote!(__usage_s.#ident.is_some())
} else {
quote!(true)
}),
Kind::Flag { .. } | Kind::Arg { .. } => Some(match field.shape {
Shape::Bool => quote!(__usage_s.#ident),
Shape::Count => {
let ty = &field.ty;
quote!(__usage_s.#ident != <#ty as ::std::default::Default>::default())
}
Shape::Optional => quote!(__usage_s.#ident.is_some()),
Shape::Required => quote!(true),
Shape::Many if field.optional_collection => quote!(__usage_s.#ident.is_some()),
Shape::Many => quote!(!__usage_s.#ident.is_empty()),
}),
}
}
fn standing_locals(cli: &Cli) -> TokenStream {
let locals = cli.fields.iter().filter_map(|field| {
let name = standing_ident(field);
let ident = &field.ident;
Some(match &field.kind {
Kind::Skip => return None,
Kind::Flatten { .. } => quote! {
let #name = __usage_standing.map(|__usage_s| &__usage_s.#ident);
},
Kind::Clause { .. } => quote! {
let #name = __usage_standing.map(|__usage_s| &__usage_s.#ident);
},
Kind::ArgGroup { multiple: true, .. } => quote! {
let #name = __usage_standing.map(|__usage_s| &__usage_s.#ident);
},
Kind::ArgGroup { optional: true, .. } | Kind::Subcommand { optional: true, .. } => {
quote! {
let #name = __usage_standing.and_then(|__usage_s| __usage_s.#ident.as_ref());
}
}
Kind::ArgGroup {
optional: false,
multiple: false,
..
}
| Kind::Subcommand {
optional: false, ..
} => quote! {
let #name = __usage_standing.map(|__usage_s| &__usage_s.#ident);
},
_ => {
let present = standing_presence(field)?;
quote!(let #name = __usage_standing.is_some_and(|__usage_s| #present);)
}
})
});
quote!(#(#locals)*)
}
fn standing_flag(field: &Field) -> Option<TokenStream> {
let name = standing_ident(field);
match &field.kind {
Kind::Skip | Kind::Flatten { .. } => None,
Kind::ArgGroup { .. } | Kind::Subcommand { .. } => Some(quote!(#name.is_some())),
_ => standing_presence(field).map(|_| quote!(#name)),
}
}
fn given_or_standing(field: &Field, given: TokenStream) -> TokenStream {
match standing_flag(field) {
Some(standing) => quote!((#given || #standing)),
None => given,
}
}
fn unless_standing(field: &Field) -> TokenStream {
match standing_flag(field) {
Some(standing) => quote!(&& !#standing),
None => quote!(),
}
}
fn unless_standing_only(field: &Field) -> TokenStream {
if field.shape != Shape::Required {
return quote!();
}
match standing_flag(field) {
Some(standing) => {
let given = format_ident!("__given_{}", field.ident);
quote!(&& !(#standing && !partial.#given))
}
None => quote!(),
}
}
fn standing_policy_given(field: &Field) -> TokenStream {
given_or_standing(field, view_policy_given(field))
}
fn standing_semantic_given(field: &Field) -> TokenStream {
given_or_standing(field, semantic_given(field))
}
fn view_field_active(field: &Field) -> TokenStream {
let Kind::Flag {
longs,
hidden_longs,
shorts,
negate,
global: true,
..
} = &field.kind
else {
return quote!(__usage_view.is_none());
};
let long_selectors = longs
.iter()
.chain(hidden_longs)
.chain(negate.iter())
.map(|long| format!("--{long}"));
let short_selectors = shorts.iter().map(|short| format!("-{short}"));
let selectors: Vec<String> = long_selectors.chain(short_selectors).collect();
let named = if selectors.is_empty() {
quote!(false)
} else {
quote! {
__usage_view.globals.iter().any(|__usage_selector| {
matches!(*__usage_selector, #(#selectors)|*)
})
}
};
quote! {
match __usage_view {
::std::option::Option::None => true,
::std::option::Option::Some(__usage_view) => {
__usage_view.all_globals || #named
}
}
}
}
fn field_active_in_view(field: &Field, view: &ViewDecl) -> bool {
let Kind::Flag {
longs,
hidden_longs,
shorts,
negate,
global: true,
..
} = &field.kind
else {
return false;
};
view.all_globals
|| longs
.iter()
.chain(hidden_longs)
.chain(negate.iter())
.map(|long| format!("--{long}"))
.chain(shorts.iter().map(|short| format!("-{short}")))
.any(|selector| view.globals.contains(&selector))
}
fn duplicates_per_level(cli: &Cli, field: &Field) -> bool {
rejects_duplicate(cli, field) && matches!(field.kind, Kind::Flag { global: true, .. })
}
fn reset_per_level(cli: &Cli) -> TokenStream {
let resets = cli
.fields
.iter()
.filter(|f| duplicates_per_level(cli, f))
.map(|f| {
let here = format_ident!("__here_{}", f.ident);
quote!(partial.#here = false;)
});
quote!(#(#resets)*)
}
fn rejects_duplicate(cli: &Cli, field: &Field) -> bool {
(!cli.args_override_self || cli.composable)
&& matches!(field.kind, Kind::Flag { .. })
&& !matches!(field.shape, Shape::Count | Shape::Many)
&& !field.repeatable
}
fn has_negate(field: &Field) -> bool {
matches!(
&field.kind,
Kind::Flag {
negate: Some(_),
..
}
)
}
fn displacements(cli: &Cli, field: &Field) -> Vec<TokenStream> {
let mut displacements: Vec<TokenStream> = displaced_by(cli, field)
.into_iter()
.map(|displaced| displace_statement(cli, displaced))
.collect();
for selector in field
.overrides
.iter()
.filter(|selector| cli.field_for_selector(selector).is_none())
{
for flattened in &cli.fields {
let Kind::Flatten { ty, .. } = &flattened.kind else {
continue;
};
let ident = &flattened.ident;
displacements.push(quote! {
let _ = <#ty as usage_argv::spec::CommandArgs>::displace(
&mut partial.#ident,
#selector,
);
});
}
for grouped in &cli.fields {
let Kind::ArgGroup { ty, .. } = &grouped.kind else {
continue;
};
let ident = &grouped.ident;
displacements.push(quote! {
let _ = <#ty as usage_argv::spec::ArgGroup>::displace(
&mut partial.#ident,
#selector,
);
});
}
}
displacements
}
fn displace_statement(cli: &Cli, field: &Field) -> TokenStream {
let reset = reset_to_default(field);
let given = format_ident!("__given_{}", field.ident);
let overridden = format_ident!("__overridden_{}", field.ident);
let duplicated = rejects_duplicate(cli, field).then(|| {
let duplicated = format_ident!("__duplicated_{}", field.ident);
quote!(partial.#duplicated = false;)
});
let mirrored = mirrored_global_ident(field).map(|mirrored| quote!(partial.#mirrored = false;));
quote! {
#reset
partial.#given = false;
#mirrored
#duplicated
partial.#overridden = true;
}
}
fn displaced_by<'a>(cli: &'a Cli, field: &Field) -> Vec<&'a Field> {
let names = |target: &Field, selectors: &[String]| {
selectors.iter().any(|selector| {
cli.field_for_selector(selector)
.is_some_and(|named| named.ident == target.ident)
})
};
cli.fields
.iter()
.filter(|other| {
other.ident != field.ident
&& (names(other, &field.overrides) || names(field, &other.overrides))
})
.collect()
}
fn is_displaceable(cli: &Cli, field: &Field) -> bool {
(cli.composable && matches!(field.kind, Kind::Flag { .. }))
|| !field.overrides.is_empty()
|| !displaced_by(cli, field).is_empty()
}
fn arg_arm(i: usize, field: &Field) -> TokenStream {
let key = key_ident("ARG", Some(i));
let ident = &field.ident;
let given = format_ident!("__given_{}", ident);
let body = match field.shape {
Shape::Many => match field.delimiter {
Some(delimiter) => {
let byte = u8::try_from(u32::from(delimiter))
.expect("the model rejects non-ASCII delimiters");
quote! {
if delimit {
for part in value.split(|b| *b == #byte) {
partial.#ident.push(__usage_text(part));
}
} else {
partial.#ident.push(__usage_text(value));
}
}
}
None => quote!(partial.#ident.push(__usage_text(value));),
},
Shape::Optional => quote! {
partial.#ident = ::std::option::Option::Some(__usage_text(value));
},
_ => quote!(partial.#ident = __usage_text(value);),
};
let remember_invalid_choice = if tracks_invalid_choice(field) {
let invalid = format_ident!("__invalid_choice_{}", field.ident);
let accepted = accepted_choices(field);
let ignore_case = choice_ignore_case(field);
let reset =
(!matches!(field.shape, Shape::Many)).then(|| quote!(partial.#invalid = false;));
let check = quote! {
if let ::std::result::Result::Ok(__usage_choice_text) =
::std::str::from_utf8(__usage_choice_value)
{
partial.#invalid |= !usage_argv::spec::choice_matches(
#accepted,
__usage_choice_text,
#ignore_case,
);
}
};
match field.delimiter {
Some(delimiter) => {
let byte = u8::try_from(u32::from(delimiter))
.expect("the model rejects non-ASCII delimiters");
quote! {
#reset
if delimit {
for __usage_choice_value in value.split(|byte| *byte == #byte) {
#check
}
} else {
let __usage_choice_value = value;
#check
}
}
}
None => quote! {
#reset
let __usage_choice_value = value;
#check
},
}
} else {
TokenStream::new()
};
let table = format_ident!("ARG_{i}");
quote! {
#key if ::core::ptr::eq(*arg, &#table) => {
#remember_invalid_choice
#body
partial.#given = true;
true
}
}
}
fn option_str(value: Option<&str>) -> TokenStream {
match value {
Some(v) => quote!(::std::option::Option::Some(#v)),
None => quote!(::std::option::Option::None),
}
}
fn examples_table(examples: &[ExampleDecl]) -> TokenStream {
let entries = examples.iter().map(|example| {
let code = &example.code;
let header = option_expr(example.header.as_ref());
let help = option_expr(example.help.as_ref());
quote! {
usage_argv::spec::Example {
code: #code,
header: #header,
help: #help,
}
}
});
quote!(&[#(#entries),*])
}
fn headings_table(headings: &[HeadingDecl]) -> TokenStream {
let entries = headings.iter().map(|heading| {
let title = &heading.title;
let help = &heading.help;
quote! {
usage_argv::spec::HeadingMeta {
title: #title,
help: #help,
}
}
});
quote!(&[#(#entries),*])
}
struct OutputTokens {
decls: Vec<TokenStream>,
outputs: TokenStream,
select: TokenStream,
exit_codes: TokenStream,
}
fn output_tokens(cli: &Cli) -> OutputTokens {
let mut decls = Vec::new();
let outputs: Vec<TokenStream> = cli
.outputs
.iter()
.enumerate()
.map(|(i, output)| {
let name = &output.name;
let media_type = option_str(output.media_type.as_deref());
let framing = match output.framing.as_str() {
"json" => quote!(usage_argv::spec::Framing::Json),
"jsonl" => quote!(usage_argv::spec::Framing::Jsonl),
_ => quote!(usage_argv::spec::Framing::Text),
};
let help = option_str(output.help.as_deref());
let default = output.default;
let hide = output.hide;
let select = option_str(output.select.as_deref());
let schema_fn = match &output.schema {
None => quote!(::std::option::Option::None),
Some(SchemaSource::Function(path)) => {
quote!(::std::option::Option::Some(#path))
}
Some(source) => {
let wrapper = format_ident!("__usage_output_schema_{i}");
let body = match source {
SchemaSource::Literal(text) => {
quote!(::std::string::ToString::to_string(#text))
}
SchemaSource::Type(ty) => {
let schemars = schemars_path();
quote!(::std::string::ToString::to_string(
&#schemars::schema_for!(#ty)
))
}
SchemaSource::Function(_) => unreachable!("handled above"),
};
decls.push(quote! {
fn #wrapper() -> ::std::string::String { #body }
});
quote!(::std::option::Option::Some(#wrapper))
}
};
quote! {
usage_argv::spec::OutputMeta {
name: #name,
media_type: #media_type,
framing: #framing,
help: #help,
default: #default,
hide: #hide,
select: #select,
schema: ::std::option::Option::None,
schema_fn: #schema_fn,
}
}
})
.collect();
let exit_codes: Vec<TokenStream> = cli
.exit_codes
.iter()
.map(|exit_code| {
let code = exit_code.code;
let help = &exit_code.help;
quote! {
usage_argv::spec::ExitCodeMeta { code: #code, help: #help }
}
})
.collect();
OutputTokens {
decls,
outputs: quote!(&[#(#outputs),*]),
select: option_str(cli.select.as_deref()),
exit_codes: quote!(&[#(#exit_codes),*]),
}
}
fn option_expr(value: Option<&TokenStream>) -> TokenStream {
match value {
Some(v) => quote!(::std::option::Option::Some(#v)),
None => quote!(::std::option::Option::None),
}
}
fn option_usize(value: Option<usize>) -> TokenStream {
match value {
Some(v) => quote!(::std::option::Option::Some(#v)),
None => quote!(::std::option::Option::None),
}
}
fn presence_methods(cli: &Cli) -> TokenStream {
let direct_given = cli.fields.iter().filter_map(|field| {
if matches!(
field.kind,
Kind::Flatten { .. }
| Kind::ArgGroup { .. }
| Kind::Clause { .. }
| Kind::Subcommand { .. }
| Kind::Skip
) {
return None;
}
let given = policy_given(field);
let name = &field.name;
Some(quote! {
if #given {
return ::std::option::Option::Some(#name);
}
})
});
let flattened_given = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(name) =
<#ty as usage_argv::spec::CommandArgs>::any_given(&partial.#ident)
{
return ::std::option::Option::Some(name);
}
})
});
let grouped_given = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(name) =
<#ty as usage_argv::spec::ArgGroup>::any_given(&partial.#ident)
{
return ::std::option::Option::Some(name);
}
})
});
let selected = cli.fields.iter().find_map(|field| {
if !matches!(field.kind, Kind::Subcommand { .. }) {
return None;
}
let name = &field.name;
Some(quote! {
if partial.__usage_selected.is_some() {
return ::std::option::Option::Some(#name);
}
})
});
let direct_exclusive = cli.fields.iter().filter_map(|field| {
if !field.exclusive {
return None;
}
let given = policy_given(field);
let name = &field.name;
Some(quote! {
if #given {
return ::std::option::Option::Some(#name);
}
})
});
let flattened_exclusive = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(name) =
<#ty as usage_argv::spec::CommandArgs>::exclusive_given(&partial.#ident)
{
return ::std::option::Option::Some(name);
}
})
});
let selected_exclusive = cli.fields.iter().find_map(|field| {
let Kind::Subcommand { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
if let ::std::option::Option::Some(name) =
<#ty as usage_argv::spec::Subcommands>::exclusive_given(
&partial.__usage_sub,
partial.__usage_selected,
)
{
return ::std::option::Option::Some(name);
}
})
});
quote! {
fn any_given(partial: &Self::Partial) -> ::std::option::Option<&'static str> {
#(#direct_given)*
#(#flattened_given)*
#(#grouped_given)*
#selected
::std::option::Option::None
}
fn exclusive_given(partial: &Self::Partial) -> ::std::option::Option<&'static str> {
#(#direct_exclusive)*
#(#flattened_exclusive)*
#selected_exclusive
::std::option::Option::None
}
fn argument_state(
partial: &Self::Partial,
selector: &str,
) -> ::std::option::Option<usage_argv::spec::ArgumentState> {
argument_state(partial, selector)
}
fn canonical_selector(selector: &str) -> ::std::option::Option<&'static str> {
canonical_selector(selector)
}
fn argument_matches(
partial: &Self::Partial,
selector: &str,
value: &[u8],
) -> ::std::option::Option<bool> {
argument_matches(partial, selector, value)
}
fn standing_state(
standing: &Self,
selector: &str,
) -> ::std::option::Option<usage_argv::spec::ArgumentState> {
standing_state(standing, selector)
}
fn standing_matches(
standing: &Self,
selector: &str,
value: &[u8],
) -> ::std::option::Option<bool> {
standing_matches(standing, selector, value)
}
fn displace(partial: &mut Self::Partial, selector: &str) -> bool {
displace(partial, selector)
}
fn event_matches(
event: &usage_argv::Event<'_, '_, '_>,
selector: &str,
) -> bool {
event_matches(event, selector)
}
}
}
fn field_selectors(field: &Field) -> Vec<String> {
match &field.kind {
Kind::Flag {
longs,
shorts,
negate,
..
} => longs
.iter()
.map(|long| format!("--{long}"))
.chain(shorts.iter().map(|short| format!("-{short}")))
.chain(negate.iter().map(|long| format!("--{long}")))
.collect(),
Kind::Arg { .. } => {
let inferred = to_kebab(&field.ident.to_string());
if inferred == field.name {
vec![field.name.clone()]
} else {
vec![field.name.clone(), inferred]
}
}
_ => Vec::new(),
}
}
fn argument_lookup_functions(cli: &Cli) -> TokenStream {
let args = &cli.ident;
let canonical_arms = cli.fields.iter().filter_map(|field| {
let canonical = Cli::selector_for_field(field)?;
let selectors = field_selectors(field);
Some(quote!(#(#selectors)|* => return ::std::option::Option::Some(#canonical),))
});
let canonical_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
if let ::std::option::Option::Some(canonical) =
<#ty as usage_argv::spec::CommandArgs>::canonical_selector(selector)
{
return ::std::option::Option::Some(canonical);
}
})
});
let canonical_clause = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
if let ::std::option::Option::Some(canonical) =
<#ty as usage_argv::spec::CommandArgs>::canonical_selector(selector)
{
return ::std::option::Option::Some(canonical);
}
})
});
let state_arms = cli.fields.iter().filter_map(|field| {
if matches!(
field.kind,
Kind::Flatten { .. }
| Kind::ArgGroup { .. }
| Kind::Clause { .. }
| Kind::Subcommand { .. }
| Kind::Skip
) {
return None;
}
let selectors = field_selectors(field);
let given = policy_given(field);
let satisfied = semantic_given(field);
let name = &field.name;
let defaulted = field.has_default();
let conditionally_defaulted =
default_if_would_apply(cli, field, Lookup::Module).unwrap_or_else(|| quote!(false));
Some(quote! {
#(#selectors)|* => return ::std::option::Option::Some(
usage_argv::spec::ArgumentState {
name: #name,
given: #given,
satisfied: #satisfied || #defaulted || (#conditionally_defaulted),
},
),
})
});
let state_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(state) =
<#ty as usage_argv::spec::CommandArgs>::argument_state(
&partial.#ident,
selector,
)
{
return ::std::option::Option::Some(state);
}
})
});
let state_grouped = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(state) =
<#ty as usage_argv::spec::ArgGroup>::argument_state(
&partial.#ident,
selector,
)
{
return ::std::option::Option::Some(state);
}
})
});
let state_clause = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
let current = format_ident!("__usage_current_{}", ident);
Some(quote! {
let mut __usage_clause_state = ::std::option::Option::None;
for __usage_instance in partial.#ident.iter().chain(::core::iter::once(&partial.#current)) {
if let ::std::option::Option::Some(state) =
<#ty as usage_argv::spec::CommandArgs>::argument_state(
__usage_instance,
selector,
)
{
if state.given || state.satisfied {
return ::std::option::Option::Some(state);
}
__usage_clause_state = ::std::option::Option::Some(state);
}
}
if __usage_clause_state.is_some() {
return __usage_clause_state;
}
})
});
let match_arms = cli.fields.iter().filter_map(|field| {
if matches!(
field.kind,
Kind::Flatten { .. }
| Kind::ArgGroup { .. }
| Kind::Clause { .. }
| Kind::Subcommand { .. }
| Kind::Skip
) {
return None;
}
let selectors = field_selectors(field);
let ident = &field.ident;
let given = policy_given(field);
let matches = match field.shape {
Shape::Optional => quote!(partial.#ident.as_deref().is_some_and(|v| v == value)),
Shape::Required => quote!(partial.#ident.as_slice() == value),
Shape::Many => quote!(partial.#ident.iter().any(|v| v.as_slice() == value)),
Shape::Bool => quote!(
(value == b"true" && partial.#ident) || (value == b"false" && !partial.#ident)
),
Shape::Count => quote!(
(value == b"true" && partial.#ident > 0)
|| (value == b"false" && partial.#ident == 0)
),
};
Some(quote!(#(#selectors)|* => return ::std::option::Option::Some(#given && #matches),))
});
let match_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(matches) =
<#ty as usage_argv::spec::CommandArgs>::argument_matches(
&partial.#ident,
selector,
value,
)
{
return ::std::option::Option::Some(matches);
}
})
});
let match_grouped = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(matches) =
<#ty as usage_argv::spec::ArgGroup>::argument_matches(
&partial.#ident,
selector,
value,
)
{
return ::std::option::Option::Some(matches);
}
})
});
let match_clause = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
let current = format_ident!("__usage_current_{}", ident);
Some(quote! {
let mut __usage_clause_recognized = false;
for __usage_instance in partial.#ident.iter().chain(::core::iter::once(&partial.#current)) {
match <#ty as usage_argv::spec::CommandArgs>::argument_matches(
__usage_instance,
selector,
value,
) {
::std::option::Option::Some(true) => {
return ::std::option::Option::Some(true);
}
::std::option::Option::Some(false) => {
__usage_clause_recognized = true;
}
::std::option::Option::None => {}
}
}
if __usage_clause_recognized {
return ::std::option::Option::Some(false);
}
})
});
let standing_state_arms = cli.fields.iter().filter_map(|field| {
if matches!(
field.kind,
Kind::Flatten { .. }
| Kind::ArgGroup { .. }
| Kind::Clause { .. }
| Kind::Subcommand { .. }
| Kind::Skip
) {
return None;
}
let selectors = field_selectors(field);
let present = standing_presence(field)?;
let name = &field.name;
Some(quote! {
#(#selectors)|* => {
let given = #present;
return ::std::option::Option::Some(usage_argv::spec::ArgumentState {
name: #name,
given,
satisfied: given,
});
}
})
});
let standing_state_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(state) =
<#ty as usage_argv::spec::CommandArgs>::standing_state(
&__usage_s.#ident,
selector,
)
{
return ::std::option::Option::Some(state);
}
})
});
let standing_state_grouped = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup {
ty,
multiple,
optional,
..
} = &field.kind
else {
return None;
};
let ident = &field.ident;
let values = if *multiple || *optional {
quote!(__usage_s.#ident.iter())
} else {
quote!(::core::iter::once(&__usage_s.#ident))
};
Some(quote! {
let mut __usage_standing_group_state = ::std::option::Option::None;
for __usage_member in #values {
if let ::std::option::Option::Some(state) =
<#ty as usage_argv::spec::ArgGroup>::standing_state(
__usage_member,
selector,
)
{
if state.given || state.satisfied {
return ::std::option::Option::Some(state);
}
__usage_standing_group_state = ::std::option::Option::Some(state);
}
}
if __usage_standing_group_state.is_some() {
return __usage_standing_group_state;
}
})
});
let standing_state_clause = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
let mut __usage_standing_clause_state = ::std::option::Option::None;
for __usage_instance in &__usage_s.#ident {
if let ::std::option::Option::Some(state) =
<#ty as usage_argv::spec::CommandArgs>::standing_state(
__usage_instance,
selector,
)
{
if state.given || state.satisfied {
return ::std::option::Option::Some(state);
}
__usage_standing_clause_state = ::std::option::Option::Some(state);
}
}
if __usage_standing_clause_state.is_some() {
return __usage_standing_clause_state;
}
})
});
let standing_match_arms = cli.fields.iter().filter_map(|field| {
if matches!(
field.kind,
Kind::Flatten { .. }
| Kind::ArgGroup { .. }
| Kind::Clause { .. }
| Kind::Subcommand { .. }
| Kind::Skip
) {
return None;
}
let selectors = field_selectors(field);
let ident = &field.ident;
let matches = match field.shape {
Shape::Bool => quote!(
(value == b"true" && __usage_s.#ident)
|| (value == b"false" && !__usage_s.#ident)
),
Shape::Count => quote!(
(value == b"true" && __usage_s.#ident > 0)
|| (value == b"false" && __usage_s.#ident == 0)
),
Shape::Optional | Shape::Required | Shape::Many => quote!(false),
};
Some(quote!(#(#selectors)|* => return ::std::option::Option::Some(#matches),))
});
let standing_match_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if let ::std::option::Option::Some(matches) =
<#ty as usage_argv::spec::CommandArgs>::standing_matches(
&__usage_s.#ident,
selector,
value,
)
{
return ::std::option::Option::Some(matches);
}
})
});
let standing_match_grouped = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup {
ty,
multiple,
optional,
..
} = &field.kind
else {
return None;
};
let ident = &field.ident;
let values = if *multiple || *optional {
quote!(__usage_s.#ident.iter())
} else {
quote!(::core::iter::once(&__usage_s.#ident))
};
Some(quote! {
let mut __usage_standing_group_recognized = false;
for __usage_member in #values {
match <#ty as usage_argv::spec::ArgGroup>::standing_matches(
__usage_member,
selector,
value,
) {
::std::option::Option::Some(true) => {
return ::std::option::Option::Some(true);
}
::std::option::Option::Some(false) => {
__usage_standing_group_recognized = true;
}
::std::option::Option::None => {}
}
}
if __usage_standing_group_recognized {
return ::std::option::Option::Some(false);
}
})
});
let standing_match_clause = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
let mut __usage_standing_clause_recognized = false;
for __usage_instance in &__usage_s.#ident {
match <#ty as usage_argv::spec::CommandArgs>::standing_matches(
__usage_instance,
selector,
value,
) {
::std::option::Option::Some(true) => {
return ::std::option::Option::Some(true);
}
::std::option::Option::Some(false) => {
__usage_standing_clause_recognized = true;
}
::std::option::Option::None => {}
}
}
if __usage_standing_clause_recognized {
return ::std::option::Option::Some(false);
}
})
});
let displace_arms = cli.fields.iter().filter_map(|field| {
if !matches!(field.kind, Kind::Flag { .. }) || !is_displaceable(cli, field) {
return None;
}
let selectors = field_selectors(field);
let statement = displace_statement(cli, field);
Some(quote! {
#(#selectors)|* => {
#statement
return true;
}
})
});
let displace_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if <#ty as usage_argv::spec::CommandArgs>::displace(
&mut partial.#ident,
selector,
) {
return true;
}
})
});
let displace_grouped = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
if <#ty as usage_argv::spec::ArgGroup>::displace(
&mut partial.#ident,
selector,
) {
return true;
}
})
});
let flags: Vec<&Field> = cli
.fields
.iter()
.filter(|field| matches!(field.kind, Kind::Flag { .. }))
.collect();
let event_arms = flags.iter().enumerate().map(|(i, field)| {
let key = key_ident("FLAG", Some(i));
let table = format_ident!("FLAG_{i}");
let selectors = field_selectors(field);
quote! {
#key if ::core::ptr::eq(*flag, &#table) => {
matches!(selector, #(#selectors)|*)
}
}
});
let event_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
if <#ty as usage_argv::spec::CommandArgs>::event_matches(event, selector) {
return true;
}
})
});
let event_grouped = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
if <#ty as usage_argv::spec::ArgGroup>::event_matches(event, selector) {
return true;
}
})
});
quote! {
#[allow(dead_code)]
pub fn canonical_selector(selector: &str) -> ::std::option::Option<&'static str> {
match selector {
#(#canonical_arms)*
_ => {}
}
#(#canonical_flattened)*
#(#canonical_clause)*
::std::option::Option::None
}
#[allow(dead_code)]
pub fn argument_state(
partial: &Partial,
selector: &str,
) -> ::std::option::Option<usage_argv::spec::ArgumentState> {
match selector {
#(#state_arms)*
_ => {}
}
#(#state_flattened)*
#(#state_grouped)*
#(#state_clause)*
::std::option::Option::None
}
#[allow(dead_code)]
pub fn argument_matches(
partial: &Partial,
selector: &str,
value: &[u8],
) -> ::std::option::Option<bool> {
match selector {
#(#match_arms)*
_ => {}
}
#(#match_flattened)*
#(#match_grouped)*
#(#match_clause)*
::std::option::Option::None
}
#[allow(dead_code)]
pub fn standing_state(
__usage_s: &#args,
selector: &str,
) -> ::std::option::Option<usage_argv::spec::ArgumentState> {
match selector {
#(#standing_state_arms)*
_ => {}
}
#(#standing_state_flattened)*
#(#standing_state_grouped)*
#(#standing_state_clause)*
::std::option::Option::None
}
#[allow(dead_code)]
pub fn standing_matches(
__usage_s: &#args,
selector: &str,
value: &[u8],
) -> ::std::option::Option<bool> {
match selector {
#(#standing_match_arms)*
_ => {}
}
#(#standing_match_flattened)*
#(#standing_match_grouped)*
#(#standing_match_clause)*
::std::option::Option::None
}
#[allow(dead_code)]
pub fn displace(partial: &mut Partial, selector: &str) -> bool {
match selector {
#(#displace_arms)*
_ => {}
}
#(#displace_flattened)*
#(#displace_grouped)*
false
}
#[allow(dead_code)]
pub fn event_matches(
event: &usage_argv::Event<'_, '_, '_>,
selector: &str,
) -> bool {
if let usage_argv::Event::Flag { flag, .. } = event {
let matched = match flag.key {
#(#event_arms)*
_ => false,
};
if matched {
return true;
}
}
#(#event_flattened)*
#(#event_grouped)*
false
}
}
}
fn partial_struct(cli: &Cli) -> TokenStream {
let sub = subcommand_parts(cli)
.map(|p| p.partial_fields)
.unwrap_or_default();
let fields = cli.fields.iter().filter_map(|f| {
if matches!(f.kind, Kind::Subcommand { .. } | Kind::Skip) {
return None;
}
if let Kind::Flatten { ty, .. } = &f.kind {
let ident = &f.ident;
return Some(quote! {
pub #ident: <#ty as usage_argv::spec::CommandArgs>::Partial,
});
}
if let Kind::ArgGroup { ty, .. } = &f.kind {
let ident = &f.ident;
return Some(quote! {
pub #ident: <#ty as usage_argv::spec::ArgGroup>::Partial,
});
}
if let Kind::Clause { ty, .. } = &f.kind {
let ident = &f.ident;
let current = format_ident!("__usage_current_{}", ident);
return Some(quote! {
pub #ident: ::std::vec::Vec<<#ty as usage_argv::spec::CommandArgs>::Partial>,
pub #current: <#ty as usage_argv::spec::CommandArgs>::Partial,
});
}
let ident = &f.ident;
let ty = match f.shape {
Shape::Bool => quote!(bool),
Shape::Count => {
let ty = &f.ty;
quote!(#ty)
}
Shape::Optional => quote!(::std::option::Option<::std::vec::Vec<u8>>),
Shape::Required => quote!(::std::vec::Vec<u8>),
Shape::Many => quote!(::std::vec::Vec<::std::vec::Vec<u8>>),
};
let given = format_ident!("__given_{}", ident);
let overridden = is_displaceable(cli, f).then(|| {
let overridden = format_ident!("__overridden_{}", ident);
quote!(pub #overridden: bool,)
});
let duplicated = rejects_duplicate(cli, f).then(|| {
let duplicated = format_ident!("__duplicated_{}", ident);
quote!(pub #duplicated: bool,)
});
let here = duplicates_per_level(cli, f).then(|| {
let here = format_ident!("__here_{}", ident);
quote!(pub #here: bool,)
});
let negated = has_negate(f).then(|| {
let negated = format_ident!("__negated_{}", ident);
quote!(pub #negated: bool,)
});
let mirrored = mirrored_global_ident(f).map(|mirrored| {
quote!(pub #mirrored: bool,)
});
let invalid_choice = tracks_invalid_choice(f).then(|| {
let invalid = format_ident!("__invalid_choice_{}", ident);
quote!(pub #invalid: bool,)
});
let deprecated_env = tracks_deprecated_env(f).then(|| {
let recorded = deprecated_env_ident(f);
quote!(pub #recorded: ::std::option::Option<&'static str>,)
});
Some(quote!(pub #ident: #ty, pub #given: bool, #overridden #duplicated #here #negated #mirrored #invalid_choice #deprecated_env))
});
quote! {
#[expect(clippy::pub_underscore_fields)]
pub struct Partial {
pub __usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
pub __usage_omit_own: bool,
#(#fields)*
#sub
}
}
}
struct Settings {
given: TokenStream,
bindings: TokenStream,
}
fn given_value(field: &Field) -> TokenStream {
let ident = &field.ident;
match field.shape {
Shape::Bool => quote! {
usage_argv::spec::SettingGiven::Bool(partial.#ident)
},
Shape::Count => quote! {
usage_argv::spec::SettingGiven::Int(
::std::convert::TryFrom::try_from(partial.#ident)
.unwrap_or(::std::primitive::i64::MAX),
)
},
Shape::Optional => quote! {
match ::std::str::from_utf8(partial.#ident.as_deref().unwrap_or_default()) {
::std::result::Result::Ok(__usage_text) => {
usage_argv::spec::SettingGiven::Text(
::std::string::ToString::to_string(__usage_text),
)
}
::std::result::Result::Err(_) => usage_argv::spec::SettingGiven::NotText,
}
},
Shape::Required => quote! {
match ::std::str::from_utf8(&partial.#ident) {
::std::result::Result::Ok(__usage_text) => {
usage_argv::spec::SettingGiven::Text(
::std::string::ToString::to_string(__usage_text),
)
}
::std::result::Result::Err(_) => usage_argv::spec::SettingGiven::NotText,
}
},
Shape::Many => quote! {
match partial
.#ident
.iter()
.map(|__usage_item| {
::std::str::from_utf8(__usage_item)
.ok()
.map(::std::string::ToString::to_string)
})
.collect::<::std::option::Option<::std::vec::Vec<_>>>()
{
::std::option::Option::Some(__usage_items) => {
usage_argv::spec::SettingGiven::List(__usage_items)
}
::std::option::Option::None => usage_argv::spec::SettingGiven::NotText,
}
},
}
}
fn own_bindings(bound: &[&Field]) -> Vec<TokenStream> {
bound
.iter()
.flat_map(|field| {
let key = field.setting.clone().unwrap_or_default();
let mut spellings = Vec::new();
if let Kind::Flag {
longs,
shorts,
negate,
..
} = &field.kind
{
spellings.extend(longs.iter().map(|long| format!("--{long}")));
spellings.extend(shorts.iter().map(|short| format!("-{short}")));
if let Some(negate) = negate {
spellings.push(format!("--{negate}"));
}
}
spellings.into_iter().map(move |flag| quote!((#flag, #key)))
})
.collect()
}
fn children(cli: &Cli) -> Vec<(TokenStream, TokenStream)> {
cli.fields
.iter()
.filter_map(|field| {
let ident = &field.ident;
match &field.kind {
Kind::Flatten { ty, .. } => Some((
quote!(<#ty as usage_argv::spec::CommandArgs>::SETTINGS_BINDINGS),
quote! {
<#ty as usage_argv::spec::CommandArgs>::settings_given(
&partial.#ident,
)
},
)),
Kind::Subcommand { ty, .. } => Some((
quote!(<#ty as usage_argv::spec::Subcommands>::SETTINGS_BINDINGS),
quote! {
<#ty as usage_argv::spec::Subcommands>::settings_given(
&partial.__usage_sub,
partial.__usage_selected,
)
},
)),
_ => None,
}
})
.collect()
}
fn joined_bindings(own: &[TokenStream], children: &[TokenStream]) -> TokenStream {
if children.is_empty() {
return quote!(&[#(#own),*]);
}
quote! {
{
const OWN: &[(&'static str, &'static str)] = &[#(#own),*];
const PARTS: &[&'static [(&'static str, &'static str)]] = &[OWN #(, #children)*];
const N: usize = OWN.len() #(+ #children.len())*;
const JOINED: [(&'static str, &'static str); N] =
usage_argv::spec::concat_bindings(PARTS);
&JOINED
}
}
}
fn settings(cli: &Cli) -> Option<Settings> {
let bound: Vec<&Field> = cli.fields.iter().filter(|f| f.setting.is_some()).collect();
let children = children(cli);
if bound.is_empty() && children.is_empty() {
return None;
}
let contributions = bound.iter().map(|field| {
let given = format_ident!("__given_{}", &field.ident);
let key = field.setting.as_deref().unwrap_or_default();
let value = given_value(field);
quote! {
if partial.#given {
__usage_given.push((#key, #value));
}
}
});
let from_children = children
.iter()
.map(|(_, given)| quote!(__usage_given.extend(#given);));
let given = quote! {
pub fn settings_given(
partial: &Partial,
) -> ::std::vec::Vec<(&'static str, usage_argv::spec::SettingGiven)> {
let mut __usage_given = ::std::vec::Vec::new();
#(#contributions)*
#(#from_children)*
__usage_given
}
};
let child_bindings: Vec<TokenStream> = children.into_iter().map(|(b, _)| b).collect();
let pairs = own_bindings(&bound);
let table = joined_bindings(&pairs, &child_bindings);
let bindings = quote! {
pub const SETTINGS_BINDINGS: &'static [(&'static str, &'static str)] = #table;
};
Some(Settings { given, bindings })
}
fn settings_layer(config: &TokenStream) -> TokenStream {
quote! {
pub fn settings_layer(partial: &Partial) -> #config::CliLayer {
let mut __usage_layer = #config::CliLayer::new(
::std::iter::empty::<(::std::string::String, ::std::string::String)>(),
);
for (__usage_key, __usage_given) in settings_given(partial) {
__usage_layer = match __usage_given {
usage_argv::spec::SettingGiven::Bool(__usage_value) => {
__usage_layer.with_value(__usage_key, #config::Value::Bool(__usage_value))
}
usage_argv::spec::SettingGiven::Int(__usage_value) => {
__usage_layer.with_value(__usage_key, #config::Value::Int(__usage_value))
}
usage_argv::spec::SettingGiven::Text(__usage_value) => {
__usage_layer
.with_value(__usage_key, #config::Value::String(__usage_value))
}
usage_argv::spec::SettingGiven::List(__usage_items) => __usage_layer.with_value(
__usage_key,
#config::Value::List(
__usage_items
.into_iter()
.map(#config::Value::String)
.collect(),
),
),
usage_argv::spec::SettingGiven::NotText => {
__usage_layer.with_unrepresentable(__usage_key)
}
};
}
__usage_layer
}
}
}
fn settings_guard(cli: &Cli) -> Option<TokenStream> {
let children = children(cli);
if children.is_empty() {
return None;
}
let checks = children.into_iter().map(|(bindings, _)| {
quote! {
const _: () = assert!(
#bindings.len() == 0,
"this command flattens or nests a group that binds a setting, and does not \
collect it: add `#[usage(settings)]` to the struct deriving `Cli`",
);
}
});
Some(quote!(#(#checks)*))
}
fn partial_defaults(cli: &Cli) -> TokenStream {
let sub_starts = subcommand_parts(cli)
.map(|p| p.partial_starts)
.unwrap_or_default();
let plain = cli.fields.iter().filter_map(|f| {
if matches!(f.kind, Kind::Subcommand { .. } | Kind::Skip) {
return None;
}
if let Kind::Flatten { ty, .. } = &f.kind {
let ident = &f.ident;
return Some(quote! {
#ident: <#ty as usage_argv::spec::CommandArgs>::start(),
});
}
if let Kind::ArgGroup { ty, .. } = &f.kind {
let ident = &f.ident;
return Some(quote! {
#ident: <#ty as usage_argv::spec::ArgGroup>::start(),
});
}
if let Kind::Clause { ty, .. } = &f.kind {
let ident = &f.ident;
let current = format_ident!("__usage_current_{}", ident);
return Some(quote! {
#ident: ::std::vec::Vec::new(),
#current: <#ty as usage_argv::spec::CommandArgs>::start(),
});
}
let ident = &f.ident;
let given = format_ident!("__given_{}", ident);
let overridden = is_displaceable(cli, f).then(|| {
let overridden = format_ident!("__overridden_{}", ident);
quote!(#overridden: false,)
});
let duplicated = rejects_duplicate(cli, f).then(|| {
let duplicated = format_ident!("__duplicated_{}", ident);
quote!(#duplicated: false,)
});
let here = duplicates_per_level(cli, f).then(|| {
let here = format_ident!("__here_{}", ident);
quote!(#here: false,)
});
let negated = has_negate(f).then(|| {
let negated = format_ident!("__negated_{}", ident);
quote!(#negated: false,)
});
let mirrored = mirrored_global_ident(f).map(|mirrored| quote!(#mirrored: false,));
let invalid_choice = tracks_invalid_choice(f).then(|| {
let invalid = format_ident!("__invalid_choice_{}", ident);
quote!(#invalid: false,)
});
let deprecated_env = tracks_deprecated_env(f).then(|| {
let recorded = deprecated_env_ident(f);
quote!(#recorded: ::std::option::Option::None,)
});
Some(quote! {
#ident: ::std::default::Default::default(),
#given: false,
#overridden
#duplicated
#here
#negated
#mirrored
#invalid_choice
#deprecated_env
})
});
quote! {
let mut partial = Partial {
__usage_view: ::std::option::Option::None,
__usage_omit_own: false,
#(#plain)*
#sub_starts
};
}
}
fn field_final(field: &Field, omitter: Option<&TokenStream>) -> TokenStream {
let ident = &field.ident;
let value = field_value(field, omitter);
quote!(#ident: #value)
}
fn field_value(field: &Field, omitter: Option<&TokenStream>) -> TokenStream {
let ident = &field.ident;
let given = format_ident!("__given_{}", ident);
let name = &field.name;
if matches!(field.kind, Kind::Skip) {
return quote!(::std::default::Default::default());
}
if let Kind::Flatten { ty, .. } = &field.kind {
return match omitter {
Some(omitter) => quote! {
<#ty as usage_argv::spec::ViewCommandArgs<#omitter>>::build_for_view(
partial.#ident,
)?
},
None => quote! {
<#ty as usage_argv::spec::CommandArgs>::build(partial.#ident)?
},
};
}
if let Kind::Clause { ty, separator } = &field.kind {
let current = format_ident!("__usage_current_{}", ident);
let finish_current = if separator.is_some() {
quote!(__usage_instances.push(partial.#current);)
} else {
quote! {
if <#ty as usage_argv::spec::CommandArgs>::any_given(&partial.#current)
.is_some()
{
__usage_instances.push(partial.#current);
}
}
};
return quote! {
{
let mut __usage_instances = partial.#ident;
#finish_current
__usage_instances
.into_iter()
.map(<#ty as usage_argv::spec::CommandArgs>::build)
.collect::<::std::result::Result<::std::vec::Vec<_>, _>>()?
}
};
}
if let Kind::ArgGroup {
ty,
optional,
multiple,
} = &field.kind
{
let group = quote!(<#ty as usage_argv::spec::ArgGroup>);
if *multiple {
return quote!(#group::try_build_many(&partial.#ident)?);
}
return if *optional {
quote!(#group::try_build(&partial.#ident)?)
} else {
quote! {
match #group::try_build(&partial.#ident)? {
::std::option::Option::Some(__usage_member) => __usage_member,
::std::option::Option::None => {
return ::std::result::Result::Err(
usage_argv::Error::MissingGroup {
group: #group::NAME,
members: #group::MEMBERS,
},
);
}
}
}
};
}
let active = view_field_active(field);
let ty = &field.ty;
let finished = |value: TokenStream| {
let Some(omitter) = omitter else {
return value;
};
quote! {
{
let __usage_view = partial.__usage_view;
if partial.__usage_omit_own || !(#active) {
<#omitter as usage_argv::spec::Omitted<#ty>>::omitted()
} else {
#value
}
}
}
};
let Some(ty) = field.value_ty.as_ref() else {
return finished(quote!(partial.#ident));
};
let rendered = rendered_path(ty);
let is_std_string = matches!(
rendered.as_str(),
"String" | "std::string::String" | "::std::string::String" | "alloc::string::String"
);
let os_target = match rendered.as_str() {
"PathBuf" | "std::path::PathBuf" | "::std::path::PathBuf" => {
Some(quote!(::std::path::PathBuf::from))
}
"OsString" | "std::ffi::OsString" | "::std::ffi::OsString" => {
Some(quote!(::std::convert::identity))
}
_ => None,
};
if let Some(build) = os_target {
let one = |value: TokenStream| {
quote! {
match usage_argv::os_string_from_bytes(#value) {
::std::result::Result::Ok(__usage_os) => #build(__usage_os),
::std::result::Result::Err(__usage_bytes) => {
return ::std::result::Result::Err(
usage_argv::invalid_os_value(#name, __usage_bytes),
);
}
}
}
};
let converted = one;
return match field.shape {
Shape::Bool | Shape::Count => finished(quote!(partial.#ident)),
Shape::Required => {
let value = converted(quote!(partial.#ident));
finished(value)
}
Shape::Optional if field.optional_value_type => {
let value = converted(quote!(__usage_value));
finished(quote! {
match partial.#ident {
::std::option::Option::Some(__usage_value) => {
::std::option::Option::Some(::std::option::Option::Some(#value))
}
::std::option::Option::None if partial.#given => {
::std::option::Option::Some(::std::option::Option::None)
}
::std::option::Option::None => ::std::option::Option::None,
}
})
}
Shape::Optional => {
let value = converted(quote!(__usage_value));
finished(quote! {
match partial.#ident {
::std::option::Option::Some(__usage_value) => {
::std::option::Option::Some(#value)
}
::std::option::Option::None => ::std::option::Option::None,
}
})
}
Shape::Many => {
let collected = quote! {
usage_argv::os_values(partial.#ident, #name)?
};
if field.optional_collection {
let given = format_ident!("__given_{}", ident);
let defaulted = !field.default.is_empty();
finished(quote! {
if partial.#given || #defaulted || !partial.#ident.is_empty() {
::std::option::Option::Some(#collected)
} else {
::std::option::Option::None
}
})
} else {
finished(collected)
}
}
};
}
let converted = |value: TokenStream| {
let text = quote! {
match ::std::string::String::from_utf8(#value) {
::std::result::Result::Ok(text) => text,
::std::result::Result::Err(bad) => {
return ::std::result::Result::Err(
usage_argv::invalid_utf8_value(#name, bad),
);
}
}
};
if field.value_enum {
return quote! {{
let __usage_text = #text;
match <#ty as usage_argv::spec::ValueEnum>::from_choice(&__usage_text) {
::std::option::Option::Some(parsed) => parsed,
::std::option::Option::None => {
return ::std::result::Result::Err(
usage_argv::invalid_choice_value(#name, __usage_text),
);
}
}
}};
}
if is_std_string {
return text;
}
quote! {{
let __usage_text = #text;
match ::std::str::FromStr::from_str(&__usage_text) {
::std::result::Result::Ok(parsed) => parsed,
::std::result::Result::Err(reason) => {
return ::std::result::Result::Err(
usage_argv::invalid_parsed_value(#name, __usage_text, &reason),
);
}
}
}}
};
match field.shape {
Shape::Bool | Shape::Count => finished(quote!(partial.#ident)),
Shape::Required => {
let one = converted(quote!(partial.#ident));
finished(one)
}
Shape::Optional if field.optional_value_type => {
let one = converted(quote!(__usage_value));
finished(quote! {
match partial.#ident {
::std::option::Option::Some(__usage_value) => {
::std::option::Option::Some(::std::option::Option::Some(#one))
}
::std::option::Option::None if partial.#given => {
::std::option::Option::Some(::std::option::Option::None)
}
::std::option::Option::None => ::std::option::Option::None,
}
})
}
Shape::Optional => {
let one = converted(quote!(__usage_value));
finished(quote! {
match partial.#ident {
::std::option::Option::Some(__usage_value) => {
::std::option::Option::Some(#one)
}
::std::option::Option::None => ::std::option::Option::None,
}
})
}
Shape::Many => {
let collected = if field.value_enum {
quote!(usage_argv::spec::choice_values::<#ty>(partial.#ident, #name)?)
} else if is_std_string {
quote!(usage_argv::utf8_values(partial.#ident, #name)?)
} else {
quote!(usage_argv::parsed_values::<#ty>(partial.#ident, #name)?)
};
if field.optional_collection {
let given = format_ident!("__given_{}", ident);
let defaulted = !field.default.is_empty();
finished(quote! {
if partial.#given || #defaulted || !partial.#ident.is_empty() {
::std::option::Option::Some(#collected)
} else {
::std::option::Option::None
}
})
} else {
finished(collected)
}
}
}
}
fn merge_present(field: &Field) -> TokenStream {
let ident = &field.ident;
let given = format_ident!("__given_{}", ident);
match field.shape {
Shape::Bool => quote!(partial.#given || partial.#ident),
Shape::Count => {
let ty = &field.ty;
quote!(partial.#given || partial.#ident != <#ty as ::std::default::Default>::default())
}
Shape::Optional => quote!(partial.#given || partial.#ident.is_some()),
Shape::Required | Shape::Many => quote!(partial.#given || !partial.#ident.is_empty()),
}
}
fn merge_fn(cli: &Cli) -> TokenStream {
let ident = &cli.ident;
let merges = cli.fields.iter().filter_map(|field| {
let field_ident = &field.ident;
match &field.kind {
Kind::Skip => None,
Kind::Flatten { ty, .. } => Some(quote! {
<#ty as usage_argv::spec::CommandArgs>::merge(
partial.#field_ident,
&mut __usage_standing.#field_ident,
)?;
}),
Kind::ArgGroup {
ty,
optional,
multiple,
} => {
let group = quote!(<#ty as usage_argv::spec::ArgGroup>);
if *multiple {
return Some(quote! {
if #group::any_given(&partial.#field_ident).is_some() {
__usage_standing.#field_ident =
#group::try_build_many(&partial.#field_ident)?;
}
});
}
let selected = if *optional {
quote!(::std::option::Option::Some(__usage_member))
} else {
quote!(__usage_member)
};
Some(quote! {
if let ::std::option::Option::Some(__usage_member) =
#group::try_build(&partial.#field_ident)?
{
__usage_standing.#field_ident = #selected;
}
})
}
Kind::Subcommand { ty, optional } => Some(if *optional {
quote! {
if let ::std::option::Option::Some(__usage_at) = partial.__usage_selected {
match &mut __usage_standing.#field_ident {
::std::option::Option::Some(__usage_existing) => {
<#ty as usage_argv::spec::Subcommands>::merge_into(
partial.__usage_sub,
__usage_at,
__usage_existing,
)?;
}
__usage_slot => {
*__usage_slot =
<#ty as usage_argv::spec::Subcommands>::select(
partial.__usage_sub,
__usage_at,
)?;
}
}
}
}
} else {
quote! {
if let ::std::option::Option::Some(__usage_at) = partial.__usage_selected {
<#ty as usage_argv::spec::Subcommands>::merge_into(
partial.__usage_sub,
__usage_at,
&mut __usage_standing.#field_ident,
)?;
}
}
}),
Kind::Clause { ty, .. } => {
let current = format_ident!("__usage_current_{}", field_ident);
let value = field_value(field, None);
Some(quote! {
if !partial.#field_ident.is_empty()
|| <#ty as usage_argv::spec::CommandArgs>::any_given(&partial.#current).is_some()
{
__usage_standing.#field_ident = #value;
}
})
}
Kind::Flag { .. } | Kind::Arg { .. } => {
let present = merge_present(field);
let value = field_value(field, None);
Some(quote! {
if #present {
__usage_standing.#field_ident = #value;
}
})
}
}
});
quote! {
pub fn merge<'t, 'v>(
partial: Partial,
__usage_standing: &mut #ident,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
let _ = (&partial, &*__usage_standing);
#(#merges)*
::std::result::Result::Ok(())
}
}
}
fn any_standing_fn(cli: &Cli) -> TokenStream {
let held = cli.fields.iter().filter_map(|field| {
if let Kind::Flatten { ty, .. } = &field.kind {
let ident = &field.ident;
return Some(quote! {
if let ::std::option::Option::Some(__usage_name) =
<#ty as usage_argv::spec::CommandArgs>::any_standing(&__usage_s.#ident)
{
return ::std::option::Option::Some(__usage_name);
}
});
}
let present = standing_presence(field)?;
let name = &field.name;
Some(quote! {
if #present {
return ::std::option::Option::Some(#name);
}
})
});
quote! {
fn any_standing(__usage_s: &Self) -> ::std::option::Option<&'static str> {
let _ = __usage_s;
#(#held)*
::std::option::Option::None
}
}
}
fn reset_to_default(field: &Field) -> TokenStream {
let ident = &field.ident;
let cleared = match field.shape {
Shape::Many => quote!(partial.#ident.clear();),
_ => quote!(partial.#ident = ::std::default::Default::default();),
};
if !field.has_default() {
return cleared;
}
if let Some(default_fn) = &field.default_fn {
let value_ty = field
.value_ty
.as_ref()
.expect("a computed default belongs to a value-taking field");
let bytes = quote!({
let __usage_default: #value_ty = #default_fn();
::std::string::ToString::to_string(&__usage_default).into_bytes()
});
return match field.shape {
Shape::Optional => quote! {
partial.#ident = ::std::option::Option::Some(#bytes);
},
Shape::Required => quote!(partial.#ident = #bytes;),
_ => cleared,
};
}
if let Some(value) = &field.default_value_t {
let value_ty = field
.value_ty
.as_ref()
.expect("a typed default belongs to a value-taking field");
let bytes = quote!({
let __usage_default: #value_ty = #value;
::std::string::ToString::to_string(&__usage_default).into_bytes()
});
return match field.shape {
Shape::Optional => quote! {
partial.#ident = ::std::option::Option::Some(#bytes);
},
Shape::Required => quote!(partial.#ident = #bytes;),
_ => cleared,
};
}
assign_literal(field, &field.default[0], field.default.as_slice())
}
fn assign_literal(field: &Field, first: &str, all: &[String]) -> TokenStream {
let ident = &field.ident;
let cleared = match field.shape {
Shape::Many => quote!(partial.#ident.clear();),
_ => quote!(),
};
match field.shape {
Shape::Bool => {
let on = matches!(first, "1" | "true" | "True" | "TRUE");
quote!(partial.#ident = #on;)
}
Shape::Optional => quote! {
partial.#ident = ::std::option::Option::Some(#first.as_bytes().to_vec());
},
Shape::Required => quote!(partial.#ident = #first.as_bytes().to_vec();),
Shape::Count => quote!(partial.#ident = ::std::default::Default::default();),
Shape::Many => {
let defaults = all;
match field.delimiter {
Some(delimiter) => {
let byte = u8::try_from(u32::from(delimiter))
.expect("the model rejects non-ASCII delimiters");
quote! {
#cleared
#(
for part in #defaults.as_bytes().split(|b| *b == #byte) {
partial.#ident.push(part.to_vec());
}
)*
}
}
None => quote! {
#cleared
#(partial.#ident.push(#defaults.as_bytes().to_vec());)*
},
}
}
}
}
#[derive(Clone, Copy, PartialEq)]
enum Lookup {
Module,
Standing,
}
impl Lookup {
fn state(self) -> TokenStream {
match self {
Lookup::Module => quote!(argument_state),
Lookup::Standing => quote!(__usage_argument_state),
}
}
fn matches(self) -> TokenStream {
match self {
Lookup::Module => quote!(argument_matches),
Lookup::Standing => quote!(__usage_argument_matches),
}
}
}
fn default_if_predicate(cli: &Cli, condition: &ConditionalDefault, lookup: Lookup) -> TokenStream {
let Some(other) = cli.field_for_selector(&condition.selector) else {
let selector = &condition.selector;
let state = lookup.state();
let matches = lookup.matches();
return match &condition.when {
None => quote!(
#state(partial, #selector).is_some_and(|state| state.given)
),
Some(when) => quote!(
#matches(partial, #selector, #when.as_bytes())
== ::std::option::Option::Some(true)
),
};
};
let other_given = policy_given(other);
let ident = &other.ident;
match &condition.when {
None => quote!(#other_given),
Some(when) => {
let matches = match other.shape {
Shape::Optional => quote!(
partial.#ident.as_deref().is_some_and(|v| v == #when.as_bytes())
),
Shape::Required => quote!(partial.#ident.as_slice() == #when.as_bytes()),
Shape::Many => quote!(
partial.#ident.iter().any(|v| v.as_slice() == #when.as_bytes())
),
Shape::Bool => match when.as_str() {
"true" => quote!(partial.#ident),
"false" => quote!(!partial.#ident),
_ => quote!(false),
},
Shape::Count => match when.as_str() {
"true" => quote!(partial.#ident > 0),
"false" => quote!(partial.#ident == 0),
_ => quote!(false),
},
};
quote!(#other_given && #matches)
}
}
}
fn default_if_would_apply(cli: &Cli, field: &Field, lookup: Lookup) -> Option<TokenStream> {
if field.default_if.is_empty() {
return None;
}
let preds: Vec<TokenStream> = field
.default_if
.iter()
.map(|condition| default_if_predicate(cli, condition, lookup))
.collect();
Some(quote!(#(#preds)||*))
}
fn opaque_apply_arm(cli: &Cli, ident: &syn::Ident, trait_path: &TokenStream) -> TokenStream {
let reverse_displacements = cli.fields.iter().flat_map(|field| {
field
.overrides
.iter()
.filter(|selector| cli.field_for_selector(selector).is_none())
.map(move |selector| {
let statement = displace_statement(cli, field);
quote! {
if #trait_path::event_matches(event, #selector) {
#statement
}
}
})
});
quote! {
if #trait_path::apply(&mut partial.#ident, event) {
#(#reverse_displacements)*
return true;
}
}
}
fn apply_fn(cli: &Cli) -> TokenStream {
let route = subcommand_parts(cli).map(|p| p.route).unwrap_or_default();
let per_level_resets = reset_per_level(cli);
let flattened: Vec<TokenStream> = cli
.fields
.iter()
.filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
Some(opaque_apply_arm(
cli,
&f.ident,
"e!(<#ty as usage_argv::spec::CommandArgs>),
))
})
.collect();
let grouped: Vec<TokenStream> = cli
.fields
.iter()
.filter_map(|f| {
let Kind::ArgGroup { ty, .. } = &f.kind else {
return None;
};
Some(opaque_apply_arm(
cli,
&f.ident,
"e!(<#ty as usage_argv::spec::ArgGroup>),
))
})
.collect();
let clauses: Vec<TokenStream> = cli
.fields
.iter()
.filter_map(|f| {
let Kind::Clause { ty, .. } = &f.kind else {
return None;
};
let ident = &f.ident;
let current = format_ident!("__usage_current_{}", ident);
Some(quote! {
if let usage_argv::Event::ClauseSeparator { .. } = event {
let __usage_finished = ::core::mem::replace(
&mut partial.#current,
<#ty as usage_argv::spec::CommandArgs>::start(),
);
partial.#ident.push(__usage_finished);
return true;
}
if <#ty as usage_argv::spec::CommandArgs>::apply(&mut partial.#current, event) {
return true;
}
})
})
.collect();
let mirrored_flattened = cli.fields.iter().filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
let ident = &f.ident;
Some(quote! {
if <#ty as usage_argv::spec::CommandArgs>::apply_mirrored_global(
&mut partial.#ident,
event,
) {
return true;
}
})
});
let flags: Vec<&Field> = cli
.fields
.iter()
.filter(|f| matches!(f.kind, Kind::Flag { .. }))
.collect();
let args: Vec<&Field> = cli
.fields
.iter()
.filter(|f| matches!(f.kind, Kind::Arg { .. }))
.collect();
let flag_arms = flags.iter().enumerate().map(|(i, f)| flag_arm(cli, i, f));
let mirrored_flag_arms = flags.iter().enumerate().filter_map(|(i, field)| {
if !matches!(field.kind, Kind::Flag { global: true, .. }) {
return None;
}
let key = key_ident("FLAG", Some(i));
let table = format_ident!("FLAG_{i}");
let body = flag_binding_body(field);
let given = format_ident!("__given_{}", field.ident);
let mirrored = mirrored_global_ident(field).map(|mirrored| {
quote! {
if !partial.#given {
partial.#mirrored = true;
}
}
});
Some(quote! {
#key if ::core::ptr::eq(*flag, &#table) => {
#body
#mirrored
partial.#given = true;
true
}
})
});
let arg_arms = args.iter().enumerate().map(|(i, f)| arg_arm(i, f));
quote! {
pub fn apply(
partial: &mut Partial,
event: &usage_argv::Event<'_, '_, '_>,
) -> bool {
use usage_argv::Event;
#route
#(#flattened)*
#(#grouped)*
#(#clauses)*
match event {
Event::Flag { flag, value, negated } => {
let (value, negated) = (*value, *negated);
let _ = (value, negated);
match flag.key {
#(#flag_arms)*
_ => false,
}
}
Event::Arg {
arg,
value,
delimit,
} => {
let (value, delimit) = (*value, *delimit);
let _ = (value, delimit);
match arg.key {
#(#arg_arms)*
_ => false,
}
}
Event::Command(_) => {
#per_level_resets
false
}
Event::External { .. } => false,
Event::ClauseSeparator { .. } => false,
}
}
pub fn apply_mirrored_global(
partial: &mut Partial,
event: &usage_argv::Event<'_, '_, '_>,
) -> bool {
#(#mirrored_flattened)*
match event {
usage_argv::Event::Flag { flag, value, negated } => {
let (value, negated) = (*value, *negated);
let _ = (value, negated);
match flag.key {
#(#mirrored_flag_arms)*
_ => false,
}
}
_ => false,
}
}
}
}
struct SubcommandParts {
commands: TokenStream,
default: TokenStream,
external: TokenStream,
metas: TokenStream,
partial_fields: TokenStream,
partial_starts: TokenStream,
route: TokenStream,
check: TokenStream,
build: TokenStream,
view_build: TokenStream,
default_view_build: TokenStream,
}
fn subcommand_parts(cli: &Cli) -> Option<SubcommandParts> {
let (field, ty) = cli.fields.iter().find_map(|f| match &f.kind {
Kind::Subcommand { ty, .. } => Some((f, ty)),
_ => None,
})?;
let ident = &field.ident;
let optional = matches!(&field.kind, Kind::Subcommand { optional: true, .. });
let command_table = if cli.composable {
quote!(COMMAND)
} else {
quote!(ROOT)
};
let selected = |omitter: Option<TokenStream>| {
let select = match omitter {
Some(omitter) => quote! {
<#ty as usage_argv::spec::ViewSubcommands<#omitter>>::select_for_view(
partial.__usage_sub,
__usage_at,
)?
},
None => quote! {
<#ty as usage_argv::spec::Subcommands>::select(
partial.__usage_sub,
__usage_at,
)?
},
};
quote! {
match partial.__usage_selected {
::std::option::Option::Some(__usage_at) => #select,
::std::option::Option::None => ::std::option::Option::None,
}
}
};
let build = |omitter: Option<TokenStream>| {
let selected = selected(omitter);
if optional {
quote!(#ident: #selected,)
} else {
quote! {
#ident: match #selected {
::std::option::Option::Some(__usage_cmd) => __usage_cmd,
::std::option::Option::None => {
return ::std::result::Result::Err(
usage_argv::Error::MissingSubcommand,
);
}
},
}
}
};
Some(SubcommandParts {
commands: quote!(subcommands: <#ty as usage_argv::spec::Subcommands>::COMMANDS,),
external: quote!(
external_subcommand: <#ty as usage_argv::spec::Subcommands>::HAS_EXTERNAL,
),
default: match cli.default_subcommand.as_deref() {
::std::option::Option::Some(name) => quote! {
default_subcommand: ::std::option::Option::Some(
usage_argv::find_subcommand(
<#ty as usage_argv::spec::Subcommands>::COMMANDS,
#name,
),
),
},
::std::option::Option::None => TokenStream::new(),
},
metas: quote!(subcommands: <#ty as usage_argv::spec::Subcommands>::METAS,),
partial_fields: quote! {
pub __usage_sub: <#ty as usage_argv::spec::Subcommands>::Partial,
pub __usage_selected: ::std::option::Option<usize>,
},
partial_starts: quote! {
__usage_sub: ::std::default::Default::default(),
__usage_selected: ::std::option::Option::None,
},
route: quote! {
if let usage_argv::Event::Command(__usage_cmd) = event {
if let ::std::option::Option::Some(__usage_named) =
<#ty as usage_argv::spec::Subcommands>::COMMANDS
.iter()
.position(|candidate| ::core::ptr::eq(*candidate, *__usage_cmd))
{
let __usage_at = if <#ty as usage_argv::spec::Subcommands>::HAS_EXTERNAL {
<#ty as usage_argv::spec::Subcommands>::VARIANT_OF[__usage_named]
} else {
__usage_named
};
partial.__usage_selected = ::std::option::Option::Some(__usage_at);
<#ty as usage_argv::spec::Subcommands>::begin(
&mut partial.__usage_sub,
__usage_at,
);
}
}
if let usage_argv::Event::External { .. } = event {
if let ::std::option::Option::Some(__usage_at) =
<#ty as usage_argv::spec::Subcommands>::EXTERNAL
{
partial.__usage_selected = ::std::option::Option::Some(__usage_at);
<#ty as usage_argv::spec::Subcommands>::begin(
&mut partial.__usage_sub,
__usage_at,
);
}
}
let __usage_routed = <#ty as usage_argv::spec::Subcommands>::apply(
&mut partial.__usage_sub,
partial.__usage_selected,
event,
);
if __usage_routed {
if let usage_argv::Event::Flag { flag, value, negated } = event {
if let ::std::option::Option::Some(__usage_global) = #command_table
.flags
.iter()
.copied()
.find(|candidate| {
candidate.global
&& candidate.key != flag.key
&& candidate.binding_key != 0
&& candidate.binding_key == flag.binding_key
&& candidate.binding_type == flag.binding_type
&& candidate.name == flag.name
})
{
let __usage_global_event = usage_argv::Event::Flag {
flag: __usage_global,
value: *value,
negated: *negated,
};
apply_mirrored_global(partial, &__usage_global_event);
}
}
return true;
}
},
check: {
let standing = standing_ident(field);
quote! {
if let ::std::option::Option::Some(__usage_at) = partial.__usage_selected {
match (#standing, __usage_view) {
(::std::option::Option::Some(__usage_s), _) => {
<#ty as usage_argv::spec::Subcommands>::check_update(
&mut partial.__usage_sub,
__usage_at,
__usage_s,
)?;
}
(
::std::option::Option::None,
::std::option::Option::Some(__usage_view),
) => {
<#ty as usage_argv::spec::Subcommands>::check_for_view_path(
&mut partial.__usage_sub,
__usage_at,
__usage_view.root.split_ascii_whitespace().count(),
)?;
}
(::std::option::Option::None, ::std::option::Option::None) => {
<#ty as usage_argv::spec::Subcommands>::check(
&mut partial.__usage_sub,
__usage_at,
)?;
}
}
}
}
},
build: build(None),
view_build: build(Some(quote!(__UsageOmitter))),
default_view_build: build(Some(quote!(usage_argv::spec::DefaultViewOmitter))),
})
}
pub fn emit_args(cli: &Cli) -> TokenStream {
let ident = &cli.ident;
let runtime = runtime_path();
let dispatch = emit_command_dispatch(cli, &runtime);
let validation = validation_path();
let validation_import = cli
.fields
.iter()
.any(|field| field.validate.is_some())
.then(|| quote!(use #validation as usage_validation;));
let presence = presence_methods(cli);
let any_standing = any_standing_fn(cli);
let standing_locals = standing_locals(cli);
let argument_state_standing = argument_state_standing(cli);
let apply_defaults = {
let defaults = declared_defaults(cli, true);
quote!(#standing_locals #argument_state_standing #defaults)
};
let apply_env = {
let env = env_fallbacks(cli, true);
quote!(#standing_locals #env)
};
let parts = settings(cli);
let settings_defs = parts.as_ref().map(|s| {
let given = &s.given;
let bindings = &s.bindings;
quote!(#given #bindings)
});
let settings_impl = parts.as_ref().map(|_| {
quote! {
const SETTINGS_BINDINGS: &'static [(&'static str, &'static str)] =
SETTINGS_BINDINGS;
fn settings_given(
partial: &Self::Partial,
) -> ::std::vec::Vec<(&'static str, usage_argv::spec::SettingGiven)> {
settings_given(partial)
}
}
});
let command_key = key_ident("COMMAND", None);
let restart_token = option_str(cli.restart_token.as_deref());
let mount = option_str(cli.mount.as_deref());
let OutputTokens {
decls: output_schema_decls,
outputs,
select,
exit_codes,
} = output_tokens(cli);
let flatten_checks = flatten_checks(cli);
let subcommand_required = cli.fields.iter().any(|f| {
matches!(
f.kind,
Kind::Subcommand {
optional: false,
..
}
)
});
let subcommand_help_heading = option_str(cli.subcommand_help_heading.as_deref());
let subcommand_value_name = option_str(cli.subcommand_value_name.as_deref());
let next_line_help = cli.next_line_help;
let flatten_help = cli.flatten_help;
let term_width = option_usize(cli.term_width);
let max_term_width = option_usize(cli.max_term_width);
let unknown_flags = unknown_flags_tokens(cli);
let arg_required_else_help = cli.arg_required_else_help;
let dont_delimit_trailing_values = cli.dont_delimit_trailing_values;
let args_override_self = cli.args_override_self;
let subcommand_negates_reqs = cli.subcommand_negates_reqs;
let args_conflicts_with_subcommands = cli.args_conflicts_with_subcommands;
let subcommand_precedence_over_arg = cli.subcommand_precedence_over_arg;
let allow_missing_positional = cli.allow_missing_positional;
let disable_help_flag = cli.disable_help_flag;
let disable_help_subcommand = cli.disable_help_subcommand;
let disable_version_flag = cli.disable_version_flag;
let before_help = option_expr(cli.before_help.as_ref());
let before_long_help = option_expr(cli.before_long_help.as_ref());
let after_help = option_expr(cli.after_help.as_ref());
let after_long_help = option_expr(cli.after_long_help.as_ref());
let examples = examples_table(&cli.examples);
let headings = headings_table(&cli.headings);
let flags: Vec<&Field> = cli
.fields
.iter()
.filter(|f| matches!(f.kind, Kind::Flag { .. }))
.collect();
let args: Vec<&Field> = cli
.fields
.iter()
.filter(|f| matches!(f.kind, Kind::Arg { .. }))
.collect();
let keys = key_consts(&cli.fingerprint, flags.len(), args.len());
let flag_tables = flags.iter().enumerate().map(|(i, f)| flag_table(i, f));
let arg_tables = args.iter().enumerate().map(|(i, f)| arg_table(i, f));
let flag_metas = flags
.iter()
.enumerate()
.map(|(i, f)| flag_meta(cli, i, f, &cli.ident));
let arg_metas = args
.iter()
.enumerate()
.map(|(i, f)| arg_meta(cli, i, f, &cli.ident));
let clause_field = cli.fields.iter().find_map(|field| match &field.kind {
Kind::Clause { ty, separator } => Some((field, ty, separator)),
_ => None,
});
let clause_table = clause_field.map_or_else(
|| quote!(::core::option::Option::None),
|(field, ty, separator)| {
let name = proc_macro2::Literal::string(&field.name);
let separator = separator.as_ref().map_or_else(
|| quote!(::core::option::Option::None),
|value| {
let value = proc_macro2::Literal::byte_string(value.as_bytes());
quote!(::core::option::Option::Some(#value))
},
);
quote!(::core::option::Option::Some(usage_argv::Clause {
key: 0, name: #name, separator: #separator,
flags: <#ty as usage_argv::spec::CommandArgs>::COMMAND.flags,
args: <#ty as usage_argv::spec::CommandArgs>::COMMAND.args,
}))
},
);
let clause_meta = clause_field.map_or_else(
|| quote!(::core::option::Option::None),
|(field, ty, separator)| {
let name = proc_macro2::Literal::string(&field.name);
let separator = option_str(separator.as_deref());
quote!(::core::option::Option::Some(usage_argv::spec::ClauseMeta {
name: #name, separator: #separator, help: ::core::option::Option::None,
long_help: ::core::option::Option::None,
flags: <#ty as usage_argv::spec::CommandArgs>::META.flags,
args: <#ty as usage_argv::spec::CommandArgs>::META.args,
canonical_selector: <#ty as usage_argv::spec::CommandArgs>::canonical_selector,
}))
},
);
let implicit_clause_assertions = clause_field
.filter(|(_, _, separator)| separator.is_none())
.map(|(_, ty, _)| {
quote! {
const _: () = {
const INNER: &usage_argv::Command<'static> =
<#ty as usage_argv::spec::CommandArgs>::COMMAND;
assert!(INNER.args.len() == 1,
"an implicit clause needs exactly one positional argument");
assert!(INNER.args[0].required && !INNER.args[0].var,
"an implicit clause positional must be required and non-variadic");
assert!(INNER.subcommands.is_empty(),
"an implicit clause cannot contain subcommands");
assert!(INNER.args[0].sigil.is_none(),
"an implicit clause positional cannot use a sigil");
};
}
});
let tables = tables(cli);
let table_decls = &tables.decls;
let meta_table_decls = &tables.meta_decls;
let (group_meta_decl, group_meta_table_ref) = group_meta_table(cli);
let flag_table_ref = &tables.flags;
let arg_table_ref = &tables.args;
let flag_meta_table_ref = &tables.flag_metas;
let arg_meta_table_ref = &tables.arg_metas;
let flatten_group_table_ref = &tables.flatten_groups;
let name = &cli.name;
let aliases = cli.aliases.iter().chain(&cli.hidden_aliases);
let hidden_aliases = &cli.hidden_aliases;
let about = cli
.about_attr
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(|| option_str(cli.about.as_deref()));
let long_about = cli
.long_about_attr
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(|| option_str(cli.long_about.as_deref()));
let deprecated = option_str(cli.deprecated.as_deref());
let deprecated_warn_at = option_str(cli.deprecated_warn_at.as_deref());
let deprecated_remove_at = option_str(cli.deprecated_remove_at.as_deref());
let surface = option_str(cli.surface.as_deref());
let available_if = &cli.available_if;
let partial = partial_struct(cli);
let argument_lookup = argument_lookup_functions(cli);
let deprecations = deprecations_fn(cli);
let defaults = partial_defaults(cli);
let apply = apply_fn(cli);
let post = post_binding(cli);
let merge = merge_fn(cli);
let parts = subcommand_parts(cli);
let sub_commands = parts
.as_ref()
.map(|p| p.commands.clone())
.unwrap_or_default();
let sub_external = parts
.as_ref()
.map(|p| p.external.clone())
.unwrap_or_default();
let sub_metas = parts.as_ref().map(|p| p.metas.clone()).unwrap_or_default();
let sub_build = parts.as_ref().map(|p| p.build.clone()).unwrap_or_default();
let sub_view_build = parts
.as_ref()
.map(|p| p.view_build.clone())
.unwrap_or_default();
let field_finals: Vec<_> = cli
.fields
.iter()
.filter(|f| !matches!(f.kind, Kind::Subcommand { .. }))
.map(|field| field_final(field, None))
.collect();
let built = built_value(cli, &sub_build, &field_finals);
let view_omitter = quote!(__UsageOmitter);
let view_field_finals: Vec<_> = cli
.fields
.iter()
.filter(|f| !matches!(f.kind, Kind::Subcommand { .. }))
.map(|field| field_final(field, Some(&view_omitter)))
.collect();
let built_for_view = built_value(cli, &sub_view_build, &view_field_finals);
let view_bounds: Vec<_> = cli
.fields
.iter()
.filter_map(|field| match &field.kind {
Kind::Flatten { ty, .. } => {
Some(quote!(#ty: usage_argv::spec::ViewCommandArgs<__UsageOmitter>))
}
Kind::Subcommand { ty, .. } => {
Some(quote!(#ty: usage_argv::spec::ViewSubcommands<__UsageOmitter>))
}
Kind::ArgGroup { .. } => None,
_ => {
let ty = &field.ty;
Some(quote!(__UsageOmitter: usage_argv::spec::Omitted<#ty>))
}
})
.collect();
let view_where = (!view_bounds.is_empty()).then(|| quote!(where #(#view_bounds,)*));
let view_path_methods = cli
.fields
.iter()
.find_map(|field| {
let Kind::Subcommand { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
fn apply_env_for_view_path(
partial: &mut Self::Partial,
remaining_descendants: usize,
) {
if remaining_descendants == 0 {
<Self as usage_argv::spec::CommandArgs>::apply_env(partial);
} else if let ::std::option::Option::Some(__usage_at) =
partial.__usage_selected
{
<#ty as usage_argv::spec::Subcommands>::apply_env_for_view_path(
&mut partial.__usage_sub,
::std::option::Option::Some(__usage_at),
remaining_descendants,
);
}
}
fn deprecations_for_view_path(
partial: &Self::Partial,
remaining_descendants: usize,
out: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) {
if remaining_descendants == 0 {
<Self as usage_argv::spec::CommandArgs>::deprecations(partial, out);
} else if let ::std::option::Option::Some(__usage_at) =
partial.__usage_selected
{
<#ty as usage_argv::spec::Subcommands>::deprecations_for_view_path(
&partial.__usage_sub,
::std::option::Option::Some(__usage_at),
remaining_descendants,
out,
);
}
}
fn check_for_view_path<'t, 'v>(
partial: &mut Self::Partial,
remaining_descendants: usize,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
if remaining_descendants == 0 {
<Self as usage_argv::spec::CommandArgs>::check(partial)
} else if let ::std::option::Option::Some(__usage_at) =
partial.__usage_selected
{
<Self as usage_argv::spec::CommandArgs>::omit_own_for_view(partial);
<#ty as usage_argv::spec::Subcommands>::check_for_view_path(
&mut partial.__usage_sub,
__usage_at,
remaining_descendants,
)
} else {
::std::result::Result::Ok(())
}
}
})
})
.unwrap_or_default();
let omit_flattened = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
<#ty as usage_argv::spec::CommandArgs>::omit_own_for_view(&mut partial.#ident);
})
});
let omit_own = quote! {
fn omit_own_for_view(partial: &mut Self::Partial) {
partial.__usage_omit_own = true;
#(#omit_flattened)*
}
};
let effect = cli
.effect
.clone()
.unwrap_or_else(|| quote!(::core::option::Option::None));
let hide = cli.hide;
quote! {
#[doc(hidden)]
#[allow(
non_upper_case_globals,
non_snake_case,
unused_imports,
// Fires when a metadata struct happens to be fully specified. `..EMPTY`
// is kept on purpose: it is what lets usage-argv gain a metadata field
// without breaking every crate that derives.
clippy::needless_update
)]
const _: () = {
use #runtime as usage_argv;
#validation_import
#flatten_checks
#keys
#(#flag_tables)*
#(#arg_tables)*
#table_decls
pub static COMMAND: usage_argv::Command = usage_argv::Command {
name: #name,
aliases: &[#(#aliases),*],
key: #command_key,
unknown_flags: #unknown_flags,
arg_required_else_help: #arg_required_else_help,
subcommand_negates_reqs: #subcommand_negates_reqs,
args_conflicts_with_subcommands: #args_conflicts_with_subcommands,
subcommand_precedence_over_arg: #subcommand_precedence_over_arg,
allow_missing_positional: #allow_missing_positional,
disable_help_flag: #disable_help_flag,
disable_help_subcommand: #disable_help_subcommand,
disable_version_flag: #disable_version_flag,
dont_delimit_trailing_values: #dont_delimit_trailing_values,
flags: #flag_table_ref,
args: #arg_table_ref,
clause: #clause_table,
#sub_commands
#sub_external
..usage_argv::Command::EMPTY
};
#implicit_clause_assertions
const _: () = usage_argv::assert_clause_flag_spellings(&COMMAND);
#(#flag_metas)*
#(#arg_metas)*
#meta_table_decls
#group_meta_decl
#(#output_schema_decls)*
pub static COMMAND_META: usage_argv::spec::CommandMeta = usage_argv::spec::CommandMeta {
cmd: &COMMAND,
outputs: #outputs,
select: #select,
exit_codes: #exit_codes,
effect: #effect,
about: #about,
long_about: #long_about,
deprecated: #deprecated,
deprecated_warn_at: #deprecated_warn_at,
deprecated_remove_at: #deprecated_remove_at,
surface: #surface,
available_if: &[#(#available_if),*],
hidden_aliases: &[#(#hidden_aliases),*],
hide: #hide,
restart_token: #restart_token,
subcommand_required: #subcommand_required,
subcommand_help_heading: #subcommand_help_heading,
subcommand_value_name: #subcommand_value_name,
next_line_help: #next_line_help,
flatten_help: #flatten_help,
term_width: #term_width,
max_term_width: #max_term_width,
args_override_self: #args_override_self,
mount: #mount,
before_help: #before_help,
before_long_help: #before_long_help,
after_help: #after_help,
after_long_help: #after_long_help,
examples: #examples,
headings: #headings,
flags: #flag_meta_table_ref,
args: #arg_meta_table_ref,
clause: #clause_meta,
groups: #group_meta_table_ref,
flatten_groups: #flatten_group_table_ref,
#sub_metas
..usage_argv::spec::CommandMeta::EMPTY
};
pub fn __usage_text(value: &[u8]) -> ::std::vec::Vec<u8> {
value.to_vec()
}
pub fn __usage_value_text(
value: ::std::option::Option<&[u8]>,
) -> ::std::vec::Vec<u8> {
value.map(__usage_text).unwrap_or_default()
}
#partial
#apply
#argument_lookup
#deprecations
pub fn start() -> Partial {
#defaults
partial
}
fn apply_declared_defaults(
partial: &mut Partial,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
__usage_standing: ::std::option::Option<&#ident>,
) {
let _ = __usage_standing.is_some();
#apply_defaults
}
fn apply_env_fallbacks(
partial: &mut Partial,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
__usage_standing: ::std::option::Option<&#ident>,
) {
let _ = __usage_standing.is_some();
#apply_env
}
fn check_with_args_override_self_for_view_standing<'t, 'v>(
partial: &mut Partial,
args_override_self: bool,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
__usage_standing: ::std::option::Option<&#ident>,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
partial.__usage_view = __usage_view;
let _ = (&partial, __usage_standing.is_some());
#post
::std::result::Result::Ok(())
}
fn check_with_args_override_self_for_view<'t, 'v>(
partial: &mut Partial,
args_override_self: bool,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_args_override_self_for_view_standing(
partial,
args_override_self,
__usage_view,
::std::option::Option::None,
)
}
pub fn check_with_args_override_self<'t, 'v>(
partial: &mut Partial,
args_override_self: bool,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_args_override_self_for_view(
partial,
args_override_self,
::std::option::Option::None,
)
}
pub fn check<'t, 'v>(
partial: &mut Partial,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_args_override_self(partial, #args_override_self)
}
pub fn check_update_with_args_override_self<'t, 'v>(
partial: &mut Partial,
args_override_self: bool,
__usage_standing: &#ident,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_args_override_self_for_view_standing(
partial,
args_override_self,
::std::option::Option::None,
::std::option::Option::Some(__usage_standing),
)
}
pub fn check_update<'t, 'v>(
partial: &mut Partial,
__usage_standing: &#ident,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_update_with_args_override_self(
partial,
#args_override_self,
__usage_standing,
)
}
#merge
#settings_defs
impl usage_argv::spec::CommandArgs for #ident {
type Partial = Partial;
const COMMAND: &'static usage_argv::Command<'static> = &COMMAND;
const META: &'static usage_argv::spec::CommandMeta<'static> =
&COMMAND_META;
fn start() -> Self::Partial {
start()
}
fn apply(
partial: &mut Self::Partial,
event: &usage_argv::Event<'_, '_, '_>,
) -> bool {
apply(partial, event)
}
fn apply_mirrored_global(
partial: &mut Self::Partial,
event: &usage_argv::Event<'_, '_, '_>,
) -> bool {
apply_mirrored_global(partial, event)
}
fn deprecations(
partial: &Self::Partial,
out: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) {
deprecations(partial, out)
}
fn check<'t, 'v>(
partial: &mut Self::Partial,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check(partial)
}
fn check_with_args_override_self<'t, 'v>(
partial: &mut Self::Partial,
args_override_self: bool,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_args_override_self(partial, args_override_self)
}
fn check_with_args_override_self_for_view<'t, 'v>(
partial: &mut Self::Partial,
args_override_self: bool,
view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_with_args_override_self_for_view(partial, args_override_self, view)
}
fn check_update<'t, 'v>(
partial: &mut Self::Partial,
standing: &Self,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_update(partial, standing)
}
fn check_update_with_args_override_self<'t, 'v>(
partial: &mut Self::Partial,
args_override_self: bool,
standing: &Self,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
check_update_with_args_override_self(
partial,
args_override_self,
standing,
)
}
#presence
#any_standing
fn apply_defaults(partial: &mut Self::Partial) {
apply_declared_defaults(
partial,
::std::option::Option::None,
::std::option::Option::None,
)
}
fn apply_defaults_for_view(
partial: &mut Self::Partial,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
) {
apply_declared_defaults(
partial,
__usage_view,
::std::option::Option::None,
)
}
fn apply_defaults_update(partial: &mut Self::Partial, standing: &Self) {
apply_declared_defaults(
partial,
::std::option::Option::None,
::std::option::Option::Some(standing),
)
}
fn apply_env(partial: &mut Self::Partial) {
apply_env_fallbacks(
partial,
::std::option::Option::None,
::std::option::Option::None,
)
}
fn apply_env_for_view(
partial: &mut Self::Partial,
__usage_view: ::std::option::Option<
&'static usage_argv::spec::ViewMeta<'static>,
>,
) {
apply_env_fallbacks(
partial,
__usage_view,
::std::option::Option::None,
)
}
fn apply_env_update(partial: &mut Self::Partial, standing: &Self) {
apply_env_fallbacks(
partial,
::std::option::Option::None,
::std::option::Option::Some(standing),
)
}
#view_path_methods
#omit_own
#settings_impl
fn build<'t, 'v>(
partial: Self::Partial,
) -> ::std::result::Result<Self, usage_argv::Error<'t, 'v>> {
::std::result::Result::Ok(#built)
}
fn merge<'t, 'v>(
partial: Self::Partial,
standing: &mut Self,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
merge(partial, standing)
}
}
impl<__UsageOmitter> usage_argv::spec::ViewCommandArgs<__UsageOmitter>
for #ident
#view_where
{
fn build_for_view<'t, 'v>(
partial: Self::Partial,
) -> ::std::result::Result<Self, usage_argv::Error<'t, 'v>> {
::std::result::Result::Ok(#built_for_view)
}
}
};
#dispatch
}
}
fn rewrite_inline_arg_meta(meta: &mut syn::Meta) {
if matches!(meta, syn::Meta::Path(path) if path.is_ident("arg")) {
*meta = syn::parse_quote!(usage(arg));
return;
}
let path = match meta {
syn::Meta::Path(path) => path,
syn::Meta::List(list) => &mut list.path,
syn::Meta::NameValue(value) => &mut value.path,
};
if path.is_ident("arg") {
*path = syn::parse_quote!(usage);
return;
}
if !path.is_ident("cfg_attr") {
return;
}
let syn::Meta::List(list) = meta else {
return;
};
let parser = syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
let Ok(mut nested) = syn::parse::Parser::parse2(parser, list.tokens.clone()) else {
return;
};
for value in nested.iter_mut().skip(1) {
rewrite_inline_arg_meta(value);
}
list.tokens = quote!(#nested);
}
fn inline_field_meta(meta: &syn::Meta) -> Option<syn::Meta> {
if meta.path().is_ident("arg") {
let mut meta = meta.clone();
rewrite_inline_arg_meta(&mut meta);
return Some(meta);
}
if meta.path().is_ident("usage") || meta.path().is_ident("doc") || meta.path().is_ident("cfg") {
return Some(meta.clone());
}
let syn::Meta::List(list) = meta else {
return None;
};
if !list.path.is_ident("cfg_attr") {
return None;
}
let parser = syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
let nested = syn::parse::Parser::parse2(parser, list.tokens.clone()).ok()?;
let mut nested = nested.into_iter();
let condition = nested.next()?;
let kept = nested
.filter_map(|meta| inline_field_meta(&meta))
.collect::<Vec<_>>();
if kept.is_empty() {
return None;
}
syn::parse2(quote!(cfg_attr(#condition, #(#kept),*))).ok()
}
fn inline_field_attr(attr: syn::Attribute) -> Option<syn::Attribute> {
let meta = inline_field_meta(&attr.meta)?;
Some(syn::Attribute { meta, ..attr })
}
fn meta_controls_field_presence(meta: &syn::Meta) -> bool {
if meta.path().is_ident("cfg") {
return true;
}
let syn::Meta::List(list) = meta else {
return false;
};
if !list.path.is_ident("cfg_attr") {
return false;
}
let parser = syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
syn::parse::Parser::parse2(parser, list.tokens.clone())
.is_ok_and(|nested| nested.iter().skip(1).any(meta_controls_field_presence))
}
struct DispatchTrait {
wanted: bool,
name: &'static str,
method: &'static str,
ctx: bool,
is_async: bool,
}
impl DispatchTrait {
fn all(dispatch: Dispatch) -> [Self; 4] {
[
DispatchTrait {
wanted: dispatch.run,
name: "Run",
method: "run",
ctx: false,
is_async: false,
},
DispatchTrait {
wanted: dispatch.run_with,
name: "RunWith",
method: "run_with",
ctx: true,
is_async: false,
},
DispatchTrait {
wanted: dispatch.run_async,
name: "RunAsync",
method: "run_async",
ctx: false,
is_async: true,
},
DispatchTrait {
wanted: dispatch.run_async_with,
name: "RunAsyncWith",
method: "run_async_with",
ctx: true,
is_async: true,
},
]
}
fn path(&self) -> TokenStream {
let name = format_ident!("{}", self.name);
if self.ctx {
quote!(usage_argv::#name<__UsageCtx>)
} else {
quote!(usage_argv::#name)
}
}
fn as_of(&self, ty: &TokenStream) -> TokenStream {
let path = self.path();
quote!(<#ty as #path>)
}
fn path_with_output(&self, output: &TokenStream) -> TokenStream {
let name = format_ident!("{}", self.name);
if self.ctx {
quote!(usage_argv::#name<__UsageCtx, Output = #output>)
} else {
quote!(usage_argv::#name<Output = #output>)
}
}
fn impl_generics(&self) -> TokenStream {
if self.ctx {
quote!(impl<__UsageCtx>)
} else {
quote!(impl)
}
}
fn signature(&self) -> TokenStream {
let method = format_ident!("{}", self.method);
let asyncness = self.is_async.then(|| quote!(async));
if self.ctx {
quote!(#asyncness fn #method(self, __usage_ctx: __UsageCtx) -> Self::Output)
} else {
quote!(#asyncness fn #method(self) -> Self::Output)
}
}
fn call(&self, value: TokenStream) -> TokenStream {
let name = format_ident!("{}", self.name);
let method = format_ident!("{}", self.method);
let call = if self.ctx {
quote!(usage_argv::#name::<__UsageCtx>::#method(#value, __usage_ctx))
} else {
quote!(usage_argv::#name::#method(#value))
};
if self.is_async {
quote!(#call.await)
} else {
call
}
}
}
fn arm_kind(kind: &DispatchTrait, v: &Variant) -> DispatchTrait {
let is_async = kind.is_async && !v.run_sync;
let ctx = kind.ctx && !v.no_ctx;
match (is_async, ctx) {
(false, false) => DispatchTrait {
wanted: true,
name: "Run",
method: "run",
ctx: false,
is_async: false,
},
(false, true) => DispatchTrait {
wanted: true,
name: "RunWith",
method: "run_with",
ctx: true,
is_async: false,
},
(true, false) => DispatchTrait {
wanted: true,
name: "RunAsync",
method: "run_async",
ctx: false,
is_async: true,
},
(true, true) => DispatchTrait {
wanted: true,
name: "RunAsyncWith",
method: "run_async_with",
ctx: true,
is_async: true,
},
}
}
fn first_command(subs: &Subcommands) -> Option<&Variant> {
subs.variants.iter().find(|v| !v.external)
}
fn dispatch_output_ty(subs: &Subcommands, kind: &DispatchTrait) -> TokenStream {
if let Some(ty) = &subs.dispatch_output {
return quote!(#ty);
}
let first = first_command(subs).expect("a dispatched enum names a command");
let ty = &first.ty;
let of = arm_kind(kind, first).as_of("e!(#ty));
quote!(#of::Output)
}
fn dispatch_pat(enum_ident: &syn::Ident, v: &Variant) -> TokenStream {
let variant = &v.ident;
if v.unit {
quote!(#enum_ident::#variant)
} else if let Some(fields) = &v.inline_fields {
let bindings = fields.iter().map(|field| {
let name = field
.ident
.as_ref()
.expect("named variant fields have names");
let cfg_attrs = field
.attrs
.iter()
.filter(|attr| meta_controls_field_presence(&attr.meta));
quote! { #(#cfg_attrs)* #name }
});
quote!(#enum_ident::#variant { #(#bindings),* })
} else {
quote!(#enum_ident::#variant(__usage_inner))
}
}
fn dispatch_value(v: &Variant) -> TokenStream {
if v.unit {
let ty = &v.ty;
quote!(#ty {})
} else if let Some(fields) = &v.inline_fields {
let ty = &v.ty;
let bindings = fields.iter().map(|field| {
let name = field
.ident
.as_ref()
.expect("named variant fields have names");
let cfg_attrs = field
.attrs
.iter()
.filter(|attr| meta_controls_field_presence(&attr.meta));
quote! { #(#cfg_attrs)* #name }
});
quote!(#ty { #(#bindings),* })
} else if v.boxed {
quote!(*__usage_inner)
} else {
quote!(__usage_inner)
}
}
fn dispatch_arm_call(
kind: &DispatchTrait,
v: &Variant,
ident: &syn::Ident,
external: Option<&syn::Path>,
) -> TokenStream {
let pat = dispatch_pat(ident, v);
let value = dispatch_value(v);
let call = if v.external {
let path = external.expect("external variants name a function");
let call = if kind.ctx {
quote!(#path(#value, __usage_ctx))
} else {
quote!(#path(#value))
};
if kind.is_async {
quote!(#call.await)
} else {
call
}
} else {
let arm = arm_kind(kind, v);
let mut call = arm.call(value);
if kind.ctx && !arm.ctx {
call = quote! {{
let _ = __usage_ctx;
#call
}};
}
call
};
quote!(#pat => #call,)
}
fn variant_bound(
kind: &DispatchTrait,
v: &Variant,
i: usize,
first_non_external: usize,
output: &TokenStream,
named_output: bool,
) -> Option<TokenStream> {
if v.external {
return None;
}
let ty = &v.ty;
let arm = arm_kind(kind, v);
if named_output || i != first_non_external {
let bound = arm.path_with_output(output);
Some(quote!(#ty: #bound))
} else {
let path = arm.path();
Some(quote!(#ty: #path))
}
}
fn lazy_arm_call(
kind: &DispatchTrait,
v: &Variant,
ident: &syn::Ident,
external: Option<&syn::Path>,
) -> TokenStream {
let pat = dispatch_pat(ident, v);
let value = dispatch_value(v);
let arm = arm_kind(kind, v);
let call = if v.external {
let path = external.expect("external variants name a function");
let invoked = if kind.ctx {
quote!(#path(#value, __usage_load()))
} else {
quote! {{
let _ = __usage_load;
#path(#value)
}}
};
if kind.is_async {
quote!(#invoked.await)
} else {
invoked
}
} else if arm.ctx {
let name = format_ident!("{}", arm.name);
let method = format_ident!("{}", arm.method);
let invoked = quote!(usage_argv::#name::<__UsageCtx>::#method(#value, __usage_load()));
if arm.is_async {
quote!(#invoked.await)
} else {
invoked
}
} else {
let mut call = arm.call(value);
call = quote! {{
let _ = __usage_load;
#call
}};
call
};
quote!(#pat => #call,)
}
fn emit_subcommands_dispatch(subs: &Subcommands, runtime: &TokenStream) -> TokenStream {
if !subs.dispatch.any() {
return TokenStream::new();
}
let ident = &subs.ident;
let external = subs.dispatch_external.as_ref();
let named_output = subs.dispatch_output.is_some();
let first_non_external = subs.variants.iter().position(|v| !v.external).unwrap_or(0);
let wants_lazy = subs.variants.iter().any(|v| v.no_ctx)
&& (subs.dispatch.run_with || subs.dispatch.run_async_with);
let impls = DispatchTrait::all(subs.dispatch)
.into_iter()
.filter(|kind| kind.wanted)
.map(|kind| {
let generics = kind.impl_generics();
let path = kind.path();
let signature = kind.signature();
let output = dispatch_output_ty(subs, &kind);
let bounds = subs.variants.iter().enumerate().filter_map(|(i, v)| {
variant_bound(&kind, v, i, first_non_external, &output, named_output)
});
let arms = subs
.variants
.iter()
.map(|v| dispatch_arm_call(&kind, v, ident, external));
quote! {
#generics #path for #ident
where
#(#bounds,)*
{
type Output = #output;
#signature {
match self {
#(#arms)*
}
}
}
}
});
let lazy = DispatchTrait::all(subs.dispatch)
.into_iter()
.filter(|kind| kind.wanted && kind.ctx && wants_lazy)
.map(|kind| {
let output = dispatch_output_ty(subs, &kind);
let arms = subs
.variants
.iter()
.map(|v| lazy_arm_call(&kind, v, ident, external));
let (name, asyncness) = if kind.is_async {
(format_ident!("run_async_with_lazy"), quote!(async))
} else {
(format_ident!("run_with_lazy"), TokenStream::new())
};
let bounds = subs.variants.iter().enumerate().filter_map(|(i, v)| {
variant_bound(&kind, v, i, first_non_external, &output, named_output)
});
quote! {
impl #ident {
pub #asyncness fn #name<__UsageCtx, __UsageLoad>(
self,
__usage_load: __UsageLoad,
) -> #output
where
__UsageLoad: ::core::ops::FnOnce() -> __UsageCtx,
#(#bounds,)*
{
match self {
#(#arms)*
}
}
}
}
});
quote! {
#[doc(hidden)]
const _: () = {
use #runtime as usage_argv;
#(#impls)*
#(#lazy)*
};
}
}
fn emit_command_dispatch(cli: &Cli, runtime: &TokenStream) -> TokenStream {
if !cli.dispatch.any() {
return TokenStream::new();
}
let ident = &cli.ident;
let Some((field, held_ty)) = cli.fields.iter().find_map(|field| match &field.kind {
Kind::Subcommand {
ty,
optional: false,
} => Some((&field.ident, ty)),
_ => None,
}) else {
return TokenStream::new();
};
let held_ty = quote!(#held_ty);
let extras = cli.fields.iter().any(|field| {
!matches!(
field.kind,
Kind::Subcommand {
optional: false,
..
}
)
});
if extras {
let methods = DispatchTrait::all(cli.dispatch)
.into_iter()
.filter(|kind| kind.wanted)
.map(|kind| {
let held = kind.as_of(&held_ty);
let output = quote!(#held::Output);
let value = quote!(#field);
let call = kind.call(value);
let name = if kind.is_async && kind.ctx {
format_ident!("run_command_async_with")
} else if kind.is_async {
format_ident!("run_command_async")
} else if kind.ctx {
format_ident!("run_command_with")
} else {
format_ident!("run_command")
};
let asyncness = kind.is_async.then(|| quote!(async));
let generics = kind.ctx.then(|| quote!(<__UsageCtx>));
let ctx_arg = kind.ctx.then(|| quote!(, __usage_ctx: __UsageCtx));
let path = kind.path();
quote! {
pub #asyncness fn #name #generics(self #ctx_arg) -> #output
where
#held_ty: #path,
{
let Self { #field, .. } = self;
#call
}
}
});
return quote! {
#[doc(hidden)]
const _: () = {
use #runtime as usage_argv;
impl #ident {
#(#methods)*
}
};
};
}
let impls = DispatchTrait::all(cli.dispatch)
.into_iter()
.filter(|kind| kind.wanted)
.map(|kind| {
let generics = kind.impl_generics();
let path = kind.path();
let signature = kind.signature();
let held = kind.as_of(&held_ty);
let call = kind.call(quote!(self.#field));
quote! {
#generics #path for #ident
where
#held_ty: #path,
{
type Output = #held::Output;
#signature {
#call
}
}
}
});
quote! {
#[doc(hidden)]
const _: () = {
use #runtime as usage_argv;
#(#impls)*
};
}
}
pub fn emit_subcommands(subs: &Subcommands) -> TokenStream {
let ident = &subs.ident;
let runtime = runtime_path();
let derive = derive_path();
let dispatch = emit_subcommands_dispatch(subs, &runtime);
let generated_structs = subs
.variants
.iter()
.filter(|v| v.unit || v.inline_fields.is_some())
.map(|v| {
let name = &v.ty;
let effect = v
.effect
.as_ref()
.map(|word| quote!(#[usage(effect = #word)]));
let hidden = quote!(#name).to_string().contains("__Usage");
let fields = v.inline_fields.iter().flatten().cloned().map(|mut field| {
field.attrs = field
.attrs
.into_iter()
.filter_map(inline_field_attr)
.collect();
if !hidden {
field.vis = syn::parse_quote!(pub);
}
field
});
let doc = if hidden {
quote!(#[doc(hidden)])
} else {
let variant = v.ident.to_string();
let enum_name = ident.to_string();
let doc = format!(
"Parsed arguments for `{enum_name}::{variant}`. Implement `usage::Run` \
(or the context / async pair) on this type."
);
quote!(#[doc = #doc])
};
quote! {
#doc
#[derive(#derive::Args)]
#effect
pub struct #name {
#(#fields),*
}
}
})
.collect::<Vec<_>>();
let generated_structs = generated_structs.into_iter();
let partial_variants = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote!(#variant(::std::vec::Vec<::std::vec::Vec<u8>>),)
} else {
let ty = &v.ty;
quote!(#variant(<#ty as usage_argv::spec::CommandArgs>::Partial),)
}
});
let begins = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
#i => {
if !::std::matches!(partial, Partial::#variant(_)) {
*partial = Partial::#variant(::std::vec::Vec::new());
}
}
}
} else {
let ty = &v.ty;
quote! {
#i => {
if !::std::matches!(partial, Partial::#variant(_)) {
*partial = Partial::#variant(
<#ty as usage_argv::spec::CommandArgs>::start(),
);
}
}
}
}
});
let applies = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
if let usage_argv::Event::External { values } = event {
__usage_p.extend(
values.iter().map(|v| v.as_encoded_bytes().to_vec()),
);
true
} else {
false
}
} else {
false
}
}
}
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::apply(__usage_p, event)
} else {
false
}
}
}
}
});
let command_overrides = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external)
.map(|(i, v)| {
let name = format_ident!("COMMAND_{i}");
let alias_groups = format_ident!("ALIAS_GROUPS_{i}");
let aliases_name = format_ident!("ALIASES_{i}");
let ty = &v.ty;
let cmd_name = &v.name;
let aliases = v.aliases.iter().chain(&v.hidden_aliases);
quote! {
const #alias_groups: &[&[&str]] = &[
<#ty as usage_argv::spec::CommandArgs>::COMMAND.aliases,
&[#(#aliases),*],
];
static #aliases_name: [&str; usage_argv::table_len(#alias_groups)] =
usage_argv::spec::concat_aliases(#alias_groups);
pub static #name: usage_argv::Command = usage_argv::Command {
name: #cmd_name,
aliases: &#aliases_name,
..*<#ty as usage_argv::spec::CommandArgs>::COMMAND
};
}
});
let commands = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external)
.map(|(i, _)| {
let name = format_ident!("COMMAND_{i}");
quote!(&#name)
});
let unique_commands = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external)
.map(|(i, _)| {
let name = format_ident!("COMMAND_{i}");
quote!(&#name)
});
let variant_of = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external)
.map(|(i, _)| quote!(#i));
let has_external = subs.variants.iter().any(|v| v.external);
let external_const = match subs.variants.iter().position(|v| v.external) {
Some(i) => quote!(::std::option::Option::Some(#i)),
None => quote!(::std::option::Option::None),
};
let meta_overrides = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external)
.map(|(i, v)| {
let name = format_ident!("META_{i}");
let cmd = format_ident!("COMMAND_{i}");
let hidden_groups = format_ident!("HIDDEN_ALIAS_GROUPS_{i}");
let hidden_name = format_ident!("HIDDEN_ALIASES_{i}");
let ty = &v.ty;
let about = match v.help.as_ref() {
Some(help) => option_expr(Some(help)),
None => quote!(<#ty as usage_argv::spec::CommandArgs>::META.about),
};
let long_about = match v.long_help.as_ref() {
Some(long) => option_expr(Some(long)),
None => quote!(<#ty as usage_argv::spec::CommandArgs>::META.long_about),
};
let deprecated = v
.deprecated
.as_deref()
.map(|value| option_str(Some(value)))
.unwrap_or_else(|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.deprecated));
let deprecated_warn_at = v
.deprecated_warn_at
.as_deref()
.map(|value| option_str(Some(value)))
.unwrap_or_else(
|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.deprecated_warn_at),
);
let deprecated_remove_at = v
.deprecated_remove_at
.as_deref()
.map(|value| option_str(Some(value)))
.unwrap_or_else(
|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.deprecated_remove_at),
);
let before_help = v
.before_help
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(
|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.before_help),
);
let before_long_help = v
.before_long_help
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(
|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.before_long_help),
);
let after_help = v
.after_help
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.after_help));
let after_long_help = v
.after_long_help
.as_ref()
.map(|value| option_expr(Some(value)))
.unwrap_or_else(
|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.after_long_help),
);
let examples = if v.examples.is_empty() {
quote!(<#ty as usage_argv::spec::CommandArgs>::META.examples)
} else {
examples_table(&v.examples)
};
let hidden = &v.hidden_aliases;
let hide = v.hide;
let help_heading = option_str(v.help_heading.as_deref());
let surface = v
.surface
.as_deref()
.map(|surface| option_str(Some(surface)))
.unwrap_or_else(|| quote!(<#ty as usage_argv::spec::CommandArgs>::META.surface));
let available_if = if v.available_if.is_empty() {
quote!(<#ty as usage_argv::spec::CommandArgs>::META.available_if)
} else {
let conditions = &v.available_if;
quote!(&[#(#conditions),*])
};
let display_order = option_usize(v.display_order);
quote! {
const #hidden_groups: &[&[&str]] = &[
<#ty as usage_argv::spec::CommandArgs>::META.hidden_aliases,
&[#(#hidden),*],
];
static #hidden_name: [&str; usage_argv::table_len(#hidden_groups)] =
usage_argv::spec::concat_aliases(#hidden_groups);
pub static #name: usage_argv::spec::CommandMeta =
usage_argv::spec::CommandMeta {
cmd: &#cmd,
about: #about,
long_about: #long_about,
deprecated: #deprecated,
deprecated_warn_at: #deprecated_warn_at,
deprecated_remove_at: #deprecated_remove_at,
before_help: #before_help,
before_long_help: #before_long_help,
after_help: #after_help,
after_long_help: #after_long_help,
examples: #examples,
hide: #hide || <#ty as usage_argv::spec::CommandArgs>::META.hide,
help_heading: #help_heading,
surface: #surface,
available_if: #available_if,
display_order: #display_order,
hidden_aliases: &#hidden_name,
..*<#ty as usage_argv::spec::CommandArgs>::META
};
}
});
let metas = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external)
.map(|(i, _)| {
let name = format_ident!("META_{i}");
quote!(&#name)
});
let checks = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
#i => ::std::result::Result::Ok(()),
}
} else {
let ty = &v.ty;
quote! {
#i => match partial {
Partial::#variant(__usage_p) => {
<#ty as usage_argv::spec::CommandArgs>::check(__usage_p)
}
_ => ::std::result::Result::Ok(()),
},
}
}
});
let view_checks = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! { #i => ::std::result::Result::Ok(()), }
} else {
let ty = &v.ty;
quote! {
#i => match partial {
Partial::#variant(__usage_p) => {
if remaining_commands <= 1 {
<#ty as usage_argv::spec::CommandArgs>::check(__usage_p)
} else {
<#ty as usage_argv::spec::CommandArgs>::check_for_view_path(
__usage_p,
remaining_commands - 1,
)
}
}
_ => ::std::result::Result::Ok(()),
},
}
}
});
let binding_parts = subs.variants.iter().filter(|v| !v.external).map(|v| {
let ty = &v.ty;
quote!(<#ty as usage_argv::spec::CommandArgs>::SETTINGS_BINDINGS)
});
let binding_lens = binding_parts.clone().map(|part| quote!(+ #part.len()));
let givens = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => ::std::vec::Vec::new(),
}
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::settings_given(__usage_p)
} else {
::std::vec::Vec::new()
}
}
}
}
});
let any_givens = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => ::std::option::Option::None,
}
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::any_given(__usage_p)
} else {
::std::option::Option::None
}
}
}
}
});
let exclusive_givens = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => ::std::option::Option::None,
}
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::exclusive_given(__usage_p)
} else {
::std::option::Option::None
}
}
}
}
});
let deprecations = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => {}
}
} else {
let ty = &v.ty;
let meta = format_ident!("META_{i}");
quote! {
::std::option::Option::Some(#i) => {
if #meta.deprecated.is_some()
|| #meta.deprecated_warn_at.is_some()
|| #meta.deprecated_remove_at.is_some()
{
out.push(usage_argv::warn::Warning::command(
#meta.cmd.name,
#meta.deprecated,
#meta.deprecated_warn_at,
#meta.deprecated_remove_at,
));
}
if let Partial::#variant(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::deprecations(__usage_p, out);
}
}
}
}
});
let view_deprecations = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => {}
}
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
if remaining_commands <= 1 {
<#ty as usage_argv::spec::CommandArgs>::deprecations(__usage_p, out);
} else {
<#ty as usage_argv::spec::CommandArgs>::deprecations_for_view_path(
__usage_p,
remaining_commands - 1,
out,
);
}
}
}
}
}
});
let apply_envs = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! {
::std::option::Option::Some(#i) => {}
}
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::apply_env(__usage_p);
}
}
}
}
});
let view_apply_envs = subs.variants.iter().enumerate().map(|(i, v)| {
let variant = format_ident!("V{i}");
if v.external {
quote! { ::std::option::Option::Some(#i) => {} }
} else {
let ty = &v.ty;
quote! {
::std::option::Option::Some(#i) => {
if let Partial::#variant(__usage_p) = partial {
if remaining_commands <= 1 {
<#ty as usage_argv::spec::CommandArgs>::apply_env(__usage_p);
} else {
<#ty as usage_argv::spec::CommandArgs>::apply_env_for_view_path(
__usage_p,
remaining_commands - 1,
);
}
}
}
}
}
});
let select_arms = |for_view: bool| {
subs.variants.iter().enumerate().map(move |(i, v)| {
let held = format_ident!("V{i}");
let variant = &v.ident;
if v.external {
let name = &v.name;
let collected = if v.external_os {
quote! {{
let mut __usage_values = ::std::vec::Vec::with_capacity(__usage_p.len());
for __usage_item in __usage_p {
match usage_argv::os_string_from_bytes(__usage_item) {
::std::result::Result::Ok(__usage_os) => {
__usage_values.push(__usage_os)
}
::std::result::Result::Err(__usage_bytes) => {
return ::std::result::Result::Err(
usage_argv::Error::InvalidValue(::std::boxed::Box::new(
usage_argv::InvalidValue {
name: #name,
value: ::std::string::String::from_utf8_lossy(
&__usage_bytes,
)
.into_owned(),
reason: ::std::string::ToString::to_string(
&"this platform cannot hold these bytes",
),
},
)),
);
}
}
}
__usage_values
}}
} else {
quote! {{
let mut __usage_values = ::std::vec::Vec::with_capacity(__usage_p.len());
for __usage_item in __usage_p {
match ::std::string::String::from_utf8(__usage_item) {
::std::result::Result::Ok(__usage_text) => {
__usage_values.push(__usage_text)
}
::std::result::Result::Err(__usage_bad) => {
return ::std::result::Result::Err(
usage_argv::invalid_utf8_value(#name, __usage_bad),
);
}
}
}
__usage_values
}}
};
let made = quote!(#ident::#variant(#collected));
quote! {
#i => match partial {
Partial::#held(__usage_p) => {
::std::result::Result::Ok(::std::option::Option::Some(#made))
}
_ => ::std::result::Result::Ok(::std::option::Option::None),
},
}
} else {
let ty = &v.ty;
let built = if for_view {
quote!(
<#ty as usage_argv::spec::ViewCommandArgs<__UsageOmitter>>::build_for_view(
__usage_p,
)?
)
} else {
quote!(<#ty as usage_argv::spec::CommandArgs>::build(__usage_p)?)
};
let built = if v.boxed {
quote!(::std::boxed::Box::new(#built))
} else {
built
};
let made = if v.unit {
quote! {{
let _ = #built;
#ident::#variant
}}
} else if let Some(fields) = &v.inline_fields {
let assignments = fields.iter().map(|field| {
let name = field
.ident
.as_ref()
.expect("named variant fields have names");
let cfg_attrs = field
.attrs
.iter()
.filter(|attr| meta_controls_field_presence(&attr.meta));
quote! {
#(#cfg_attrs)*
#name: __usage_built.#name
}
});
quote! {{
let __usage_built = #built;
let _ = &__usage_built;
#ident::#variant {
#(#assignments),*
}
}}
} else {
quote!(#ident::#variant(#built))
};
quote! {
#i => match partial {
Partial::#held(__usage_p) => {
::std::result::Result::Ok(::std::option::Option::Some(#made))
}
_ => ::std::result::Result::Ok(::std::option::Option::None),
},
}
}
})
};
let selects = select_arms(false);
let view_selects = select_arms(true);
let mergeable: Vec<(usize, &crate::model::Variant)> = subs
.variants
.iter()
.enumerate()
.filter(|(_, v)| !v.external && !v.unit && v.inline_fields.is_none())
.collect();
let standing_inner = |v: &crate::model::Variant| {
if v.boxed {
quote!(&**__usage_inner)
} else {
quote!(__usage_inner)
}
};
let standing_inner_mut = |v: &crate::model::Variant| {
if v.boxed {
quote!(&mut **__usage_inner)
} else {
quote!(__usage_inner)
}
};
let update_checks = mergeable.iter().map(|(i, v)| {
let held = format_ident!("V{i}");
let variant = &v.ident;
let ty = &v.ty;
let inner = standing_inner(v);
quote! {
(#i, #ident::#variant(__usage_inner)) => {
if let Partial::#held(__usage_p) = partial {
return <#ty as usage_argv::spec::CommandArgs>::check_update(
__usage_p,
#inner,
);
}
}
}
});
let update_envs = mergeable.iter().map(|(i, v)| {
let held = format_ident!("V{i}");
let variant = &v.ident;
let ty = &v.ty;
let inner = standing_inner(v);
quote! {
(::std::option::Option::Some(#i), #ident::#variant(__usage_inner)) => {
if let Partial::#held(__usage_p) = partial {
<#ty as usage_argv::spec::CommandArgs>::apply_env_update(
__usage_p,
#inner,
);
return;
}
}
}
});
let merges = mergeable.iter().map(|(i, v)| {
let held = format_ident!("V{i}");
let variant = &v.ident;
let ty = &v.ty;
let inner = standing_inner_mut(v);
quote! {
if selected == #i {
if let #ident::#variant(__usage_inner) = &mut *standing {
if let Partial::#held(__usage_p) = partial {
return <#ty as usage_argv::spec::CommandArgs>::merge(
__usage_p,
#inner,
);
}
return ::std::result::Result::Ok(());
}
}
}
});
let view_bounds: Vec<_> = subs
.variants
.iter()
.filter(|v| !v.external)
.map(|v| {
let ty = &v.ty;
quote!(#ty: usage_argv::spec::ViewCommandArgs<__UsageOmitter>)
})
.collect();
let view_where = (!view_bounds.is_empty()).then(|| quote!(where #(#view_bounds,)*));
quote! {
#(#generated_structs)*
#[doc(hidden)]
#[allow(
non_upper_case_globals,
non_snake_case,
unused_imports,
// Fires when a metadata struct happens to be fully specified. `..EMPTY`
// is kept on purpose: it is what lets usage-argv gain a metadata field
// without breaking every crate that derives.
clippy::needless_update
)]
const _: () = {
use #runtime as usage_argv;
pub enum Partial {
Unselected,
#(#partial_variants)*
}
impl ::std::default::Default for Partial {
fn default() -> Self {
Partial::Unselected
}
}
#(#command_overrides)*
#(#meta_overrides)*
const _: () = usage_argv::assert_unique_subcommand_names(&[#(#unique_commands),*]);
impl usage_argv::spec::Subcommands for #ident {
type Partial = Partial;
const COMMANDS: &'static [&'static usage_argv::Command<'static>] =
&[#(#commands),*];
const METAS: &'static [&'static usage_argv::spec::CommandMeta<'static>] =
&[#(#metas),*];
const HAS_EXTERNAL: bool = #has_external;
const EXTERNAL: ::std::option::Option<usize> = #external_const;
const VARIANT_OF: &'static [usize] = &[#(#variant_of),*];
fn apply(
partial: &mut Self::Partial,
selected: ::std::option::Option<usize>,
event: &usage_argv::Event<'_, '_, '_>,
) -> bool {
match selected {
#(#applies)*
_ => false,
}
}
fn begin(partial: &mut Self::Partial, selected: usize) {
match selected {
#(#begins)*
_ => {}
}
}
const SETTINGS_BINDINGS: &'static [(&'static str, &'static str)] = {
const PARTS: &[&'static [(&'static str, &'static str)]] = &[#(#binding_parts),*];
const N: usize = 0 #(#binding_lens)*;
const JOINED: [(&'static str, &'static str); N] =
usage_argv::spec::concat_bindings(PARTS);
&JOINED
};
fn settings_given(
partial: &Self::Partial,
selected: ::std::option::Option<usize>,
) -> ::std::vec::Vec<(&'static str, usage_argv::spec::SettingGiven)> {
match selected {
#(#givens)*
_ => ::std::vec::Vec::new(),
}
}
fn any_given(
partial: &Self::Partial,
selected: ::std::option::Option<usize>,
) -> ::std::option::Option<&'static str> {
match selected {
#(#any_givens)*
_ => ::std::option::Option::None,
}
}
fn exclusive_given(
partial: &Self::Partial,
selected: ::std::option::Option<usize>,
) -> ::std::option::Option<&'static str> {
match selected {
#(#exclusive_givens)*
_ => ::std::option::Option::None,
}
}
fn deprecations(
partial: &Self::Partial,
selected: ::std::option::Option<usize>,
out: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) {
match selected {
#(#deprecations)*
_ => {}
}
}
fn deprecations_for_view_path(
partial: &Self::Partial,
selected: ::std::option::Option<usize>,
remaining_commands: usize,
out: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) {
match selected {
#(#view_deprecations)*
_ => {}
}
}
fn apply_env(
partial: &mut Self::Partial,
selected: ::std::option::Option<usize>,
) {
match selected {
#(#apply_envs)*
_ => {}
}
}
fn apply_env_for_view_path(
partial: &mut Self::Partial,
selected: ::std::option::Option<usize>,
remaining_commands: usize,
) {
match selected {
#(#view_apply_envs)*
_ => {}
}
}
fn check<'t, 'v>(
partial: &mut Self::Partial,
selected: usize,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
match selected {
#(#checks)*
_ => ::std::result::Result::Ok(()),
}
}
fn check_for_view_path<'t, 'v>(
partial: &mut Self::Partial,
selected: usize,
remaining_commands: usize,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
match selected {
#(#view_checks)*
_ => ::std::result::Result::Ok(()),
}
}
fn select<'t, 'v>(
partial: Self::Partial,
selected: usize,
) -> ::std::result::Result<
::std::option::Option<Self>,
usage_argv::Error<'t, 'v>,
> {
match selected {
#(#selects)*
_ => ::std::result::Result::Ok(::std::option::Option::None),
}
}
fn check_update<'t, 'v>(
partial: &mut Self::Partial,
selected: usize,
standing: &Self,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
match (selected, standing) {
#(#update_checks)*
_ => {}
}
Self::check(partial, selected)
}
fn apply_env_update(
partial: &mut Self::Partial,
selected: ::std::option::Option<usize>,
standing: &Self,
) {
match (selected, standing) {
#(#update_envs)*
_ => {}
}
Self::apply_env(partial, selected)
}
fn merge_into<'t, 'v>(
partial: Self::Partial,
selected: usize,
standing: &mut Self,
) -> ::std::result::Result<(), usage_argv::Error<'t, 'v>> {
#(#merges)*
if let ::std::option::Option::Some(__usage_built) =
Self::select(partial, selected)?
{
*standing = __usage_built;
}
::std::result::Result::Ok(())
}
}
impl<__UsageOmitter> usage_argv::spec::ViewSubcommands<__UsageOmitter>
for #ident
#view_where
{
fn select_for_view<'t, 'v>(
partial: Self::Partial,
selected: usize,
) -> ::std::result::Result<
::std::option::Option<Self>,
usage_argv::Error<'t, 'v>,
> {
match selected {
#(#view_selects)*
_ => ::std::result::Result::Ok(::std::option::Option::None),
}
}
}
};
#dispatch
}
}
fn displaced_guard(cli: &Cli, field: &Field) -> TokenStream {
if !is_displaceable(cli, field) {
return quote!();
}
let overridden = format_ident!("__overridden_{}", field.ident);
quote!(&& !partial.#overridden)
}
fn declared_groups(cli: &Cli) -> Vec<(String, bool, bool, Vec<String>)> {
let mut groups: Vec<(String, bool, bool, Vec<String>)> = Vec::new();
for field in &cli.fields {
let Some(selector) = Cli::selector_for_field(field) else {
continue;
};
let names = field.group.iter().map(String::as_str);
for name in names {
match groups.iter_mut().find(|(n, _, _, _)| n == name) {
Some((_, _, _, members)) => {
if !members.contains(&selector) {
members.push(selector.clone());
}
}
None => {
let decl = cli.groups.iter().find(|d| d.name == name);
groups.push((
name.to_string(),
decl.is_some_and(|d| d.required),
decl.is_some_and(|d| d.multiple),
vec![selector.clone()],
));
}
}
}
}
groups
}
fn group_meta_table(cli: &Cli) -> (TokenStream, TokenStream) {
let declared: Vec<_> = declared_groups(cli)
.into_iter()
.filter(|(_, required, multiple, members)| members.len() >= 2 && (*required || !*multiple))
.collect();
let mut entries: Vec<(usize, TokenStream)> = declared
.iter()
.map(|(name, required, multiple, members)| {
let at = cli
.fields
.iter()
.position(|f| Cli::selector_for_field(f).is_some_and(|s| members.contains(&s)))
.unwrap_or(usize::MAX);
(
at,
quote! {
usage_argv::spec::GroupMeta {
name: #name,
members: &[#(#members),*],
required: #required,
multiple: #multiple,
}
},
)
})
.collect();
for (at, field) in cli.fields.iter().enumerate() {
let Kind::ArgGroup {
ty,
optional,
multiple,
} = &field.kind
else {
continue;
};
let required = !optional && !multiple;
entries.push((
at,
quote! {
usage_argv::spec::GroupMeta {
name: <#ty as usage_argv::spec::ArgGroup>::NAME,
members: <#ty as usage_argv::spec::ArgGroup>::MEMBERS,
required: #required,
multiple: {
assert!(
<#ty as usage_argv::spec::ArgGroup>::MULTIPLE == #multiple,
"an ArgGroup marked multiple must be held by Vec<T>, and Vec<T> needs #[usage(multiple)] on the ArgGroup"
);
#multiple
},
}
},
));
}
entries.sort_by_key(|(at, _)| *at);
let mut parts: Vec<TokenStream> = Vec::new();
let mut run: Vec<TokenStream> = Vec::new();
let mut emitted = vec![false; entries.len()];
let mut any_flattened = false;
for (i, field) in cli.fields.iter().enumerate() {
let Kind::Flatten { ty, .. } = &field.kind else {
continue;
};
any_flattened = true;
for (e, (at, group)) in entries.iter().enumerate() {
if !emitted[e] && *at < i {
emitted[e] = true;
run.push(group.clone());
}
}
if !run.is_empty() {
let run = std::mem::take(&mut run);
parts.push(quote!(&[#(#run),*]));
}
parts.push(quote!(<#ty as usage_argv::spec::CommandArgs>::META.groups));
}
for (e, (_, group)) in entries.iter().enumerate() {
if !emitted[e] {
run.push(group.clone());
}
}
if !run.is_empty() {
parts.push(quote!(&[#(#run),*]));
}
if parts.is_empty() {
return (quote!(), quote!(&[]));
}
if !any_flattened {
let len = entries.len();
let entries = entries.iter().map(|(_, group)| group);
return (
quote! {
pub static GROUP_METAS: [usage_argv::spec::GroupMeta; #len] = [#(#entries),*];
},
quote!(&GROUP_METAS),
);
}
(
quote! {
const GROUP_META_GROUPS: &[&[usage_argv::spec::GroupMeta<'static>]] =
&[#(#parts),*];
static GROUP_METAS: [usage_argv::spec::GroupMeta<'static>;
usage_argv::table_len(GROUP_META_GROUPS)] =
usage_argv::spec::concat_group_metas(GROUP_META_GROUPS);
},
quote!(&GROUP_METAS),
)
}
fn declared_defaults(cli: &Cli, filter_view: bool) -> TokenStream {
let own = cli.fields.iter().filter_map(|f| {
if matches!(f.kind, Kind::Subcommand { .. } | Kind::Skip) {
return None;
}
if !f.has_default() && f.default_if.is_empty() {
return None;
}
let given = format_ident!("__given_{}", f.ident);
let standing = displaced_guard(cli, f);
let mut fills: Vec<TokenStream> = f
.default_if
.iter()
.map(|condition| {
let pred = default_if_predicate(cli, condition, Lookup::Standing);
let assign =
assign_literal(f, &condition.value, std::slice::from_ref(&condition.value));
quote!(if !__usage_filled && (#pred) {
#assign
__usage_filled = true;
})
})
.collect();
if f.has_default() {
let assign = reset_to_default(f);
fills.push(quote!(if !__usage_filled {
#assign
}));
}
let active = if filter_view {
let active = view_field_active(f);
quote!((#active) &&)
} else {
quote!()
};
let standing_held = unless_standing(f);
Some(quote! {
if #active !partial.#given #standing #standing_held {
let mut __usage_filled = false;
#(#fills)*
}
})
});
let flattened = cli.fields.iter().filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
let ident = &f.ident;
let standing = standing_ident(f);
Some(if filter_view {
quote! {
match #standing {
::std::option::Option::Some(__usage_s) => {
<#ty as usage_argv::spec::CommandArgs>::apply_defaults_update(
&mut partial.#ident,
__usage_s,
);
}
::std::option::Option::None => {
<#ty as usage_argv::spec::CommandArgs>::apply_defaults_for_view(
&mut partial.#ident,
__usage_view,
);
}
}
}
} else {
quote! {
<#ty as usage_argv::spec::CommandArgs>::apply_defaults(&mut partial.#ident);
}
})
});
quote! {
#(#own)*
#(#flattened)*
}
}
fn env_fallbacks(cli: &Cli, filter_view: bool) -> TokenStream {
let own = cli.fields.iter().filter_map(|f| {
let ident = &f.ident;
let given = format_ident!("__given_{}", ident);
let vars: Vec<&str> = f
.env
.iter()
.map(String::as_str)
.chain(f.env_fallback.iter().map(String::as_str))
.chain(f.deprecated_env.iter().map(String::as_str))
.collect();
if vars.is_empty() {
return None;
}
let assign = match f.shape {
Shape::Optional => quote! {
partial.#ident = ::std::option::Option::Some(value.into_bytes());
},
Shape::Required => quote!(partial.#ident = value.into_bytes();),
Shape::Many => match f.delimiter {
Some(delimiter) => {
let byte = u8::try_from(u32::from(delimiter))
.expect("the model rejects non-ASCII delimiters");
quote! {
partial.#ident.clear();
for part in value.as_bytes().split(|b| *b == #byte) {
partial.#ident.push(part.to_vec());
}
}
}
None => quote! {
partial.#ident.clear();
partial.#ident.push(value.into_bytes());
},
},
Shape::Bool => quote! {
partial.#ident = !matches!(
value.as_str(),
"" | "0" | "false" | "no" | "off"
);
},
Shape::Count => {
let ty = &f.ty;
quote! {
match value.parse::<#ty>() {
::std::result::Result::Ok(count) => partial.#ident = count,
::std::result::Result::Err(_) => continue_unset = true,
}
}
}
};
let standing = displaced_guard(cli, f);
let active = if filter_view {
let active = view_field_active(f);
quote!((#active) &&)
} else {
quote!()
};
let record_deprecated = tracks_deprecated_env(f).then(|| {
let recorded = deprecated_env_ident(f);
let first_deprecated = vars.len() - f.deprecated_env.len();
if first_deprecated == 0 {
quote!(partial.#recorded = ::std::option::Option::Some(__usage_env);)
} else {
quote! {
if __usage_index >= #first_deprecated {
partial.#recorded = ::std::option::Option::Some(__usage_env);
}
}
}
});
let names = if record_deprecated.is_some() {
quote!([#(#vars),*].into_iter().enumerate())
} else {
quote!([#(#vars),*])
};
let bind = if record_deprecated.is_some() {
quote!((__usage_index, __usage_env))
} else {
quote!(__usage_env)
};
let standing_held = unless_standing(f);
Some(quote! {
if #active !partial.#given #standing #standing_held {
for #bind in #names {
if let ::std::result::Result::Ok(value) = ::std::env::var(__usage_env) {
let mut continue_unset = false;
#assign
if !continue_unset {
partial.#given = true;
#record_deprecated
break;
}
}
}
}
})
});
let flattened = cli.fields.iter().filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
let ident = &f.ident;
let standing = standing_ident(f);
Some(if filter_view {
quote! {
match #standing {
::std::option::Option::Some(__usage_s) => {
<#ty as usage_argv::spec::CommandArgs>::apply_env_update(
&mut partial.#ident,
__usage_s,
);
}
::std::option::Option::None => {
<#ty as usage_argv::spec::CommandArgs>::apply_env_for_view(
&mut partial.#ident,
__usage_view,
);
}
}
}
} else {
quote! {
<#ty as usage_argv::spec::CommandArgs>::apply_env(&mut partial.#ident);
}
})
});
let selected = cli.fields.iter().find_map(|f| {
let Kind::Subcommand { ty, .. } = &f.kind else {
return None;
};
let standing = standing_ident(f);
Some(if filter_view {
quote! {
match (#standing, __usage_view) {
(::std::option::Option::Some(__usage_s), _) => {
<#ty as usage_argv::spec::Subcommands>::apply_env_update(
&mut partial.__usage_sub,
partial.__usage_selected,
__usage_s,
);
}
(::std::option::Option::None, ::std::option::Option::Some(__usage_view)) => {
<#ty as usage_argv::spec::Subcommands>::apply_env_for_view_path(
&mut partial.__usage_sub,
partial.__usage_selected,
__usage_view.root.split_ascii_whitespace().count(),
);
}
(::std::option::Option::None, ::std::option::Option::None) => {
<#ty as usage_argv::spec::Subcommands>::apply_env(
&mut partial.__usage_sub,
partial.__usage_selected,
);
}
}
}
} else {
quote! {
<#ty as usage_argv::spec::Subcommands>::apply_env(
&mut partial.__usage_sub,
partial.__usage_selected,
);
}
})
});
quote! {
#(#own)*
#(#flattened)*
#selected
}
}
fn deprecations_fn(cli: &Cli) -> TokenStream {
let own = cli.fields.iter().filter_map(|f| {
let Kind::Flag { longs, shorts, .. } = &f.kind else {
return None;
};
if !tracks_deprecated(f) {
return None;
}
let given = format_ident!("__given_{}", f.ident);
let spelling = match (longs.first(), shorts.first()) {
(Some(long), _) => format!("--{long}"),
(None, Some(short)) => format!("-{short}"),
(None, None) => f.name.clone(),
};
let message = option_str(f.deprecated.as_deref());
let warn_at = option_str(f.deprecated_warn_at.as_deref());
let remove_at = option_str(f.deprecated_remove_at.as_deref());
Some(quote! {
if partial.#given {
out.push(usage_argv::warn::Warning::flag(
#spelling,
#message,
#warn_at,
#remove_at,
));
}
})
});
let aliases = cli.fields.iter().filter_map(|f| {
if !tracks_deprecated_env(f) {
return None;
}
let recorded = deprecated_env_ident(f);
let replacement = option_str(
f.env
.as_deref()
.or_else(|| f.env_fallback.first().map(String::as_str)),
);
Some(quote! {
if let ::std::option::Option::Some(__usage_env) = partial.#recorded {
out.push(usage_argv::warn::Warning::env(__usage_env, #replacement));
}
})
});
let flattened = cli.fields.iter().filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
let ident = &f.ident;
Some(quote! {
<#ty as usage_argv::spec::CommandArgs>::deprecations(&partial.#ident, out);
})
});
let selected = cli.fields.iter().find_map(|f| {
let Kind::Subcommand { ty, .. } = &f.kind else {
return None;
};
Some(quote! {
if let ::std::option::Option::Some(__usage_at) = partial.__usage_selected {
match partial.__usage_view {
::std::option::Option::Some(__usage_view) => {
<#ty as usage_argv::spec::Subcommands>::deprecations_for_view_path(
&partial.__usage_sub,
::std::option::Option::Some(__usage_at),
__usage_view.root.split_ascii_whitespace().count(),
out,
);
}
::std::option::Option::None => {
<#ty as usage_argv::spec::Subcommands>::deprecations(
&partial.__usage_sub,
::std::option::Option::Some(__usage_at),
out,
);
}
}
}
})
});
quote! {
pub fn deprecations(
partial: &Partial,
out: &mut ::std::vec::Vec<usage_argv::warn::Warning<'static>>,
) {
let _ = (&partial, &mut *out);
#(#own)*
#(#aliases)*
#(#flattened)*
#selected
}
}
}
fn argument_state_standing(cli: &Cli) -> TokenStream {
let overlays = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, multiple, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
let standing = standing_ident(field);
let group = quote!(<#ty as usage_argv::spec::ArgGroup>);
let read_standing = if *multiple {
quote! {
for __usage_member in __usage_s {
if let ::std::option::Option::Some(__usage_standing_state) =
#group::standing_state(__usage_member, selector)
{
if __usage_standing_state.given {
return ::std::option::Option::Some(__usage_standing_state);
}
}
}
}
} else {
quote! {
if let ::std::option::Option::Some(__usage_standing_state) =
#group::standing_state(__usage_s, selector)
{
if __usage_standing_state.given {
return ::std::option::Option::Some(__usage_standing_state);
}
}
}
};
Some(quote! {
if #group::any_given(&partial.#ident).is_none() {
if let ::std::option::Option::Some(__usage_s) = #standing {
#read_standing
}
}
})
});
let match_overlays = cli.fields.iter().filter_map(|field| {
let Kind::ArgGroup { ty, multiple, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
let standing = standing_ident(field);
let group = quote!(<#ty as usage_argv::spec::ArgGroup>);
let read_standing = if *multiple {
quote! {
for __usage_member in __usage_s {
if #group::standing_matches(__usage_member, selector, value)
== ::std::option::Option::Some(true)
{
return ::std::option::Option::Some(true);
}
}
}
} else {
quote! {
if #group::standing_matches(__usage_s, selector, value)
== ::std::option::Option::Some(true)
{
return ::std::option::Option::Some(true);
}
}
};
Some(quote! {
if #group::any_given(&partial.#ident).is_none() {
if let ::std::option::Option::Some(__usage_s) = #standing {
#read_standing
}
}
})
});
let clause_overlays = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
let current = format_ident!("__usage_current_{}", ident);
let standing = standing_ident(field);
Some(quote! {
if !partial.#ident.iter().chain(::core::iter::once(&partial.#current)).any(
|__usage_instance| {
<#ty as usage_argv::spec::CommandArgs>::any_given(__usage_instance).is_some()
},
) {
if let ::std::option::Option::Some(__usage_s) = #standing {
for __usage_instance in __usage_s {
if let ::std::option::Option::Some(__usage_standing_state) =
<#ty as usage_argv::spec::CommandArgs>::standing_state(
__usage_instance,
selector,
)
{
if __usage_standing_state.given
|| __usage_standing_state.satisfied
{
return ::std::option::Option::Some(__usage_standing_state);
}
}
}
}
}
})
});
let clause_match_overlays = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
let current = format_ident!("__usage_current_{}", ident);
let standing = standing_ident(field);
Some(quote! {
if !partial.#ident.iter().chain(::core::iter::once(&partial.#current)).any(
|__usage_instance| {
<#ty as usage_argv::spec::CommandArgs>::any_given(__usage_instance).is_some()
},
) {
if let ::std::option::Option::Some(__usage_s) = #standing {
for __usage_instance in __usage_s {
if <#ty as usage_argv::spec::CommandArgs>::standing_matches(
__usage_instance,
selector,
value,
) == ::std::option::Option::Some(true)
{
return ::std::option::Option::Some(true);
}
}
}
}
})
});
quote! {
#[allow(dead_code)]
let __usage_argument_state =
|partial: &Partial, selector: &str| -> ::std::option::Option<
usage_argv::spec::ArgumentState,
> {
match argument_state(partial, selector) {
::std::option::Option::Some(state) if state.given || state.satisfied => {
::std::option::Option::Some(state)
}
recognized => {
#(#overlays)*
#(#clause_overlays)*
recognized
}
}
};
#[allow(dead_code)]
let __usage_argument_matches =
|partial: &Partial, selector: &str, value: &[u8]|
-> ::std::option::Option<bool> {
match argument_matches(partial, selector, value) {
::std::option::Option::Some(true) => ::std::option::Option::Some(true),
recognized => {
#(#match_overlays)*
#(#clause_match_overlays)*
recognized
}
}
};
}
}
fn post_binding(cli: &Cli) -> TokenStream {
let policy_given = standing_policy_given;
let semantic_given = standing_semantic_given;
let standing_locals = standing_locals(cli);
let argument_state_standing = argument_state_standing(cli);
let sub_check = subcommand_parts(cli).map(|p| p.check).unwrap_or_default();
let subcommand_satisfies_requirements = if cli.subcommand_negates_reqs
&& cli
.fields
.iter()
.any(|field| matches!(field.kind, Kind::Subcommand { .. }))
{
let standing = cli
.fields
.iter()
.find(|field| matches!(field.kind, Kind::Subcommand { .. }))
.and_then(standing_flag)
.unwrap_or_else(|| quote!(false));
quote!((partial.__usage_selected.is_some() || #standing))
} else {
quote!(false)
};
let direct_exclusive_present = cli.fields.iter().filter_map(|field| {
if !field.exclusive {
return None;
}
Some(policy_given(field))
});
let flattened_exclusive_present = cli.fields.iter().filter_map(|field| {
let Kind::Flatten { ty, .. } = &field.kind else {
return None;
};
let ident = &field.ident;
Some(quote! {
<#ty as usage_argv::spec::CommandArgs>::exclusive_given(&partial.#ident).is_some()
})
});
let selected_exclusive_present = cli.fields.iter().filter_map(|field| {
let Kind::Subcommand { ty, .. } = &field.kind else {
return None;
};
Some(quote! {
<#ty as usage_argv::spec::Subcommands>::exclusive_given(
&partial.__usage_sub,
partial.__usage_selected,
).is_some()
})
});
let exclusive_present = quote! {
false
#(|| #direct_exclusive_present)*
#(|| #flattened_exclusive_present)*
#(|| #selected_exclusive_present)*
};
let flattened_checks = cli.fields.iter().filter_map(|f| {
let Kind::Flatten { ty, .. } = &f.kind else {
return None;
};
let ident = &f.ident;
let standing = standing_ident(f);
Some(quote! {
if !__usage_exclusive_present
|| <#ty as usage_argv::spec::CommandArgs>::exclusive_given(&partial.#ident)
.is_some()
{
match #standing {
::std::option::Option::Some(__usage_s) => {
<#ty as usage_argv::spec::CommandArgs>
::check_update_with_args_override_self(
&mut partial.#ident,
args_override_self,
__usage_s,
)?;
}
::std::option::Option::None => {
<#ty as usage_argv::spec::CommandArgs>
::check_with_args_override_self_for_view(
&mut partial.#ident,
args_override_self,
__usage_view,
)?;
}
}
}
})
});
let clause_checks = cli.fields.iter().filter_map(|field| {
let Kind::Clause { ty, separator } = &field.kind else {
return None;
};
let ident = &field.ident;
let current = format_ident!("__usage_current_{}", ident);
let check_current = if separator.is_some() {
quote!(true)
} else {
quote!(
<#ty as usage_argv::spec::CommandArgs>::any_given(&partial.#current).is_some()
)
};
Some(quote! {
if !__usage_exclusive_present
|| <#ty as usage_argv::spec::CommandArgs>::exclusive_given(&partial.#current)
.is_some()
{
for __usage_instance in &mut partial.#ident {
<#ty as usage_argv::spec::CommandArgs>::check_with_args_override_self(
__usage_instance,
false,
)?;
}
if #check_current {
<#ty as usage_argv::spec::CommandArgs>::check_with_args_override_self(
&mut partial.#current,
false,
)?;
}
}
})
});
let duplicate_checks = cli
.fields
.iter()
.filter(|f| rejects_duplicate(cli, f))
.map(|f| {
let duplicated = format_ident!("__duplicated_{}", f.ident);
let name = &f.name;
quote! {
if !args_override_self && partial.#duplicated {
return ::std::result::Result::Err(
usage_argv::Error::DuplicateFlag { name: #name },
);
}
}
});
let declared_defaults = declared_defaults(cli, true);
let env_fallbacks = env_fallbacks(cli, true);
let required_checks = cli.fields.iter().filter_map(|f| {
if !(f.shape == Shape::Required || f.required_collection) || f.has_default() {
return None;
}
let given = format_ident!("__given_{}", f.ident);
let active = view_field_active(f);
let name = &f.name;
let standing = displaced_guard(cli, f);
let standing_held = unless_standing(f);
Some(quote! {
if #active && !partial.#given #standing #standing_held {
return ::std::result::Result::Err(
usage_argv::Error::MissingRequired { name: #name },
);
}
})
});
let choice_checks = cli.fields.iter().filter_map(|f| {
if (f.choices.is_empty() && !f.value_enum) || f.allow_unknown_choices {
return None;
}
let ident = &f.ident;
let name = &f.name;
let choices: TokenStream = match (f.value_enum, f.value_ty.as_ref()) {
(true, Some(ty)) => {
quote!(<#ty as usage_argv::spec::ValueEnum>::CHOICES)
}
_ => {
let list = &f.choices;
quote!(&[#(#list),*])
}
};
let accepted_choices = accepted_choices(f);
let ignore_case = choice_ignore_case(f);
let invalid = format_ident!("__invalid_choice_{}", ident);
let values = match f.shape {
Shape::Optional => quote!(partial.#ident.iter()),
Shape::Required => quote!(::std::iter::once(&partial.#ident)),
Shape::Many => quote!(partial.#ident.iter()),
Shape::Bool | Shape::Count => return None,
};
let active = view_field_active(f);
let standing_only = unless_standing_only(f);
let given = format_ident!("__given_{}", ident);
Some(quote! {
if #active && partial.#invalid {
return ::std::result::Result::Err(
usage_argv::Error::InvalidChoice {
name: #name,
choices: #choices,
},
);
}
if #active && partial.#given #standing_only {
for value in #values {
let ::std::result::Result::Ok(__usage_text) = ::std::str::from_utf8(value)
else {
continue;
};
if !usage_argv::spec::choice_matches(#accepted_choices, __usage_text, #ignore_case) {
return ::std::result::Result::Err(
usage_argv::Error::InvalidChoice {
name: #name,
choices: #choices,
},
);
}
}
}
})
});
let validation_checks = cli.fields.iter().filter_map(|f| {
let expression = f.validate.as_ref()?;
let ident = &f.ident;
let name = &f.name;
let message = f
.validate_error
.as_deref()
.unwrap_or("does not satisfy the validation expression");
let values = match f.shape {
Shape::Optional => quote!(partial.#ident.iter()),
Shape::Required => quote!(::std::iter::once(&partial.#ident)),
Shape::Many => quote!(partial.#ident.iter()),
Shape::Bool | Shape::Count => return None,
};
let active = view_field_active(f);
let standing_only = unless_standing_only(f);
Some(quote! {
if #active #standing_only {
for value in #values {
let ::std::result::Result::Ok(__usage_text) = ::std::str::from_utf8(value)
else {
continue;
};
let __usage_reason = match usage_validation::validate(#expression, __usage_text) {
::std::result::Result::Ok(true) => continue,
::std::result::Result::Ok(false) => #message.to_string(),
::std::result::Result::Err(error) =>
::std::format!("validation expression failed: {error}"),
};
return ::std::result::Result::Err(
usage_argv::Error::InvalidValue(::std::boxed::Box::new(
usage_argv::InvalidValue {
name: #name,
value: __usage_text.to_string(),
reason: __usage_reason,
},
)),
);
}
}
})
});
let bound_checks = cli.fields.iter().filter_map(|f| {
let (var_min, var_max) = if matches!(f.kind, Kind::Flag { variadic: true, .. }) {
(f.value_var_min, f.value_var_max)
} else {
(f.var_min, f.var_max)
};
if (var_min.is_none() && var_max.is_none()) || f.shape != Shape::Many {
return None;
}
let ident = &f.ident;
let name = &f.name;
let min = match var_min {
Some(min) => quote! {
if got < #min {
return ::std::result::Result::Err(
usage_argv::Error::VarTooFew { name: #name, min: #min, got },
);
}
},
None => quote!(),
};
let counts_occurrences = matches!(
&f.kind,
Kind::Flag {
variadic: false,
..
}
) && f.repeatable;
let max = match var_max.filter(|_| counts_occurrences) {
Some(max) => quote! {
if got > #max {
return ::std::result::Result::Err(
usage_argv::Error::VarTooMany { name: #name, max: #max, got },
);
}
},
None => quote!(),
};
let given = format_ident!("__given_{}", ident);
let active = view_field_active(f);
Some(quote! {
if #active && partial.#given {
let got = partial.#ident.len();
#min
#max
}
})
});
let conflict_fields = cli
.fields
.iter()
.filter(|field| matches!(field.kind, Kind::Flag { .. }))
.chain(
cli.fields
.iter()
.filter(|field| !matches!(field.kind, Kind::Flag { .. })),
);
let conflict_checks = conflict_fields.flat_map(move |f| {
let given = policy_given(f);
let name = &f.name;
f.conflicts.iter().map(move |selector| {
if let Some(other) = cli.field_for_selector(selector) {
let other_given = policy_given(other);
let other_name = &other.name;
quote! {
if #given && #other_given {
return ::std::result::Result::Err(
usage_argv::Error::ConflictingFlags {
name: #name,
other: #other_name,
},
);
}
}
} else {
quote! {
if #given {
if let ::std::option::Option::Some(other) =
__usage_argument_state(partial, #selector)
{
if other.given {
return ::std::result::Result::Err(
usage_argv::Error::ConflictingFlags {
name: #name,
other: other.name,
},
);
}
}
}
}
}
})
});
let requirement_checks = cli.fields.iter().flat_map(move |f| {
let given = policy_given(f);
f.requires.iter().map(move |selector| {
let Some(other) = cli.field_for_selector(selector) else {
return quote! {
if #given {
match __usage_argument_state(partial, #selector) {
::std::option::Option::Some(other) if other.satisfied => {}
::std::option::Option::Some(other) => {
return ::std::result::Result::Err(
usage_argv::Error::MissingRequired { name: other.name },
);
}
::std::option::Option::None => {}
}
}
};
};
if other.has_default() {
return quote!();
}
let other_given = semantic_given(other);
let other_name = &other.name;
let missing = quote! {
return ::std::result::Result::Err(
usage_argv::Error::MissingRequired { name: #other_name },
);
};
match default_if_would_apply(cli, other, Lookup::Standing) {
Some(pred) => quote! {
if #given && !(#other_given) && !(#pred) {
#missing
}
},
None => quote! {
if #given && !(#other_given) {
#missing
}
},
}
})
});
let conditional_requirement_checks = cli.fields.iter().flat_map(move |f| {
let given = policy_given(f);
let ident = &f.ident;
f.requires_if.iter().map(move |condition| {
let external = cli.field_for_selector(&condition.requires).is_none();
let other = cli.field_for_selector(&condition.requires);
if external {
let selector = &condition.requires;
let value = &condition.value;
let matches = match f.shape {
Shape::Optional => {
quote!(partial.#ident.as_deref().is_some_and(|v| v == #value.as_bytes()))
}
Shape::Required => quote!(partial.#ident.as_slice() == #value.as_bytes()),
Shape::Many => {
quote!(partial.#ident.iter().any(|v| v.as_slice() == #value.as_bytes()))
}
Shape::Bool => match value.as_str() {
"true" => quote!(partial.#ident),
"false" => quote!(!partial.#ident),
_ => quote!(false),
},
Shape::Count => match value.as_str() {
"true" => quote!(partial.#ident > 0),
"false" => quote!(partial.#ident == 0),
_ => quote!(false),
},
};
return quote! {
if #given && #matches {
match __usage_argument_state(partial, #selector) {
::std::option::Option::Some(other) if other.satisfied => {}
::std::option::Option::Some(other) => {
return ::std::result::Result::Err(
usage_argv::Error::MissingRequired { name: other.name },
);
}
::std::option::Option::None => {}
}
}
};
}
let other = other.expect("the local relationship was resolved above");
if other.has_default() {
return quote!();
}
let other_given = semantic_given(other);
let other_name = &other.name;
let value = &condition.value;
let matches = match f.shape {
Shape::Optional => quote!(
partial.#ident.as_deref().is_some_and(|v| v == #value.as_bytes())
),
Shape::Required => quote!(partial.#ident.as_slice() == #value.as_bytes()),
Shape::Many => quote!(
partial.#ident.iter().any(|v| v.as_slice() == #value.as_bytes())
),
Shape::Bool => match value.as_str() {
"true" => quote!(partial.#ident),
"false" => quote!(!partial.#ident),
_ => quote!(false),
},
Shape::Count => match value.as_str() {
"true" => quote!(partial.#ident > 0),
"false" => quote!(partial.#ident == 0),
_ => quote!(false),
},
};
let unless_default_if = match default_if_would_apply(cli, other, Lookup::Standing) {
Some(pred) => quote!(&& !(#pred)),
None => quote!(),
};
quote! {
if #given && #matches && !(#other_given) #unless_default_if {
return ::std::result::Result::Err(
usage_argv::Error::MissingRequired { name: #other_name },
);
}
}
})
});
let exclusive_checks = cli
.fields
.iter()
.filter(|f| f.exclusive)
.flat_map(move |f| {
let given = policy_given(f);
let name = &f.name;
cli.fields
.iter()
.filter(move |other| other.ident != f.ident)
.filter(|other| {
!matches!(
other.kind,
Kind::Subcommand { .. }
| Kind::Flatten { .. }
| Kind::ArgGroup { .. }
| Kind::Skip
)
})
.map(move |other| {
let other_given = policy_given(other);
let other_name = &other.name;
quote! {
if #given && #other_given {
return ::std::result::Result::Err(
usage_argv::Error::ConflictingFlags {
name: #other_name,
other: #name,
},
);
}
}
})
.collect::<Vec<_>>()
});
let direct_given = cli
.fields
.iter()
.filter(|field| {
!matches!(
field.kind,
Kind::Flatten { .. } | Kind::ArgGroup { .. } | Kind::Subcommand { .. } | Kind::Skip
)
})
.rev()
.fold(quote!(::std::option::Option::None), |rest, field| {
let given = policy_given(field);
let name = &field.name;
quote!(if #given { ::std::option::Option::Some(#name) } else { #rest })
});
let direct_exclusive = cli
.fields
.iter()
.filter(|field| field.exclusive)
.rev()
.fold(quote!(::std::option::Option::None), |rest, field| {
let given = policy_given(field);
let name = &field.name;
quote!(if #given { ::std::option::Option::Some(#name) } else { #rest })
});
let has_flatten = cli
.fields
.iter()
.any(|field| matches!(field.kind, Kind::Flatten { .. } | Kind::ArgGroup { .. }));
let flattened_segments = cli.fields.iter().filter_map(|field| {
let ident = &field.ident;
let standing = standing_ident(field);
match &field.kind {
Kind::Flatten { ty, .. } => Some(quote! {
(
<#ty as usage_argv::spec::CommandArgs>::any_given(&partial.#ident)
.or_else(|| {
#standing.and_then(
<#ty as usage_argv::spec::CommandArgs>::any_standing,
)
}),
<#ty as usage_argv::spec::CommandArgs>::exclusive_given(&partial.#ident),
),
}),
Kind::ArgGroup { ty, .. } => {
let name = &field.name;
Some(quote! {
(
<#ty as usage_argv::spec::ArgGroup>::any_given(&partial.#ident)
.or_else(|| #standing.map(|_| #name)),
::std::option::Option::None,
),
})
}
_ => None,
}
});
let subcommand_segment = cli.fields.iter().find_map(|field| {
let Kind::Subcommand { ty, .. } = &field.kind else {
return None;
};
let name = &field.name;
let standing = standing_ident(field);
Some(quote! {
(
partial
.__usage_selected
.map(|_| #name)
.or_else(|| #standing.map(|_| #name)),
<#ty as usage_argv::spec::Subcommands>::exclusive_given(
&partial.__usage_sub,
partial.__usage_selected,
),
),
})
});
let exclusive_cross_checks = (has_flatten || subcommand_segment.is_some()).then(|| {
quote! {
let __usage_exclusive_segments = [
(#direct_given, #direct_exclusive),
#(#flattened_segments)*
#subcommand_segment
];
for __usage_i in 0..__usage_exclusive_segments.len() {
if let ::std::option::Option::Some(exclusive) =
__usage_exclusive_segments[__usage_i].1
{
for __usage_j in 0..__usage_exclusive_segments.len() {
if __usage_i == __usage_j {
continue;
}
if let ::std::option::Option::Some(other) =
__usage_exclusive_segments[__usage_j].0
{
return ::std::result::Result::Err(
usage_argv::Error::ConflictingFlags {
name: other,
other: exclusive,
},
);
}
}
}
}
}
});
let group_checks = declared_groups(cli)
.into_iter()
.map(|(name, required, multiple, members)| {
let fields: Vec<&Field> = members
.iter()
.filter_map(|selector| cli.field_for_selector(selector))
.collect();
let given: Vec<TokenStream> = fields.iter().map(|f| policy_given(f)).collect();
let exclusivity = (!multiple).then(|| {
let names: Vec<&String> = fields.iter().map(|f| &f.name).collect();
let pairs = (0..fields.len()).flat_map(|i| {
let (later, earlier) = (given.clone(), given.clone());
let (later_names, earlier_names) = (names.clone(), names.clone());
((i + 1)..fields.len())
.map(move |j| {
let (a, b) = (&earlier[i], &later[j]);
let (name_a, name_b) = (earlier_names[i], later_names[j]);
quote! {
if #a && #b {
return ::std::result::Result::Err(
usage_argv::Error::ConflictingFlags {
name: #name_b,
other: #name_a,
},
);
}
}
})
.collect::<Vec<_>>()
});
quote!(#(#pairs)*)
});
let requiredness = required.then(|| {
let selectors = &members;
let active: Vec<TokenStream> =
fields.iter().map(|f| view_field_active(f)).collect();
let view_members = cli.views.iter().map(|view| {
let id = &view.id;
let carried: Vec<&String> = fields
.iter()
.zip(&members)
.filter_map(|(field, selector)| {
field_active_in_view(field, view).then_some(selector)
})
.collect();
quote!(#id => &[#(#carried),*])
});
let filled: Vec<TokenStream> = fields
.iter()
.zip(&given)
.zip(&active)
.map(|((field, given), active)| {
if !field.has_default() {
quote!((#active) && (#given))
} else {
quote!(#active)
}
})
.collect();
quote! {
if (#(#active)||*) && !(#(#filled)||*) {
let __usage_group_members: &'static [&'static str] =
match __usage_view {
::std::option::Option::None => &[#(#selectors),*],
::std::option::Option::Some(__usage_group_view) => {
match __usage_group_view.id {
#(#view_members,)*
_ => &[#(#selectors),*],
}
}
};
return ::std::result::Result::Err(
usage_argv::Error::MissingGroup {
group: #name,
members: __usage_group_members,
},
);
}
}
});
(exclusivity, requiredness)
})
.collect::<Vec<_>>();
let mut group_exclusivity_checks: Vec<TokenStream> =
group_checks.iter().filter_map(|(e, _)| e.clone()).collect();
let mut group_required_checks: Vec<TokenStream> =
group_checks.iter().filter_map(|(_, r)| r.clone()).collect();
for field in &cli.fields {
let Kind::ArgGroup {
ty,
optional,
multiple,
} = &field.kind
else {
continue;
};
let ident = &field.ident;
let group = quote!(<#ty as usage_argv::spec::ArgGroup>);
if !multiple {
group_exclusivity_checks.push(quote! {
if let ::std::option::Option::Some((__usage_earlier, __usage_later)) =
#group::conflict(&partial.#ident)
{
return ::std::result::Result::Err(
usage_argv::Error::ConflictingFlags {
name: __usage_later,
other: __usage_earlier,
},
);
}
});
}
if *optional || *multiple {
continue;
}
let active = view_field_active(field);
let standing_held = unless_standing(field);
group_required_checks.push(quote! {
if #active && #group::any_given(&partial.#ident).is_none() #standing_held {
return ::std::result::Result::Err(
usage_argv::Error::MissingGroup {
group: #group::NAME,
members: #group::MEMBERS,
},
);
}
});
}
let relationship_required_checks = cli.fields.iter().filter_map(move |f| {
if f.required_if.is_empty()
&& f.required_if_eq.is_empty()
&& f.required_if_eq_all.is_empty()
&& f.required_unless.is_empty()
&& f.required_unless_all.is_empty()
{
return None;
}
if f.has_default() {
return None;
}
let given = format_ident!("__given_{}", f.ident);
let active = view_field_active(f);
let name = &f.name;
let selector_given = |selector: &String| {
Some(match cli.field_for_selector(selector) {
Some(other) => policy_given(other),
None => quote!(
__usage_argument_state(partial, #selector).is_some_and(|state| state.given)
),
})
};
let if_given: Vec<_> = f.required_if.iter().filter_map(selector_given).collect();
let unless_given: Vec<_> = f
.required_unless
.iter()
.filter_map(selector_given)
.collect();
let if_eq: Vec<_> = f
.required_if_eq
.iter()
.map(|condition| {
let selector = cli
.field_for_selector(&condition.selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| condition.selector.clone());
let value = &condition.value;
quote!(
__usage_argument_matches(partial, #selector, #value.as_bytes())
.unwrap_or(false)
)
})
.collect();
let if_eq_all: Vec<_> = f
.required_if_eq_all
.iter()
.map(|condition| {
let selector = cli
.field_for_selector(&condition.selector)
.and_then(Cli::selector_for_field)
.unwrap_or_else(|| condition.selector.clone());
let value = &condition.value;
quote!(
__usage_argument_matches(partial, #selector, #value.as_bytes())
.unwrap_or(false)
)
})
.collect();
let unless_all_given: Vec<_> = f
.required_unless_all
.iter()
.filter_map(selector_given)
.collect();
let missing = quote! {
return ::std::result::Result::Err(
usage_argv::Error::MissingRequired { name: #name },
);
};
let required_if = (!if_given.is_empty()).then(|| {
quote! {
if #(#if_given)||* {
#missing
}
}
});
let required_if_eq = (!if_eq.is_empty()).then(|| {
quote! {
if #(#if_eq)||* {
#missing
}
}
});
let required_if_eq_all = (!if_eq_all.is_empty()).then(|| {
quote! {
if #(#if_eq_all)&&* {
#missing
}
}
});
let required_unless =
(!unless_given.is_empty() || !unless_all_given.is_empty()).then(|| {
let any = if unless_given.is_empty() {
quote!(false)
} else {
quote!(#(#unless_given)||*)
};
let all = if unless_all_given.is_empty() {
quote!(false)
} else {
quote!(#(#unless_all_given)&&*)
};
quote! {
if !(#any || #all) {
#missing
}
}
});
let standing_held = unless_standing(f);
Some(quote! {
if #active && !partial.#given #standing_held {
#required_if
#required_if_eq
#required_if_eq_all
#required_unless
}
})
});
quote! {
#standing_locals
#argument_state_standing
#env_fallbacks
#declared_defaults
let __usage_exclusive_present = #exclusive_present;
#(#duplicate_checks)*
#(#conflict_checks)*
#(#exclusive_checks)*
#exclusive_cross_checks
#(#group_exclusivity_checks)*
#(#flattened_checks)*
#(#clause_checks)*
if !__usage_exclusive_present && !(#subcommand_satisfies_requirements) {
#(#requirement_checks)*
#(#conditional_requirement_checks)*
#(#required_checks)*
#(#group_required_checks)*
#(#relationship_required_checks)*
}
#(#choice_checks)*
#(#validation_checks)*
#(#bound_checks)*
#sub_check
}
}
pub fn emit_value_enum(value_enum: &ValueEnum) -> TokenStream {
let ident = &value_enum.ident;
let runtime = runtime_path();
let words = value_enum.variants.iter().flat_map(|value| {
let cfg = &value.cfg_attrs;
::std::iter::once((!value.hide).then_some(&value.name))
.chain(
value
.aliases
.iter()
.map(|alias| (!alias.hide).then_some(&alias.name)),
)
.flatten()
.map(move |word| quote!(#(#cfg)* #word))
});
let accepted = value_enum.variants.iter().flat_map(|value| {
let cfg = &value.cfg_attrs;
::std::iter::once(&value.name)
.chain(value.aliases.iter().map(|alias| &alias.name))
.map(move |word| quote!(#(#cfg)* #word))
});
let aliases = value_enum.variants.iter().flat_map(|value| {
let canonical = &value.name;
let cfg = &value.cfg_attrs;
value.aliases.iter().map(move |alias| {
let alias = &alias.name;
quote!(#(#cfg)* (#canonical, #alias))
})
});
let details = value_enum.variants.iter().map(|value| {
let canonical = &value.name;
let help = option_str(value.help.as_deref());
let hide = value.hide;
let cfg = &value.cfg_attrs;
let aliases = value.aliases.iter().map(|alias| {
let name = &alias.name;
let hide = alias.hide;
quote!(usage_argv::spec::ChoiceAliasMeta { value: #name, hide: #hide })
});
quote!(
#(#cfg)* usage_argv::spec::ChoiceMeta {
value: #canonical,
help: #help,
hide: #hide,
aliases: &[#(#aliases),*],
}
)
});
let ignore_case = value_enum.ignore_case;
let parse_arms = value_enum.variants.iter().map(|value| {
let ident = &value.ident;
let cfg = &value.cfg_attrs;
let names = ::std::iter::once(&value.name).chain(value.aliases.iter().map(|a| &a.name));
quote! {
#(#cfg)*
if [#(#names),*].iter().any(|candidate| {
*candidate == value || #ignore_case && candidate.eq_ignore_ascii_case(value)
}) {
return ::std::option::Option::Some(Self::#ident);
}
}
});
quote! {
#[doc(hidden)]
const _: () = {
use #runtime as usage_argv;
impl usage_argv::spec::ValueEnum for #ident {
const CHOICES: &'static [&'static str] = &[#(#words),*];
const ACCEPTED_CHOICES: &'static [&'static str] = &[#(#accepted),*];
const ALIASES: &'static [(&'static str, &'static str)] = &[#(#aliases),*];
const DETAILS: &'static [usage_argv::spec::ChoiceMeta<'static>] = &[#(#details),*];
const IGNORE_CASE: bool = #ignore_case;
fn from_choice(value: &str) -> ::std::option::Option<Self> {
#(#parse_arms)*
::std::option::Option::None
}
}
};
}
}
pub fn emit_arg_group(group: &ArgGroup) -> TokenStream {
let ident = &group.ident;
let runtime = runtime_path();
let name = &group.name;
let multiple = group.multiple;
let declaration = declaration_hash(&group.fingerprint);
let key_decls = group.variants.iter().enumerate().map(|(i, member)| {
let key = key_ident("FLAG", Some(i));
let index = i as u64;
let cfg = &member.cfg_attrs;
quote!(#(#cfg)* const #key: u64 = __USAGE_KEY_BASE | #KIND_FLAG | #index;)
});
let flags = group.variants.iter().enumerate().map(|(i, member)| {
let table = format_ident!("FLAG_{i}");
let key = key_ident("FLAG", Some(i));
let cfg = &member.cfg_attrs;
let long = &member.name;
let shorts: Vec<u8> = member.short.map(|short| short as u8).into_iter().collect();
let shape = if member.value_ty.is_some() {
quote!(usage_argv::Flag::VALUE)
} else {
quote!(usage_argv::Flag::BOOL)
};
quote! {
#(#cfg)*
pub static #table: usage_argv::Flag = usage_argv::Flag {
key: #key,
name: #long,
longs: &[#long],
shorts: &[#(#shorts),*],
..#shape
};
}
});
let flag_refs = group.variants.iter().enumerate().map(|(i, member)| {
let table = format_ident!("FLAG_{i}");
let cfg = &member.cfg_attrs;
quote!(#(#cfg)* &#table)
});
let flag_metas = group.variants.iter().enumerate().map(|(i, member)| {
let table = format_ident!("FLAG_{i}");
let cfg = &member.cfg_attrs;
let help = option_str(member.help.as_deref());
let long_help = option_str(member.long_help.as_deref());
let hide = member.hide;
let value_name = option_str(member.value_name.as_deref());
let (accepted_choices, choices, choice_aliases, choice_details, ignore_case) =
match (&member.value_ty, member.value_enum) {
(Some(ty), true) => (
quote!(<#ty as usage_argv::spec::ValueEnum>::ACCEPTED_CHOICES),
quote!(<#ty as usage_argv::spec::ValueEnum>::CHOICES),
quote!(<#ty as usage_argv::spec::ValueEnum>::ALIASES),
quote!(<#ty as usage_argv::spec::ValueEnum>::DETAILS),
quote!(<#ty as usage_argv::spec::ValueEnum>::IGNORE_CASE),
),
_ => (
quote!(&[]),
quote!(&[]),
quote!(&[]),
quote!(&[]),
quote!(false),
),
};
quote! {
#(#cfg)*
usage_argv::spec::FlagMeta {
flag: &#table,
help: #help,
long_help: #long_help,
hide: #hide,
repeatable: #multiple,
value_name: #value_name,
accepted_choices: #accepted_choices,
choices: #choices,
choice_aliases: #choice_aliases,
choice_details: #choice_details,
ignore_case: #ignore_case,
..usage_argv::spec::FlagMeta::EMPTY
}
}
});
let members = group.variants.iter().map(|member| {
let cfg = &member.cfg_attrs;
let selector = format!("--{}", member.name);
quote!(#(#cfg)* #selector)
});
let ordered_field = multiple.then(|| {
quote!(pub ordered: ::std::vec::Vec<(usize, ::std::option::Option<::std::vec::Vec<u8>>)> ,)
});
let partial_fields = group.variants.iter().enumerate().map(|(i, member)| {
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
if member.value_ty.is_some() {
quote!(#(#cfg)* pub #given: ::std::option::Option<::std::vec::Vec<u8>>,)
} else {
quote!(#(#cfg)* pub #given: bool,)
}
});
let apply_arms = group.variants.iter().enumerate().map(|(i, member)| {
let table = format_ident!("FLAG_{i}");
let key = key_ident("FLAG", Some(i));
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let record = if multiple {
if member.value_ty.is_some() {
quote!(partial.ordered.push((#i, value.map(<[u8]>::to_vec)));)
} else {
quote!(partial.ordered.push((#i, ::std::option::Option::None));)
}
} else {
quote!()
};
let assign = if member.value_ty.is_some() {
quote! {
if let ::std::option::Option::Some(value) = value {
partial.#given = ::std::option::Option::Some(value.to_vec());
}
}
} else {
quote!(partial.#given = true;)
};
quote! {
#(#cfg)*
#key if ::core::ptr::eq(*flag, &#table) => {
#record
#assign
true
}
}
});
let given_arms = group.variants.iter().enumerate().map(|(i, member)| {
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let name = &member.name;
let is_given = if member.value_ty.is_some() {
quote!(partial.#given.is_some())
} else {
quote!(partial.#given)
};
quote! {
#(#cfg)*
if #is_given {
return ::std::option::Option::Some(#name);
}
}
});
let conflict_arms: Vec<_> = group
.variants
.iter()
.enumerate()
.filter_map(|(i, member)| {
if multiple {
return None;
}
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let name = &member.name;
let is_given = if member.value_ty.is_some() {
quote!(partial.#given.is_some())
} else {
quote!(partial.#given)
};
Some(quote! {
#(#cfg)*
if #is_given {
if let ::std::option::Option::Some(__usage_earlier) = __usage_first {
return ::std::option::Option::Some((__usage_earlier, #name));
}
__usage_first = ::std::option::Option::Some(#name);
}
})
})
.collect();
let build_arms = group.variants.iter().enumerate().map(|(i, member)| {
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let variant = &member.ident;
let Some(ty) = &member.value_ty else {
return quote! {
#(#cfg)*
if partial.#given {
return ::std::result::Result::Ok(
::std::option::Option::Some(Self::#variant),
);
}
};
};
let name = &member.name;
let rendered = rendered_path(ty);
let converted = match rendered.as_str() {
"PathBuf" | "std::path::PathBuf" | "::std::path::PathBuf" => quote! {
match usage_argv::os_string_from_bytes(__usage_value) {
::std::result::Result::Ok(value) => ::std::path::PathBuf::from(value),
::std::result::Result::Err(bytes) => {
return ::std::result::Result::Err(
usage_argv::invalid_os_value(#name, bytes),
);
}
}
},
"OsString" | "std::ffi::OsString" | "::std::ffi::OsString" => quote! {
match usage_argv::os_string_from_bytes(__usage_value) {
::std::result::Result::Ok(value) => value,
::std::result::Result::Err(bytes) => {
return ::std::result::Result::Err(
usage_argv::invalid_os_value(#name, bytes),
);
}
}
},
_ if member.value_enum => quote! {{
let __usage_text = match ::std::string::String::from_utf8(__usage_value) {
::std::result::Result::Ok(text) => text,
::std::result::Result::Err(bad) => {
return ::std::result::Result::Err(
usage_argv::invalid_utf8_value(#name, bad),
);
}
};
match <#ty as usage_argv::spec::ValueEnum>::from_choice(&__usage_text) {
::std::option::Option::Some(value) => value,
::std::option::Option::None => {
return ::std::result::Result::Err(
usage_argv::invalid_choice_value(#name, __usage_text),
);
}
}
}},
_ => quote! {{
let __usage_text = match ::std::string::String::from_utf8(__usage_value) {
::std::result::Result::Ok(text) => text,
::std::result::Result::Err(bad) => {
return ::std::result::Result::Err(
usage_argv::invalid_utf8_value(#name, bad),
);
}
};
match ::std::str::FromStr::from_str(&__usage_text) {
::std::result::Result::Ok(value) => value,
::std::result::Result::Err(reason) => {
return ::std::result::Result::Err(
usage_argv::invalid_parsed_value(#name, __usage_text, &reason),
);
}
}
}},
};
quote! {
#(#cfg)*
if let ::std::option::Option::Some(__usage_value) = partial.#given.clone() {
let __usage_value: #ty = #converted;
return ::std::result::Result::Ok(
::std::option::Option::Some(Self::#variant(__usage_value)),
);
}
}
});
let build_many_arms = group.variants.iter().enumerate().map(|(i, member)| {
let cfg = &member.cfg_attrs;
let variant = &member.ident;
let Some(ty) = &member.value_ty else {
return quote! {
#(#cfg)*
#i => Self::#variant,
};
};
let name = &member.name;
let rendered = rendered_path(ty);
let converted = match rendered.as_str() {
"PathBuf" | "std::path::PathBuf" | "::std::path::PathBuf" => quote! {
match usage_argv::os_string_from_bytes(__usage_value) {
::std::result::Result::Ok(value) => ::std::path::PathBuf::from(value),
::std::result::Result::Err(bytes) => {
return ::std::result::Result::Err(
usage_argv::invalid_os_value(#name, bytes),
);
}
}
},
"OsString" | "std::ffi::OsString" | "::std::ffi::OsString" => quote! {
match usage_argv::os_string_from_bytes(__usage_value) {
::std::result::Result::Ok(value) => value,
::std::result::Result::Err(bytes) => {
return ::std::result::Result::Err(
usage_argv::invalid_os_value(#name, bytes),
);
}
}
},
_ if member.value_enum => quote! {{
let __usage_text = match ::std::string::String::from_utf8(__usage_value) {
::std::result::Result::Ok(text) => text,
::std::result::Result::Err(bad) => {
return ::std::result::Result::Err(
usage_argv::invalid_utf8_value(#name, bad),
);
}
};
match <#ty as usage_argv::spec::ValueEnum>::from_choice(&__usage_text) {
::std::option::Option::Some(value) => value,
::std::option::Option::None => {
return ::std::result::Result::Err(
usage_argv::invalid_choice_value(#name, __usage_text),
);
}
}
}},
_ => quote! {{
let __usage_text = match ::std::string::String::from_utf8(__usage_value) {
::std::result::Result::Ok(text) => text,
::std::result::Result::Err(bad) => {
return ::std::result::Result::Err(
usage_argv::invalid_utf8_value(#name, bad),
);
}
};
match ::std::str::FromStr::from_str(&__usage_text) {
::std::result::Result::Ok(value) => value,
::std::result::Result::Err(reason) => {
return ::std::result::Result::Err(
usage_argv::invalid_parsed_value(#name, __usage_text, &reason),
);
}
}
}},
};
quote! {
#(#cfg)*
#i => {
let __usage_value = __usage_value.clone().unwrap_or_default();
let __usage_value: #ty = #converted;
Self::#variant(__usage_value)
}
}
});
let member_selectors = |member: &ArgGroupMember| -> Vec<String> {
let mut selectors = vec![format!("--{}", member.name)];
if let Some(short) = member.short {
selectors.push(format!("-{short}"));
}
selectors
};
let state_arms = group.variants.iter().enumerate().map(|(i, member)| {
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let name = &member.name;
let selectors = member_selectors(member);
let is_given = if member.value_ty.is_some() {
quote!(partial.#given.is_some())
} else {
quote!(partial.#given)
};
quote! {
#(#cfg)*
#(#selectors)|* => {
::std::option::Option::Some(usage_argv::spec::ArgumentState {
name: #name,
given: #is_given,
satisfied: #is_given,
})
}
}
});
let standing_state_arms = group.variants.iter().map(|member| {
let cfg = &member.cfg_attrs;
let name = &member.name;
let variant = &member.ident;
let selectors = member_selectors(member);
let standing = if member.value_ty.is_some() {
quote!(::core::matches!(standing, Self::#variant(_)))
} else {
quote!(::core::matches!(standing, Self::#variant))
};
quote! {
#(#cfg)*
#(#selectors)|* => {
let __usage_given = #standing;
::std::option::Option::Some(usage_argv::spec::ArgumentState {
name: #name,
given: __usage_given,
satisfied: __usage_given,
})
}
}
});
let match_arms = group.variants.iter().enumerate().map(|(i, member)| {
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let selectors = member_selectors(member);
let matches = if member.value_enum {
let ty = member
.value_ty
.as_ref()
.expect("value_enum members were checked to carry a value");
quote! {
partial.#given.as_deref().is_some_and(|given| {
if <#ty as usage_argv::spec::ValueEnum>::IGNORE_CASE {
given.eq_ignore_ascii_case(value)
} else {
given == value
}
})
}
} else if member.value_ty.is_some() {
quote!(partial.#given.as_deref().is_some_and(|given| given == value))
} else {
quote!(partial.#given && value == b"true")
};
quote! {
#(#cfg)*
#(#selectors)|* => {
return ::std::option::Option::Some(#matches);
}
}
});
let standing_match_arms = group.variants.iter().map(|member| {
let cfg = &member.cfg_attrs;
let variant = &member.ident;
let selectors = member_selectors(member);
let Some(_) = &member.value_ty else {
return quote! {
#(#cfg)*
#(#selectors)|* => {
return ::std::option::Option::Some(
::core::matches!(standing, Self::#variant) && value == b"true",
);
}
};
};
quote! {
#(#cfg)*
#(#selectors)|* => {
return ::std::option::Option::None;
}
}
});
let displace_arms = group.variants.iter().enumerate().map(|(i, member)| {
let given = format_ident!("given_{i}");
let cfg = &member.cfg_attrs;
let selectors = member_selectors(member);
let clear_ordered =
multiple.then(|| quote!(partial.ordered.retain(|(__usage_at, _)| *__usage_at != #i);));
let clear = if member.value_ty.is_some() {
quote!(partial.#given = ::std::option::Option::None;)
} else {
quote!(partial.#given = false;)
};
quote! {
#(#cfg)*
#(#selectors)|* => {
#clear_ordered
#clear
return true;
}
}
});
let event_arms = group.variants.iter().enumerate().map(|(i, member)| {
let table = format_ident!("FLAG_{i}");
let key = key_ident("FLAG", Some(i));
let cfg = &member.cfg_attrs;
let selectors = member_selectors(member);
quote! {
#(#cfg)*
#key if ::core::ptr::eq(*flag, &#table) => {
matches!(selector, #(#selectors)|*)
}
}
});
let build_many_impl = multiple.then(|| {
quote! {
fn try_build_many(
partial: &Self::Partial,
) -> ::std::result::Result<
::std::vec::Vec<Self>,
usage_argv::Error<'static, 'static>,
> {
let mut __usage_members = ::std::vec::Vec::with_capacity(partial.ordered.len());
for (__usage_at, __usage_value) in &partial.ordered {
let __usage_member = match *__usage_at {
#(#build_many_arms)*
_ => continue,
};
__usage_members.push(__usage_member);
}
::std::result::Result::Ok(__usage_members)
}
}
});
quote! {
#[doc(hidden)]
#[allow(
non_upper_case_globals,
non_snake_case,
unused_imports,
clippy::needless_update
)]
const _: () = {
use #runtime as usage_argv;
const __USAGE_KEY_BASE: u64 =
usage_argv::key_base(::core::module_path!(), #declaration);
#(#key_decls)*
#(#flags)*
#[derive(Default)]
pub struct Partial {
#ordered_field
#(#partial_fields)*
}
impl usage_argv::spec::ArgGroup for #ident {
const NAME: &'static str = #name;
const FLAGS: &'static [&'static usage_argv::Flag<'static>] = &[#(#flag_refs),*];
const FLAG_METAS: &'static [usage_argv::spec::FlagMeta<'static>] =
&[#(#flag_metas),*];
const MEMBERS: &'static [&'static str] = &[#(#members),*];
const MULTIPLE: bool = #multiple;
type Partial = Partial;
fn apply(
partial: &mut Self::Partial,
event: &usage_argv::Event<'_, '_, '_>,
) -> bool {
match event {
usage_argv::Event::Flag { flag, value, .. } => match flag.key {
#(#apply_arms)*
_ => false,
},
_ => false,
}
}
fn any_given(partial: &Self::Partial) -> ::std::option::Option<&'static str> {
#(#given_arms)*
::std::option::Option::None
}
fn conflict(
partial: &Self::Partial,
) -> ::std::option::Option<(&'static str, &'static str)> {
let mut __usage_first: ::std::option::Option<&'static str> =
::std::option::Option::None;
#(#conflict_arms)*
let _ = __usage_first;
::std::option::Option::None
}
fn build(partial: &Self::Partial) -> ::std::option::Option<Self> {
Self::try_build(partial).ok().flatten()
}
fn try_build(
partial: &Self::Partial,
) -> ::std::result::Result<
::std::option::Option<Self>,
usage_argv::Error<'static, 'static>,
> {
#(#build_arms)*
::std::result::Result::Ok(::std::option::Option::None)
}
#build_many_impl
fn argument_state(
partial: &Self::Partial,
selector: &str,
) -> ::std::option::Option<usage_argv::spec::ArgumentState> {
match selector {
#(#state_arms)*
_ => ::std::option::Option::None,
}
}
fn standing_state(
standing: &Self,
selector: &str,
) -> ::std::option::Option<usage_argv::spec::ArgumentState> {
match selector {
#(#standing_state_arms)*
_ => ::std::option::Option::None,
}
}
fn argument_matches(
partial: &Self::Partial,
selector: &str,
value: &[u8],
) -> ::std::option::Option<bool> {
match selector {
#(#match_arms)*
_ => ::std::option::Option::None,
}
}
fn standing_matches(
standing: &Self,
selector: &str,
value: &[u8],
) -> ::std::option::Option<bool> {
match selector {
#(#standing_match_arms)*
_ => ::std::option::Option::None,
}
}
fn displace(partial: &mut Self::Partial, selector: &str) -> bool {
match selector {
#(#displace_arms)*
_ => false,
}
}
fn event_matches(
event: &usage_argv::Event<'_, '_, '_>,
selector: &str,
) -> bool {
match event {
usage_argv::Event::Flag { flag, .. } => match flag.key {
#(#event_arms)*
_ => false,
},
_ => false,
}
}
}
};
}
}
#[cfg(test)]
mod binding_hash_tests {
use super::hash_binding_part;
fn contract(validate: &str, validate_error: &str) -> u64 {
let mut hash = 0xcbf2_9ce4_8422_2325u64;
hash_binding_part(&mut hash, b"validate", Some(validate.as_bytes()));
hash_binding_part(
&mut hash,
b"validate-error",
Some(validate_error.as_bytes()),
);
hash
}
#[test]
fn adjacent_binding_properties_cannot_alias_by_concatenation() {
assert_ne!(contract("a", "bc"), contract("ab", "c"));
}
}