Merge pull request #60 from epage/reorg

docs: Re-order items in docs
This commit is contained in:
Ed Page 2021-12-04 19:39:36 -06:00 committed by GitHub
commit d085797da6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 6305 additions and 6279 deletions

View file

@ -34,66 +34,6 @@ pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
/// Default casing style for environment variables
pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum Kind {
Arg(Sp<Ty>),
FromGlobal(Sp<Ty>),
Subcommand(Sp<Ty>),
Flatten,
Skip(Option<Expr>),
ExternalSubcommand,
}
#[derive(Clone)]
pub struct Method {
name: Ident,
args: TokenStream,
}
#[derive(Clone)]
pub struct Parser {
pub kind: Sp<ParserKind>,
pub func: TokenStream,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ParserKind {
FromStr,
TryFromStr,
FromOsStr,
TryFromOsStr,
FromOccurrences,
FromFlag,
}
/// Defines the casing for the attributes long representation.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CasingStyle {
/// Indicate word boundaries with uppercase letter, excluding the first word.
Camel,
/// Keep all letters lowercase and indicate word boundaries with hyphens.
Kebab,
/// Indicate word boundaries with uppercase letter, including the first word.
Pascal,
/// Keep all letters uppercase and indicate word boundaries with underscores.
ScreamingSnake,
/// Keep all letters lowercase and indicate word boundaries with underscores.
Snake,
/// Keep all letters lowercase and remove word boundaries.
Lower,
/// Keep all letters uppercase and remove word boundaries.
Upper,
/// Use the original attribute name defined in the code.
Verbatim,
}
#[derive(Clone)]
pub enum Name {
Derived(Ident),
Assigned(TokenStream),
}
#[derive(Clone)]
pub struct Attrs {
name: Name,
@ -112,345 +52,7 @@ pub struct Attrs {
kind: Sp<Kind>,
}
impl Method {
pub fn new(name: Ident, args: TokenStream) -> Self {
Method { name, args }
}
fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Self {
let mut lit = match lit {
Some(lit) => lit,
None => match env::var(env_var) {
Ok(val) => LitStr::new(&val, ident.span()),
Err(_) => {
abort!(ident,
"cannot derive `{}` from Cargo.toml", ident;
note = "`{}` environment variable is not set", env_var;
help = "use `{} = \"...\"` to set {} manually", ident, ident;
);
}
},
};
if ident == "author" {
let edited = process_author_str(&lit.value());
lit = LitStr::new(&edited, lit.span());
}
Method::new(ident, quote!(#lit))
}
}
impl ToTokens for Method {
fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
let Method { ref name, ref args } = self;
let tokens = quote!( .#name(#args) );
tokens.to_tokens(ts);
}
}
impl Parser {
fn default_spanned(span: Span) -> Sp<Self> {
let kind = Sp::new(ParserKind::TryFromStr, span);
let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
Sp::new(Parser { kind, func }, span)
}
fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
use self::ParserKind::*;
let kind = match &*spec.kind.to_string() {
"from_str" => FromStr,
"try_from_str" => TryFromStr,
"from_os_str" => FromOsStr,
"try_from_os_str" => TryFromOsStr,
"from_occurrences" => FromOccurrences,
"from_flag" => FromFlag,
s => abort!(spec.kind.span(), "unsupported parser `{}`", s),
};
let func = match spec.parse_func {
None => match kind {
FromStr | FromOsStr => {
quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
}
TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
TryFromOsStr => abort!(
spec.kind.span(),
"you must set parser for `try_from_os_str` explicitly"
),
FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
},
Some(func) => match func {
Expr::Path(_) => quote!(#func),
_ => abort!(func, "`parse` argument must be a function path"),
},
};
let kind = Sp::new(kind, spec.kind.span());
let parser = Parser { kind, func };
Sp::new(parser, parse_ident.span())
}
}
impl CasingStyle {
fn from_lit(name: LitStr) -> Sp<Self> {
use self::CasingStyle::*;
let normalized = name.value().to_camel_case().to_lowercase();
let cs = |kind| Sp::new(kind, name.span());
match normalized.as_ref() {
"camel" | "camelcase" => cs(Camel),
"kebab" | "kebabcase" => cs(Kebab),
"pascal" | "pascalcase" => cs(Pascal),
"screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
"snake" | "snakecase" => cs(Snake),
"lower" | "lowercase" => cs(Lower),
"upper" | "uppercase" => cs(Upper),
"verbatim" | "verbatimcase" => cs(Verbatim),
s => abort!(name, "unsupported casing: `{}`", s),
}
}
}
impl Name {
pub fn translate(self, style: CasingStyle) -> TokenStream {
use CasingStyle::*;
match self {
Name::Assigned(tokens) => tokens,
Name::Derived(ident) => {
let s = ident.unraw().to_string();
let s = match style {
Pascal => s.to_camel_case(),
Kebab => s.to_kebab_case(),
Camel => s.to_mixed_case(),
ScreamingSnake => s.to_shouty_snake_case(),
Snake => s.to_snake_case(),
Lower => s.to_snake_case().replace("_", ""),
Upper => s.to_shouty_snake_case().replace("_", ""),
Verbatim => s,
};
quote_spanned!(ident.span()=> #s)
}
}
}
pub fn translate_char(self, style: CasingStyle) -> TokenStream {
use CasingStyle::*;
match self {
Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
Name::Derived(ident) => {
let s = ident.unraw().to_string();
let s = match style {
Pascal => s.to_camel_case(),
Kebab => s.to_kebab_case(),
Camel => s.to_mixed_case(),
ScreamingSnake => s.to_shouty_snake_case(),
Snake => s.to_snake_case(),
Lower => s.to_snake_case(),
Upper => s.to_shouty_snake_case(),
Verbatim => s,
};
let s = s.chars().next().unwrap();
quote_spanned!(ident.span()=> #s)
}
}
}
}
impl Attrs {
fn new(
default_span: Span,
name: Name,
ty: Option<Type>,
casing: Sp<CasingStyle>,
env_casing: Sp<CasingStyle>,
) -> Self {
Self {
name,
ty,
casing,
env_casing,
doc_comment: vec![],
methods: vec![],
parser: Parser::default_spanned(default_span),
author: None,
version: None,
verbatim_doc_comment: None,
help_heading: None,
is_enum: false,
has_custom_parser: false,
kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
}
}
fn push_method(&mut self, name: Ident, arg: impl ToTokens) {
if name == "name" {
self.name = Name::Assigned(quote!(#arg));
} else if name == "version" {
self.version = Some(Method::new(name, quote!(#arg)));
} else {
self.methods.push(Method::new(name, quote!(#arg)))
}
}
fn push_attrs(&mut self, attrs: &[Attribute]) {
use ClapAttr::*;
for attr in parse_clap_attributes(attrs) {
match attr {
Short(ident) => {
self.push_method(ident, self.name.clone().translate_char(*self.casing));
}
Long(ident) => {
self.push_method(ident, self.name.clone().translate(*self.casing));
}
Env(ident) => {
self.push_method(ident, self.name.clone().translate(*self.env_casing));
}
ArgEnum(_) => self.is_enum = true,
FromGlobal(ident) => {
let ty = Sp::call_site(Ty::Other);
let kind = Sp::new(Kind::FromGlobal(ty), ident.span());
self.set_kind(kind);
}
Subcommand(ident) => {
let ty = Sp::call_site(Ty::Other);
let kind = Sp::new(Kind::Subcommand(ty), ident.span());
self.set_kind(kind);
}
ExternalSubcommand(ident) => {
let kind = Sp::new(Kind::ExternalSubcommand, ident.span());
self.set_kind(kind);
}
Flatten(ident) => {
let kind = Sp::new(Kind::Flatten, ident.span());
self.set_kind(kind);
}
Skip(ident, expr) => {
let kind = Sp::new(Kind::Skip(expr), ident.span());
self.set_kind(kind);
}
VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
DefaultValueT(ident, expr) => {
let ty = if let Some(ty) = self.ty.as_ref() {
ty
} else {
abort!(
ident,
"#[clap(default_value_t)] (without an argument) can be used \
only on field level";
note = "see \
https://docs.rs/structopt/0.3.5/structopt/#magical-methods")
};
let val = if let Some(expr) = expr {
quote!(#expr)
} else {
quote!(<#ty as ::std::default::Default>::default())
};
let val = quote_spanned!(ident.span()=> {
clap::lazy_static::lazy_static! {
static ref DEFAULT_VALUE: &'static str = {
let val: #ty = #val;
let s = ::std::string::ToString::to_string(&val);
::std::boxed::Box::leak(s.into_boxed_str())
};
}
*DEFAULT_VALUE
});
let raw_ident = Ident::new("default_value", ident.span());
self.methods.push(Method::new(raw_ident, val));
}
HelpHeading(ident, expr) => {
self.help_heading = Some(Method::new(ident, quote!(#expr)));
}
About(ident, about) => {
let method = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION");
self.methods.push(method);
}
Author(ident, author) => {
self.author = Some(Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS"));
}
Version(ident, version) => {
self.version =
Some(Method::from_lit_or_env(ident, version, "CARGO_PKG_VERSION"));
}
NameLitStr(name, lit) => {
self.push_method(name, lit);
}
NameExpr(name, expr) => {
self.push_method(name, expr);
}
MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),
RenameAll(_, casing_lit) => {
self.casing = CasingStyle::from_lit(casing_lit);
}
RenameAllEnv(_, casing_lit) => {
self.env_casing = CasingStyle::from_lit(casing_lit);
}
Parse(ident, spec) => {
self.has_custom_parser = true;
self.parser = Parser::from_spec(ident, spec);
}
}
}
}
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
use syn::Lit::*;
use syn::Meta::*;
let comment_parts: Vec<_> = attrs
.iter()
.filter(|attr| attr.path.is_ident("doc"))
.filter_map(|attr| {
if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
Some(s.value())
} else {
// non #[doc = "..."] attributes are not our concern
// we leave them for rustc to handle
None
}
})
.collect();
self.doc_comment =
process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none());
}
pub fn from_struct(
span: Span,
attrs: &[Attribute],
@ -759,6 +361,189 @@ impl Attrs {
res
}
fn new(
default_span: Span,
name: Name,
ty: Option<Type>,
casing: Sp<CasingStyle>,
env_casing: Sp<CasingStyle>,
) -> Self {
Self {
name,
ty,
casing,
env_casing,
doc_comment: vec![],
methods: vec![],
parser: Parser::default_spanned(default_span),
author: None,
version: None,
verbatim_doc_comment: None,
help_heading: None,
is_enum: false,
has_custom_parser: false,
kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
}
}
fn push_method(&mut self, name: Ident, arg: impl ToTokens) {
if name == "name" {
self.name = Name::Assigned(quote!(#arg));
} else if name == "version" {
self.version = Some(Method::new(name, quote!(#arg)));
} else {
self.methods.push(Method::new(name, quote!(#arg)))
}
}
fn push_attrs(&mut self, attrs: &[Attribute]) {
use ClapAttr::*;
for attr in parse_clap_attributes(attrs) {
match attr {
Short(ident) => {
self.push_method(ident, self.name.clone().translate_char(*self.casing));
}
Long(ident) => {
self.push_method(ident, self.name.clone().translate(*self.casing));
}
Env(ident) => {
self.push_method(ident, self.name.clone().translate(*self.env_casing));
}
ArgEnum(_) => self.is_enum = true,
FromGlobal(ident) => {
let ty = Sp::call_site(Ty::Other);
let kind = Sp::new(Kind::FromGlobal(ty), ident.span());
self.set_kind(kind);
}
Subcommand(ident) => {
let ty = Sp::call_site(Ty::Other);
let kind = Sp::new(Kind::Subcommand(ty), ident.span());
self.set_kind(kind);
}
ExternalSubcommand(ident) => {
let kind = Sp::new(Kind::ExternalSubcommand, ident.span());
self.set_kind(kind);
}
Flatten(ident) => {
let kind = Sp::new(Kind::Flatten, ident.span());
self.set_kind(kind);
}
Skip(ident, expr) => {
let kind = Sp::new(Kind::Skip(expr), ident.span());
self.set_kind(kind);
}
VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
DefaultValueT(ident, expr) => {
let ty = if let Some(ty) = self.ty.as_ref() {
ty
} else {
abort!(
ident,
"#[clap(default_value_t)] (without an argument) can be used \
only on field level";
note = "see \
https://docs.rs/structopt/0.3.5/structopt/#magical-methods")
};
let val = if let Some(expr) = expr {
quote!(#expr)
} else {
quote!(<#ty as ::std::default::Default>::default())
};
let val = quote_spanned!(ident.span()=> {
clap::lazy_static::lazy_static! {
static ref DEFAULT_VALUE: &'static str = {
let val: #ty = #val;
let s = ::std::string::ToString::to_string(&val);
::std::boxed::Box::leak(s.into_boxed_str())
};
}
*DEFAULT_VALUE
});
let raw_ident = Ident::new("default_value", ident.span());
self.methods.push(Method::new(raw_ident, val));
}
HelpHeading(ident, expr) => {
self.help_heading = Some(Method::new(ident, quote!(#expr)));
}
About(ident, about) => {
let method = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION");
self.methods.push(method);
}
Author(ident, author) => {
self.author = Some(Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS"));
}
Version(ident, version) => {
self.version =
Some(Method::from_lit_or_env(ident, version, "CARGO_PKG_VERSION"));
}
NameLitStr(name, lit) => {
self.push_method(name, lit);
}
NameExpr(name, expr) => {
self.push_method(name, expr);
}
MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),
RenameAll(_, casing_lit) => {
self.casing = CasingStyle::from_lit(casing_lit);
}
RenameAllEnv(_, casing_lit) => {
self.env_casing = CasingStyle::from_lit(casing_lit);
}
Parse(ident, spec) => {
self.has_custom_parser = true;
self.parser = Parser::from_spec(ident, spec);
}
}
}
}
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
use syn::Lit::*;
use syn::Meta::*;
let comment_parts: Vec<_> = attrs
.iter()
.filter(|attr| attr.path.is_ident("doc"))
.filter_map(|attr| {
if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
Some(s.value())
} else {
// non #[doc = "..."] attributes are not our concern
// we leave them for rustc to handle
None
}
})
.collect();
self.doc_comment =
process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none());
}
fn set_kind(&mut self, kind: Sp<Kind>) {
if let Kind::Arg(_) = *self.kind {
self.kind = kind;
@ -868,6 +653,63 @@ impl Attrs {
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum Kind {
Arg(Sp<Ty>),
FromGlobal(Sp<Ty>),
Subcommand(Sp<Ty>),
Flatten,
Skip(Option<Expr>),
ExternalSubcommand,
}
#[derive(Clone)]
pub struct Method {
name: Ident,
args: TokenStream,
}
impl Method {
pub fn new(name: Ident, args: TokenStream) -> Self {
Method { name, args }
}
fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Self {
let mut lit = match lit {
Some(lit) => lit,
None => match env::var(env_var) {
Ok(val) => LitStr::new(&val, ident.span()),
Err(_) => {
abort!(ident,
"cannot derive `{}` from Cargo.toml", ident;
note = "`{}` environment variable is not set", env_var;
help = "use `{} = \"...\"` to set {} manually", ident, ident;
);
}
},
};
if ident == "author" {
let edited = process_author_str(&lit.value());
lit = LitStr::new(&edited, lit.span());
}
Method::new(ident, quote!(#lit))
}
}
impl ToTokens for Method {
fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
let Method { ref name, ref args } = self;
let tokens = quote!( .#name(#args) );
tokens.to_tokens(ts);
}
}
/// replace all `:` with `, ` when not inside the `<>`
///
/// `"author1:author2:author3" => "author1, author2, author3"`
@ -892,3 +734,161 @@ fn process_author_str(author: &str) -> String {
res
}
#[derive(Clone)]
pub struct Parser {
pub kind: Sp<ParserKind>,
pub func: TokenStream,
}
impl Parser {
fn default_spanned(span: Span) -> Sp<Self> {
let kind = Sp::new(ParserKind::TryFromStr, span);
let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
Sp::new(Parser { kind, func }, span)
}
fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
use self::ParserKind::*;
let kind = match &*spec.kind.to_string() {
"from_str" => FromStr,
"try_from_str" => TryFromStr,
"from_os_str" => FromOsStr,
"try_from_os_str" => TryFromOsStr,
"from_occurrences" => FromOccurrences,
"from_flag" => FromFlag,
s => abort!(spec.kind.span(), "unsupported parser `{}`", s),
};
let func = match spec.parse_func {
None => match kind {
FromStr | FromOsStr => {
quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
}
TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
TryFromOsStr => abort!(
spec.kind.span(),
"you must set parser for `try_from_os_str` explicitly"
),
FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
},
Some(func) => match func {
Expr::Path(_) => quote!(#func),
_ => abort!(func, "`parse` argument must be a function path"),
},
};
let kind = Sp::new(kind, spec.kind.span());
let parser = Parser { kind, func };
Sp::new(parser, parse_ident.span())
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ParserKind {
FromStr,
TryFromStr,
FromOsStr,
TryFromOsStr,
FromOccurrences,
FromFlag,
}
/// Defines the casing for the attributes long representation.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CasingStyle {
/// Indicate word boundaries with uppercase letter, excluding the first word.
Camel,
/// Keep all letters lowercase and indicate word boundaries with hyphens.
Kebab,
/// Indicate word boundaries with uppercase letter, including the first word.
Pascal,
/// Keep all letters uppercase and indicate word boundaries with underscores.
ScreamingSnake,
/// Keep all letters lowercase and indicate word boundaries with underscores.
Snake,
/// Keep all letters lowercase and remove word boundaries.
Lower,
/// Keep all letters uppercase and remove word boundaries.
Upper,
/// Use the original attribute name defined in the code.
Verbatim,
}
impl CasingStyle {
fn from_lit(name: LitStr) -> Sp<Self> {
use self::CasingStyle::*;
let normalized = name.value().to_camel_case().to_lowercase();
let cs = |kind| Sp::new(kind, name.span());
match normalized.as_ref() {
"camel" | "camelcase" => cs(Camel),
"kebab" | "kebabcase" => cs(Kebab),
"pascal" | "pascalcase" => cs(Pascal),
"screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
"snake" | "snakecase" => cs(Snake),
"lower" | "lowercase" => cs(Lower),
"upper" | "uppercase" => cs(Upper),
"verbatim" | "verbatimcase" => cs(Verbatim),
s => abort!(name, "unsupported casing: `{}`", s),
}
}
}
#[derive(Clone)]
pub enum Name {
Derived(Ident),
Assigned(TokenStream),
}
impl Name {
pub fn translate(self, style: CasingStyle) -> TokenStream {
use CasingStyle::*;
match self {
Name::Assigned(tokens) => tokens,
Name::Derived(ident) => {
let s = ident.unraw().to_string();
let s = match style {
Pascal => s.to_camel_case(),
Kebab => s.to_kebab_case(),
Camel => s.to_mixed_case(),
ScreamingSnake => s.to_shouty_snake_case(),
Snake => s.to_snake_case(),
Lower => s.to_snake_case().replace("_", ""),
Upper => s.to_shouty_snake_case().replace("_", ""),
Verbatim => s,
};
quote_spanned!(ident.span()=> #s)
}
}
}
pub fn translate_char(self, style: CasingStyle) -> TokenStream {
use CasingStyle::*;
match self {
Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
Name::Derived(ident) => {
let s = ident.unraw().to_string();
let s = match style {
Pascal => s.to_camel_case(),
Kebab => s.to_kebab_case(),
Camel => s.to_mixed_case(),
ScreamingSnake => s.to_shouty_snake_case(),
Snake => s.to_snake_case(),
Lower => s.to_snake_case(),
Upper => s.to_shouty_snake_case(),
Verbatim => s,
};
let s = s.chars().next().unwrap();
quote_spanned!(ident.span()=> #s)
}
}
}
}

View file

@ -9,6 +9,17 @@ use syn::{
Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
};
pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr> {
all_attrs
.iter()
.filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt"))
.flat_map(|attr| {
attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)
.unwrap_or_abort()
})
.collect()
}
#[allow(clippy::large_enum_variant)]
pub enum ClapAttr {
// single-identifier attributes
@ -270,14 +281,3 @@ fn raw_method_suggestion(ts: ParseBuffer) -> String {
.into()
}
}
pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr> {
all_attrs
.iter()
.filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt"))
.flat_map(|attr| {
attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)
.unwrap_or_abort()
})
.collect()
}

View file

@ -5,43 +5,6 @@ use crate::{
};
use std::cmp::Ordering;
#[derive(Eq)]
enum Flag<'a> {
App(String, &'a str),
Arg(String, &'a str),
}
impl PartialEq for Flag<'_> {
fn eq(&self, other: &Flag) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl PartialOrd for Flag<'_> {
fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
use Flag::*;
match (self, other) {
(App(s1, _), App(s2, _))
| (Arg(s1, _), Arg(s2, _))
| (App(s1, _), Arg(s2, _))
| (Arg(s1, _), App(s2, _)) => {
if s1 == s2 {
Some(Ordering::Equal)
} else {
s1.partial_cmp(s2)
}
}
}
}
}
impl Ord for Flag<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
pub(crate) fn assert_app(app: &App) {
debug!("App::_debug_asserts");
@ -301,6 +264,43 @@ pub(crate) fn assert_app(app: &App) {
assert_app_flags(app);
}
#[derive(Eq)]
enum Flag<'a> {
App(String, &'a str),
Arg(String, &'a str),
}
impl PartialEq for Flag<'_> {
fn eq(&self, other: &Flag) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl PartialOrd for Flag<'_> {
fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
use Flag::*;
match (self, other) {
(App(s1, _), App(s2, _))
| (Arg(s1, _), Arg(s2, _))
| (App(s1, _), Arg(s2, _))
| (Arg(s1, _), App(s2, _)) => {
if s1 == s2 {
Some(Ordering::Equal)
} else {
s1.partial_cmp(s2)
}
}
}
}
}
impl Ord for Flag<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
use Flag::*;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -32,80 +32,6 @@ pub struct PossibleValue<'help> {
pub(crate) hide: bool,
}
impl<'help> From<&'help str> for PossibleValue<'help> {
fn from(s: &'help str) -> Self {
Self::new(s)
}
}
impl<'help> From<&'help &'help str> for PossibleValue<'help> {
fn from(s: &'help &'help str) -> Self {
Self::new(s)
}
}
/// Getters
impl<'help> PossibleValue<'help> {
/// Get the name of the argument value
#[inline]
pub fn get_name(&self) -> &str {
self.name
}
/// Get the help specified for this argument, if any
#[inline]
pub fn get_help(&self) -> Option<&str> {
self.help
}
/// Should the value be hidden from help messages and completion
#[inline]
pub fn is_hidden(&self) -> bool {
self.hide
}
/// Get the name if argument value is not hidden, `None` otherwise
pub fn get_visible_name(&self) -> Option<&str> {
if self.hide {
None
} else {
Some(self.name)
}
}
/// Returns all valid values of the argument value.
///
/// Namely the name and all aliases.
pub fn get_name_and_aliases(&self) -> impl Iterator<Item = &str> {
iter::once(&self.name).chain(&self.aliases).copied()
}
/// Tests if the value is valid for this argument value
///
/// The value is valid if it is either the name or one of the aliases.
///
/// # Examples
///
/// ```rust
/// # use clap::PossibleValue;
/// let arg_value = PossibleValue::new("fast").alias("not-slow");
///
/// assert!(arg_value.matches("fast", false));
/// assert!(arg_value.matches("not-slow", false));
///
/// assert!(arg_value.matches("FAST", true));
/// assert!(!arg_value.matches("FAST", false));
/// ```
pub fn matches(&self, value: &str, ignore_case: bool) -> bool {
if ignore_case {
self.get_name_and_aliases()
.any(|name| eq_ignore_case(name, value))
} else {
self.get_name_and_aliases().any(|name| name == value)
}
}
}
impl<'help> PossibleValue<'help> {
/// Create a [`PossibleValue`] with its name.
///
@ -203,3 +129,77 @@ impl<'help> PossibleValue<'help> {
self
}
}
/// Reflection
impl<'help> PossibleValue<'help> {
/// Get the name of the argument value
#[inline]
pub fn get_name(&self) -> &str {
self.name
}
/// Get the help specified for this argument, if any
#[inline]
pub fn get_help(&self) -> Option<&str> {
self.help
}
/// Should the value be hidden from help messages and completion
#[inline]
pub fn is_hidden(&self) -> bool {
self.hide
}
/// Get the name if argument value is not hidden, `None` otherwise
pub fn get_visible_name(&self) -> Option<&str> {
if self.hide {
None
} else {
Some(self.name)
}
}
/// Returns all valid values of the argument value.
///
/// Namely the name and all aliases.
pub fn get_name_and_aliases(&self) -> impl Iterator<Item = &str> {
iter::once(&self.name).chain(&self.aliases).copied()
}
/// Tests if the value is valid for this argument value
///
/// The value is valid if it is either the name or one of the aliases.
///
/// # Examples
///
/// ```rust
/// # use clap::PossibleValue;
/// let arg_value = PossibleValue::new("fast").alias("not-slow");
///
/// assert!(arg_value.matches("fast", false));
/// assert!(arg_value.matches("not-slow", false));
///
/// assert!(arg_value.matches("FAST", true));
/// assert!(!arg_value.matches("FAST", false));
/// ```
pub fn matches(&self, value: &str, ignore_case: bool) -> bool {
if ignore_case {
self.get_name_and_aliases()
.any(|name| eq_ignore_case(name, value))
} else {
self.get_name_and_aliases().any(|name| name == value)
}
}
}
impl<'help> From<&'help str> for PossibleValue<'help> {
fn from(s: &'help str) -> Self {
Self::new(s)
}
}
impl<'help> From<&'help &'help str> for PossibleValue<'help> {
fn from(s: &'help &'help str) -> Self {
Self::new(s)
}
}

View file

@ -23,22 +23,6 @@ impl<'a> RegexRef<'a> {
}
}
impl<'a> FromStr for RegexRef<'a> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Regex::from_str(s).map(|v| Self::Regex(Cow::Owned(v)))
}
}
impl<'a> TryFrom<&'a str> for RegexRef<'a> {
type Error = <Self as FromStr>::Err;
fn try_from(r: &'a str) -> Result<Self, Self::Error> {
Self::from_str(r)
}
}
impl<'a> From<&'a Regex> for RegexRef<'a> {
fn from(r: &'a Regex) -> Self {
Self::Regex(Cow::Borrowed(r))
@ -63,6 +47,22 @@ impl<'a> From<RegexSet> for RegexRef<'a> {
}
}
impl<'a> TryFrom<&'a str> for RegexRef<'a> {
type Error = <Self as FromStr>::Err;
fn try_from(r: &'a str) -> Result<Self, Self::Error> {
Self::from_str(r)
}
}
impl<'a> FromStr for RegexRef<'a> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Regex::from_str(s).map(|v| Self::Regex(Cow::Owned(v)))
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -4,37 +4,6 @@ use std::{ops::BitOr, str::FromStr};
// Third party
use bitflags::bitflags;
bitflags! {
struct Flags: u32 {
const REQUIRED = 1;
const MULTIPLE_OCC = 1 << 1;
const NO_EMPTY_VALS = 1 << 2;
const GLOBAL = 1 << 3;
const HIDDEN = 1 << 4;
const TAKES_VAL = 1 << 5;
const USE_DELIM = 1 << 6;
const NEXT_LINE_HELP = 1 << 7;
const REQ_DELIM = 1 << 9;
const DELIM_NOT_SET = 1 << 10;
const HIDE_POS_VALS = 1 << 11;
const ALLOW_TAC_VALS = 1 << 12;
const REQUIRE_EQUALS = 1 << 13;
const LAST = 1 << 14;
const HIDE_DEFAULT_VAL = 1 << 15;
const CASE_INSENSITIVE = 1 << 16;
#[cfg(feature = "env")]
const HIDE_ENV_VALS = 1 << 17;
const HIDDEN_SHORT_H = 1 << 18;
const HIDDEN_LONG_H = 1 << 19;
const MULTIPLE_VALS = 1 << 20;
const MULTIPLE = Self::MULTIPLE_OCC.bits | Self::MULTIPLE_VALS.bits;
#[cfg(feature = "env")]
const HIDE_ENV = 1 << 21;
const UTF8_NONE = 1 << 22;
const NO_OP = 0;
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArgFlags(Flags);
@ -45,36 +14,6 @@ impl Default for ArgFlags {
}
}
// @TODO @p6 @internal: Reorder alphabetically
impl_settings! { ArgSettings, ArgFlags,
Required("required") => Flags::REQUIRED,
MultipleOccurrences("multipleoccurrences") => Flags::MULTIPLE_OCC,
MultipleValues("multiplevalues") => Flags::MULTIPLE_VALS,
Multiple("multiple") => Flags::MULTIPLE,
ForbidEmptyValues("forbidemptyvalues") => Flags::NO_EMPTY_VALS,
Global("global") => Flags::GLOBAL,
Hidden("hidden") => Flags::HIDDEN,
TakesValue("takesvalue") => Flags::TAKES_VAL,
UseValueDelimiter("usevaluedelimiter") => Flags::USE_DELIM,
NextLineHelp("nextlinehelp") => Flags::NEXT_LINE_HELP,
RequireDelimiter("requiredelimiter") => Flags::REQ_DELIM,
HidePossibleValues("hidepossiblevalues") => Flags::HIDE_POS_VALS,
AllowHyphenValues("allowhyphenvalues") => Flags::ALLOW_TAC_VALS,
AllowLeadingHyphen("allowleadinghypyhen") => Flags::ALLOW_TAC_VALS,
RequireEquals("requireequals") => Flags::REQUIRE_EQUALS,
Last("last") => Flags::LAST,
IgnoreCase("ignorecase") => Flags::CASE_INSENSITIVE,
CaseInsensitive("caseinsensitive") => Flags::CASE_INSENSITIVE,
#[cfg(feature = "env")]
HideEnv("hideenv") => Flags::HIDE_ENV,
#[cfg(feature = "env")]
HideEnvValues("hideenvvalues") => Flags::HIDE_ENV_VALS,
HideDefaultValue("hidedefaultvalue") => Flags::HIDE_DEFAULT_VAL,
HiddenShortHelp("hiddenshorthelp") => Flags::HIDDEN_SHORT_H,
HiddenLongHelp("hiddenlonghelp") => Flags::HIDDEN_LONG_H,
AllowInvalidUtf8("allowinvalidutf8") => Flags::UTF8_NONE
}
/// Various settings that apply to arguments and may be set, unset, and checked via getter/setter
/// methods [`Arg::setting`], [`Arg::unset_setting`], and [`Arg::is_set`]. This is what the
/// [`Arg`] methods which accept a `bool` use internally.
@ -150,6 +89,67 @@ pub enum ArgSettings {
AllowInvalidUtf8,
}
bitflags! {
struct Flags: u32 {
const REQUIRED = 1;
const MULTIPLE_OCC = 1 << 1;
const NO_EMPTY_VALS = 1 << 2;
const GLOBAL = 1 << 3;
const HIDDEN = 1 << 4;
const TAKES_VAL = 1 << 5;
const USE_DELIM = 1 << 6;
const NEXT_LINE_HELP = 1 << 7;
const REQ_DELIM = 1 << 9;
const DELIM_NOT_SET = 1 << 10;
const HIDE_POS_VALS = 1 << 11;
const ALLOW_TAC_VALS = 1 << 12;
const REQUIRE_EQUALS = 1 << 13;
const LAST = 1 << 14;
const HIDE_DEFAULT_VAL = 1 << 15;
const CASE_INSENSITIVE = 1 << 16;
#[cfg(feature = "env")]
const HIDE_ENV_VALS = 1 << 17;
const HIDDEN_SHORT_H = 1 << 18;
const HIDDEN_LONG_H = 1 << 19;
const MULTIPLE_VALS = 1 << 20;
const MULTIPLE = Self::MULTIPLE_OCC.bits | Self::MULTIPLE_VALS.bits;
#[cfg(feature = "env")]
const HIDE_ENV = 1 << 21;
const UTF8_NONE = 1 << 22;
const NO_OP = 0;
}
}
// @TODO @p6 @internal: Reorder alphabetically
impl_settings! { ArgSettings, ArgFlags,
Required("required") => Flags::REQUIRED,
MultipleOccurrences("multipleoccurrences") => Flags::MULTIPLE_OCC,
MultipleValues("multiplevalues") => Flags::MULTIPLE_VALS,
Multiple("multiple") => Flags::MULTIPLE,
ForbidEmptyValues("forbidemptyvalues") => Flags::NO_EMPTY_VALS,
Global("global") => Flags::GLOBAL,
Hidden("hidden") => Flags::HIDDEN,
TakesValue("takesvalue") => Flags::TAKES_VAL,
UseValueDelimiter("usevaluedelimiter") => Flags::USE_DELIM,
NextLineHelp("nextlinehelp") => Flags::NEXT_LINE_HELP,
RequireDelimiter("requiredelimiter") => Flags::REQ_DELIM,
HidePossibleValues("hidepossiblevalues") => Flags::HIDE_POS_VALS,
AllowHyphenValues("allowhyphenvalues") => Flags::ALLOW_TAC_VALS,
AllowLeadingHyphen("allowleadinghypyhen") => Flags::ALLOW_TAC_VALS,
RequireEquals("requireequals") => Flags::REQUIRE_EQUALS,
Last("last") => Flags::LAST,
IgnoreCase("ignorecase") => Flags::CASE_INSENSITIVE,
CaseInsensitive("caseinsensitive") => Flags::CASE_INSENSITIVE,
#[cfg(feature = "env")]
HideEnv("hideenv") => Flags::HIDE_ENV,
#[cfg(feature = "env")]
HideEnvValues("hideenvvalues") => Flags::HIDE_ENV_VALS,
HideDefaultValue("hidedefaultvalue") => Flags::HIDE_DEFAULT_VAL,
HiddenShortHelp("hiddenshorthelp") => Flags::HIDDEN_SHORT_H,
HiddenLongHelp("hiddenlonghelp") => Flags::HIDDEN_LONG_H,
AllowInvalidUtf8("allowinvalidutf8") => Flags::UTF8_NONE
}
#[cfg(test)]
mod test {
use super::ArgSettings;

View file

@ -112,22 +112,6 @@ impl<'help> ArgGroup<'help> {
ArgGroup::default().name(n)
}
/// Deprecated, replaced with [`ArgGroup::new`]
#[deprecated(since = "3.0.0", note = "Replaced with `ArgGroup::new`")]
pub fn with_name<S: Into<&'help str>>(n: S) -> Self {
Self::new(n)
}
/// Deprecated in [Issue #9](https://github.com/epage/clapng/issues/9), maybe [`clap::Parser`][crate::Parser] would fit your use case?
#[cfg(feature = "yaml")]
#[deprecated(
since = "3.0.0",
note = "Maybe clap::Parser would fit your use case? (Issue #9)"
)]
pub fn from_yaml(yaml: &'help Yaml) -> Self {
Self::from(yaml)
}
/// Sets the group name.
///
/// # Examples
@ -432,6 +416,22 @@ impl<'help> ArgGroup<'help> {
}
self
}
/// Deprecated, replaced with [`ArgGroup::new`]
#[deprecated(since = "3.0.0", note = "Replaced with `ArgGroup::new`")]
pub fn with_name<S: Into<&'help str>>(n: S) -> Self {
Self::new(n)
}
/// Deprecated in [Issue #9](https://github.com/epage/clapng/issues/9), maybe [`clap::Parser`][crate::Parser] would fit your use case?
#[cfg(feature = "yaml")]
#[deprecated(
since = "3.0.0",
note = "Maybe clap::Parser would fit your use case? (Issue #9)"
)]
pub fn from_yaml(yaml: &'help Yaml) -> Self {
Self::from(yaml)
}
}
impl<'help> From<&'_ ArgGroup<'help>> for ArgGroup<'help> {

View file

@ -18,21 +18,6 @@ use crate::{
use indexmap::IndexSet;
use textwrap::core::display_width;
pub(crate) fn dimensions() -> Option<(usize, usize)> {
#[cfg(not(feature = "wrap_help"))]
return None;
#[cfg(feature = "wrap_help")]
terminal_size::terminal_size().map(|(w, h)| (w.0.into(), h.0.into()))
}
const TAB: &str = " ";
pub(crate) enum HelpWriter<'writer> {
Normal(&'writer mut dyn Write),
Buffer(&'writer mut Colorizer),
}
/// `clap` Help Writer.
///
/// Wraps a writer stream providing different methods to generate help for `clap` objects.
@ -1007,6 +992,21 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}
}
pub(crate) fn dimensions() -> Option<(usize, usize)> {
#[cfg(not(feature = "wrap_help"))]
return None;
#[cfg(feature = "wrap_help")]
terminal_size::terminal_size().map(|(w, h)| (w.0.into(), h.0.into()))
}
const TAB: &str = " ";
pub(crate) enum HelpWriter<'writer> {
Normal(&'writer mut dyn Write),
Buffer(&'writer mut Colorizer),
}
fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
debug!("should_show_arg: use_long={:?}, arg={}", use_long, arg.name);
if arg.is_set(ArgSettings::Hidden) {

View file

@ -14,14 +14,6 @@ use indexmap::map::Entry;
#[derive(Debug, Default)]
pub(crate) struct ArgMatcher(pub(crate) ArgMatches);
impl Deref for ArgMatcher {
type Target = ArgMatches;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ArgMatcher {
pub(crate) fn new(app: &App) -> Self {
ArgMatcher(ArgMatches {
@ -204,3 +196,11 @@ impl ArgMatcher {
true
}
}
impl Deref for ArgMatcher {
type Target = ArgMatches;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View file

@ -442,52 +442,7 @@ pub struct Error {
backtrace: Option<Backtrace>,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
// Assuming `self.message` already has a trailing newline, from `try_help` or similar
write!(f, "{}", self.message.formatted())?;
if let Some(backtrace) = self.backtrace.as_ref() {
writeln!(f)?;
writeln!(f, "Backtrace:")?;
writeln!(f, "{}", backtrace)?;
}
Ok(())
}
}
fn start_error(c: &mut Colorizer, msg: impl Into<String>) {
c.error("error:");
c.none(" ");
c.none(msg);
}
fn put_usage(c: &mut Colorizer, usage: impl Into<String>) {
c.none("\n\n");
c.none(usage);
}
fn try_help(app: &App, c: &mut Colorizer) {
if !app.settings.is_set(AppSettings::DisableHelpFlag) {
c.none("\n\nFor more information try ");
c.good("--help");
c.none("\n");
} else if app.has_subcommands() && !app.settings.is_set(AppSettings::DisableHelpSubcommand) {
c.none("\n\nFor more information try ");
c.good("help");
c.none("\n");
}
}
impl Error {
/// Returns the singular or plural form on the verb to be based on the argument's value.
fn singular_or_plural(n: usize) -> String {
if n > 1 {
String::from("were")
} else {
String::from("was")
}
}
/// Create an unformatted error
///
/// This is for you need to pass the error up to
@ -561,6 +516,14 @@ impl Error {
self.message.formatted().print()
}
/// Deprecated, replaced with [`App::error`]
///
/// [`App::error`]: crate::App::error
#[deprecated(since = "3.0.0", note = "Replaced with `App::error`")]
pub fn with_description(description: String, kind: ErrorKind) -> Self {
Error::raw(kind, description)
}
pub(crate) fn new(message: impl Into<Message>, kind: ErrorKind, wait_on_exit: bool) -> Self {
Self {
message: message.into(),
@ -1108,12 +1071,13 @@ impl Error {
Self::new(c, ErrorKind::ArgumentNotFound, false).set_info(vec![arg])
}
/// Deprecated, replaced with [`App::error`]
///
/// [`App::error`]: crate::App::error
#[deprecated(since = "3.0.0", note = "Replaced with `App::error`")]
pub fn with_description(description: String, kind: ErrorKind) -> Self {
Error::raw(kind, description)
/// Returns the singular or plural form on the verb to be based on the argument's value.
fn singular_or_plural(n: usize) -> String {
if n > 1 {
String::from("were")
} else {
String::from("was")
}
}
}
@ -1136,6 +1100,42 @@ impl error::Error for Error {
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
// Assuming `self.message` already has a trailing newline, from `try_help` or similar
write!(f, "{}", self.message.formatted())?;
if let Some(backtrace) = self.backtrace.as_ref() {
writeln!(f)?;
writeln!(f, "Backtrace:")?;
writeln!(f, "{}", backtrace)?;
}
Ok(())
}
}
fn start_error(c: &mut Colorizer, msg: impl Into<String>) {
c.error("error:");
c.none(" ");
c.none(msg);
}
fn put_usage(c: &mut Colorizer, usage: impl Into<String>) {
c.none("\n\n");
c.none(usage);
}
fn try_help(app: &App, c: &mut Colorizer) {
if !app.settings.is_set(AppSettings::DisableHelpFlag) {
c.none("\n\nFor more information try ");
c.good("--help");
c.none("\n");
} else if app.has_subcommands() && !app.settings.is_set(AppSettings::DisableHelpSubcommand) {
c.none("\n\nFor more information try ");
c.good("help");
c.none("\n");
}
}
#[derive(Clone, Debug)]
pub(crate) enum Message {
Raw(String),

View file

@ -18,13 +18,6 @@ use crate::{
{Error, INVALID_UTF8},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SubCommand {
pub(crate) id: Id,
pub(crate) name: String,
pub(crate) matches: ArgMatches,
}
/// Container for parse results.
///
/// Used to get information about the arguments that were supplied to the program at runtime by
@ -84,44 +77,6 @@ pub struct ArgMatches {
pub(crate) subcommand: Option<Box<SubCommand>>,
}
// Private methods
impl ArgMatches {
#[inline]
fn get_arg(&self, arg: &Id) -> Option<&MatchedArg> {
#[cfg(debug_assertions)]
{
if *arg != Id::empty_hash() && !self.valid_args.contains(arg) {
panic!(
"`'{:?}' is not a name of an argument or a group.\n\
Make sure you're using the name of the argument itself \
and not the name of short or long flags.",
arg
);
}
}
self.args.get(arg)
}
#[inline]
fn get_subcommand(&self, id: &Id) -> Option<&SubCommand> {
#[cfg(debug_assertions)]
{
if *id != Id::empty_hash() && !self.valid_subcommands.contains(id) {
panic!("'{:?}' is not a name of a subcommand.", id);
}
}
if let Some(ref sc) = self.subcommand {
if sc.id == *id {
return Some(sc);
}
}
None
}
}
impl ArgMatches {
/// Gets the value of a specific option or positional argument.
///
@ -905,6 +860,60 @@ impl ArgMatches {
Some(i)
}
/// The name and `ArgMatches` of the current [subcommand].
///
/// Subcommand values are put in a child [`ArgMatches`]
///
/// Returns `None` if the subcommand wasn't present at runtime,
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, };
/// let app_m = App::new("git")
/// .subcommand(App::new("clone"))
/// .subcommand(App::new("push"))
/// .subcommand(App::new("commit"))
/// .get_matches();
///
/// match app_m.subcommand() {
/// Some(("clone", sub_m)) => {}, // clone was used
/// Some(("push", sub_m)) => {}, // push was used
/// Some(("commit", sub_m)) => {}, // commit was used
/// _ => {}, // Either no subcommand or one not tested for...
/// }
/// ```
///
/// Another useful scenario is when you want to support third party, or external, subcommands.
/// In these cases you can't know the subcommand name ahead of time, so use a variable instead
/// with pattern matching!
///
/// ```rust
/// # use clap::{App, AppSettings};
/// // Assume there is an external subcommand named "subcmd"
/// let app_m = App::new("myprog")
/// .setting(AppSettings::AllowExternalSubcommands)
/// .get_matches_from(vec![
/// "myprog", "subcmd", "--option", "value", "-fff", "--flag"
/// ]);
///
/// // All trailing arguments will be stored under the subcommand's sub-matches using an empty
/// // string argument name
/// match app_m.subcommand() {
/// Some((external, sub_m)) => {
/// let ext_args: Vec<&str> = sub_m.values_of("").unwrap().collect();
/// assert_eq!(external, "subcmd");
/// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]);
/// },
/// _ => {},
/// }
/// ```
/// [subcommand]: crate::App::subcommand
#[inline]
pub fn subcommand(&self) -> Option<(&str, &ArgMatches)> {
self.subcommand.as_ref().map(|sc| (&*sc.name, &sc.matches))
}
/// The `ArgMatches` for the current [subcommand].
///
/// Subcommand values are put in a child [`ArgMatches`]
@ -973,60 +982,51 @@ impl ArgMatches {
pub fn subcommand_name(&self) -> Option<&str> {
self.subcommand.as_ref().map(|sc| &*sc.name)
}
}
/// The name and `ArgMatches` of the current [subcommand].
///
/// Subcommand values are put in a child [`ArgMatches`]
///
/// Returns `None` if the subcommand wasn't present at runtime,
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, };
/// let app_m = App::new("git")
/// .subcommand(App::new("clone"))
/// .subcommand(App::new("push"))
/// .subcommand(App::new("commit"))
/// .get_matches();
///
/// match app_m.subcommand() {
/// Some(("clone", sub_m)) => {}, // clone was used
/// Some(("push", sub_m)) => {}, // push was used
/// Some(("commit", sub_m)) => {}, // commit was used
/// _ => {}, // Either no subcommand or one not tested for...
/// }
/// ```
///
/// Another useful scenario is when you want to support third party, or external, subcommands.
/// In these cases you can't know the subcommand name ahead of time, so use a variable instead
/// with pattern matching!
///
/// ```rust
/// # use clap::{App, AppSettings};
/// // Assume there is an external subcommand named "subcmd"
/// let app_m = App::new("myprog")
/// .setting(AppSettings::AllowExternalSubcommands)
/// .get_matches_from(vec![
/// "myprog", "subcmd", "--option", "value", "-fff", "--flag"
/// ]);
///
/// // All trailing arguments will be stored under the subcommand's sub-matches using an empty
/// // string argument name
/// match app_m.subcommand() {
/// Some((external, sub_m)) => {
/// let ext_args: Vec<&str> = sub_m.values_of("").unwrap().collect();
/// assert_eq!(external, "subcmd");
/// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]);
/// },
/// _ => {},
/// }
/// ```
/// [subcommand]: crate::App::subcommand
// Private methods
impl ArgMatches {
#[inline]
pub fn subcommand(&self) -> Option<(&str, &ArgMatches)> {
self.subcommand.as_ref().map(|sc| (&*sc.name, &sc.matches))
fn get_arg(&self, arg: &Id) -> Option<&MatchedArg> {
#[cfg(debug_assertions)]
{
if *arg != Id::empty_hash() && !self.valid_args.contains(arg) {
panic!(
"`'{:?}' is not a name of an argument or a group.\n\
Make sure you're using the name of the argument itself \
and not the name of short or long flags.",
arg
);
}
}
self.args.get(arg)
}
#[inline]
fn get_subcommand(&self, id: &Id) -> Option<&SubCommand> {
#[cfg(debug_assertions)]
{
if *id != Id::empty_hash() && !self.valid_subcommands.contains(id) {
panic!("'{:?}' is not a name of a subcommand.", id);
}
}
if let Some(ref sc) = self.subcommand {
if sc.id == *id {
return Some(sc);
}
}
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SubCommand {
pub(crate) id: Id,
pub(crate) name: String,
pub(crate) matches: ArgMatches,
}
// The following were taken and adapted from vec_map source

View file

@ -8,16 +8,6 @@ use std::{
use crate::util::eq_ignore_case;
use crate::INTERNAL_ERROR_MSG;
// TODO: Maybe make this public?
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType {
Unknown,
#[cfg(feature = "env")]
EnvVariable,
CommandLine,
DefaultValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct MatchedArg {
pub(crate) occurs: u64,
@ -28,12 +18,6 @@ pub(crate) struct MatchedArg {
invalid_utf8_allowed: Option<bool>,
}
impl Default for MatchedArg {
fn default() -> Self {
Self::new()
}
}
impl MatchedArg {
pub(crate) fn new() -> Self {
MatchedArg {
@ -155,6 +139,22 @@ impl MatchedArg {
}
}
impl Default for MatchedArg {
fn default() -> Self {
Self::new()
}
}
// TODO: Maybe make this public?
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType {
Unknown,
#[cfg(feature = "env")]
EnvVariable,
CommandLine,
DefaultValue,
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -23,89 +23,6 @@ use crate::{
INTERNAL_ERROR_MSG, INVALID_UTF8,
};
#[derive(Debug)]
pub(crate) enum ParseState {
ValuesDone,
Opt(Id),
Pos(Id),
}
/// Recoverable Parsing results.
#[derive(Debug, PartialEq, Clone)]
enum ParseResult {
FlagSubCommand(String),
Opt(Id),
ValuesDone,
/// Value attached to the short flag is not consumed(e.g. 'u' for `-cu` is
/// not consumed).
AttachedValueNotConsumed,
/// This long flag doesn't need a value but is provided one.
UnneededAttachedValue {
rest: String,
used: Vec<Id>,
arg: String,
},
/// This flag might be an hyphen Value.
MaybeHyphenValue,
/// Equals required but not provided.
EqualsNotProvided {
arg: String,
},
/// Failed to match a Arg.
NoMatchingArg {
arg: String,
},
/// No argument found e.g. parser is given `-` when parsing a flag.
NoArg,
/// This is a Help flag.
HelpFlag,
/// This is a version flag.
VersionFlag,
}
#[derive(Debug)]
pub(crate) struct Input {
items: Vec<OsString>,
cursor: usize,
}
impl<I, T> From<I> for Input
where
I: Iterator<Item = T>,
T: Into<OsString> + Clone,
{
fn from(val: I) -> Self {
Self {
items: val.map(|x| x.into()).collect(),
cursor: 0,
}
}
}
impl Input {
pub(crate) fn next(&mut self) -> Option<(&OsStr, &[OsString])> {
if self.cursor >= self.items.len() {
None
} else {
let current = &self.items[self.cursor];
self.cursor += 1;
let remaining = &self.items[self.cursor..];
Some((current, remaining))
}
}
/// Insert some items to the Input items just after current parsing cursor.
/// Usually used by replaced items recovering.
pub(crate) fn insert(&mut self, insert_items: &[&str]) {
self.items = insert_items
.iter()
.map(OsString::from)
.chain(self.items.drain(self.cursor..))
.collect();
self.cursor = 0;
}
}
pub(crate) struct Parser<'help, 'app> {
pub(crate) app: &'app mut App<'help>,
pub(crate) required: ChildGraph<Id>,
@ -142,6 +59,23 @@ impl<'help, 'app> Parser<'help, 'app> {
}
}
// Does all the initializing and prepares the parser
pub(crate) fn _build(&mut self) {
debug!("Parser::_build");
#[cfg(debug_assertions)]
self._verify_positionals();
for group in &self.app.groups {
if group.required {
let idx = self.required.insert(group.id.clone());
for a in &group.requires {
self.required.insert_child(idx, a.clone());
}
}
}
}
#[cfg(debug_assertions)]
fn _verify_positionals(&self) -> bool {
debug!("Parser::_verify_positionals");
@ -316,23 +250,6 @@ impl<'help, 'app> Parser<'help, 'app> {
true
}
// Does all the initializing and prepares the parser
pub(crate) fn _build(&mut self) {
debug!("Parser::_build");
#[cfg(debug_assertions)]
self._verify_positionals();
for group in &self.app.groups {
if group.required {
let idx = self.required.insert(group.id.clone());
for a in &group.requires {
self.required.insert_child(idx, a.clone());
}
}
}
}
// Should we color the help?
pub(crate) fn color_help(&self) -> ColorChoice {
#[cfg(feature = "color")]
@ -1923,3 +1840,86 @@ impl<'help, 'app> Parser<'help, 'app> {
self.app.is_set(s)
}
}
#[derive(Debug)]
pub(crate) struct Input {
items: Vec<OsString>,
cursor: usize,
}
impl<I, T> From<I> for Input
where
I: Iterator<Item = T>,
T: Into<OsString> + Clone,
{
fn from(val: I) -> Self {
Self {
items: val.map(|x| x.into()).collect(),
cursor: 0,
}
}
}
impl Input {
pub(crate) fn next(&mut self) -> Option<(&OsStr, &[OsString])> {
if self.cursor >= self.items.len() {
None
} else {
let current = &self.items[self.cursor];
self.cursor += 1;
let remaining = &self.items[self.cursor..];
Some((current, remaining))
}
}
/// Insert some items to the Input items just after current parsing cursor.
/// Usually used by replaced items recovering.
pub(crate) fn insert(&mut self, insert_items: &[&str]) {
self.items = insert_items
.iter()
.map(OsString::from)
.chain(self.items.drain(self.cursor..))
.collect();
self.cursor = 0;
}
}
#[derive(Debug)]
pub(crate) enum ParseState {
ValuesDone,
Opt(Id),
Pos(Id),
}
/// Recoverable Parsing results.
#[derive(Debug, PartialEq, Clone)]
enum ParseResult {
FlagSubCommand(String),
Opt(Id),
ValuesDone,
/// Value attached to the short flag is not consumed(e.g. 'u' for `-cu` is
/// not consumed).
AttachedValueNotConsumed,
/// This long flag doesn't need a value but is provided one.
UnneededAttachedValue {
rest: String,
used: Vec<Id>,
arg: String,
},
/// This flag might be an hyphen Value.
MaybeHyphenValue,
/// Equals required but not provided.
EqualsNotProvided {
arg: String,
},
/// Failed to match a Arg.
NoMatchingArg {
arg: String,
},
/// No argument found e.g. parser is given `-` when parsing a flag.
NoArg,
/// This is a Help flag.
HelpFlag,
/// This is a version flag.
VersionFlag,
}