mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 14:22:34 +00:00
commit
d085797da6
16 changed files with 6305 additions and 6279 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
2786
src/build/app/mod.rs
2786
src/build/app/mod.rs
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
6639
src/build/arg/mod.rs
6639
src/build/arg/mod.rs
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue