mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
Allow help argument to option and multiple option attributes
This commit is contained in:
+47
-36
@@ -38,10 +38,16 @@ pub(crate) fn parse_arguments_attr(attrs: &[Attribute]) -> ArgumentsAttr {
|
||||
ArgumentsAttr::default()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_argument(v: Variant) -> Option<Argument> {
|
||||
pub(crate) fn parse_argument(v: Variant) -> Vec<Argument> {
|
||||
let ident = v.ident;
|
||||
let name = ident.to_string();
|
||||
let attribute = get_arg_attribute(&v.attrs)?;
|
||||
let attributes = get_arg_attributes(&v.attrs);
|
||||
|
||||
// Return early because we don't need to check the fields if it's not used.
|
||||
if attributes.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let help = collect_help(&v.attrs);
|
||||
|
||||
let field = match v.fields {
|
||||
@@ -59,34 +65,43 @@ pub(crate) fn parse_argument(v: Variant) -> Option<Argument> {
|
||||
}
|
||||
};
|
||||
|
||||
let arg_type = match attribute {
|
||||
ArgAttr::Option(opt) => {
|
||||
let default_expr = match opt.default {
|
||||
Some(expr) => quote!(#expr),
|
||||
None => quote!(Default::default()),
|
||||
attributes
|
||||
.into_iter()
|
||||
.map(|attribute| {
|
||||
// We might override the help with the help given in the attribute
|
||||
let mut arg_help = help.clone();
|
||||
let arg_type = match attribute {
|
||||
ArgAttr::Option(opt) => {
|
||||
let default_expr = match opt.default {
|
||||
Some(expr) => quote!(#expr),
|
||||
None => quote!(Default::default()),
|
||||
};
|
||||
if let Some(help) = opt.help {
|
||||
arg_help = help;
|
||||
}
|
||||
ArgType::Option {
|
||||
flags: opt.flags,
|
||||
takes_value: field.is_some(),
|
||||
default: default_expr,
|
||||
hidden: opt.hidden,
|
||||
}
|
||||
}
|
||||
ArgAttr::Positional(pos) => {
|
||||
assert!(field.is_some(), "Positional arguments must have a field");
|
||||
ArgType::Positional {
|
||||
num_args: pos.num_args,
|
||||
last: pos.last,
|
||||
}
|
||||
}
|
||||
};
|
||||
ArgType::Option {
|
||||
flags: opt.flags,
|
||||
takes_value: field.is_some(),
|
||||
default: default_expr,
|
||||
hidden: opt.hidden,
|
||||
Argument {
|
||||
ident: ident.clone(),
|
||||
name: name.clone(),
|
||||
arg_type,
|
||||
help: arg_help,
|
||||
}
|
||||
}
|
||||
ArgAttr::Positional(pos) => {
|
||||
assert!(field.is_some(), "Positional arguments must have a field");
|
||||
ArgType::Positional {
|
||||
num_args: pos.num_args,
|
||||
last: pos.last,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some(Argument {
|
||||
ident,
|
||||
name,
|
||||
arg_type,
|
||||
help,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn collect_help(attrs: &[Attribute]) -> String {
|
||||
@@ -103,16 +118,12 @@ fn collect_help(attrs: &[Attribute]) -> String {
|
||||
help.join("\n")
|
||||
}
|
||||
|
||||
fn get_arg_attribute(attrs: &[Attribute]) -> Option<ArgAttr> {
|
||||
let attrs: Vec<_> = attrs
|
||||
fn get_arg_attributes(attrs: &[Attribute]) -> Vec<ArgAttr> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|a| a.path.is_ident("option") || a.path.is_ident("positional"))
|
||||
.collect();
|
||||
match attrs[..] {
|
||||
[] => None,
|
||||
[attr] => Some(parse_argument_attribute(attr)),
|
||||
_ => panic!("Can only specify one #[option] or #[positional] per argument variant"),
|
||||
}
|
||||
.map(parse_argument_attribute)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn short_handling(args: &[Argument]) -> TokenStream {
|
||||
|
||||
@@ -32,8 +32,9 @@ enum AttributeArguments {
|
||||
File(String),
|
||||
Env(String),
|
||||
ExitCode(i32),
|
||||
Help(Vec<String>),
|
||||
Version(Vec<String>),
|
||||
Help(String),
|
||||
HelpFlags(Vec<String>),
|
||||
VersionFlags(Vec<String>),
|
||||
Last,
|
||||
Hidden,
|
||||
}
|
||||
@@ -69,10 +70,10 @@ impl ArgumentsAttr {
|
||||
let mut arguments_attr = Self::default();
|
||||
for arg in AttributeArguments::parse_all(attr) {
|
||||
match arg {
|
||||
AttributeArguments::Help(flags) => {
|
||||
AttributeArguments::HelpFlags(flags) => {
|
||||
arguments_attr.help_flags = Flags::new(flags);
|
||||
}
|
||||
AttributeArguments::Version(flags) => {
|
||||
AttributeArguments::VersionFlags(flags) => {
|
||||
arguments_attr.version_flags = Flags::new(flags);
|
||||
}
|
||||
AttributeArguments::File(s) => arguments_attr.file = Some(s),
|
||||
@@ -91,6 +92,7 @@ pub(crate) struct OptionAttr {
|
||||
pub(crate) parser: Option<Expr>,
|
||||
pub(crate) default: Option<Expr>,
|
||||
pub(crate) hidden: bool,
|
||||
pub(crate) help: Option<String>,
|
||||
}
|
||||
|
||||
impl OptionAttr {
|
||||
@@ -103,6 +105,7 @@ impl OptionAttr {
|
||||
AttributeArguments::Parser(e) => option_attr.parser = Some(e),
|
||||
AttributeArguments::Default(e) => option_attr.default = Some(e),
|
||||
AttributeArguments::Hidden => option_attr.hidden = true,
|
||||
AttributeArguments::Help(h) => option_attr.help = Some(h),
|
||||
_ => panic!("Invalid argument"),
|
||||
};
|
||||
}
|
||||
@@ -258,8 +261,9 @@ impl Parse for AttributeArguments {
|
||||
"value" => return Ok(Self::Value(input.parse::<Expr>()?)),
|
||||
"file" => return Ok(Self::File(input.parse::<LitStr>()?.value())),
|
||||
"env" => return Ok(Self::Env(input.parse::<LitStr>()?.value())),
|
||||
"help" => return Ok(Self::Help(input.parse::<LitStr>()?.value())),
|
||||
"exit_code" => return Ok(Self::ExitCode(input.parse::<LitInt>()?.base10_parse()?)),
|
||||
"help" => {
|
||||
"help_flags" => {
|
||||
let expr = input.parse::<Expr>()?;
|
||||
let arr = match expr {
|
||||
syn::Expr::Array(arr) => arr,
|
||||
@@ -277,9 +281,9 @@ impl Parse for AttributeArguments {
|
||||
};
|
||||
strings.push(val);
|
||||
}
|
||||
return Ok(Self::Help(strings));
|
||||
return Ok(Self::HelpFlags(strings));
|
||||
}
|
||||
"version" => {
|
||||
"version_flags" => {
|
||||
let expr = input.parse::<Expr>()?;
|
||||
let arr = match expr {
|
||||
syn::Expr::Array(arr) => arr,
|
||||
@@ -299,7 +303,7 @@ impl Parse for AttributeArguments {
|
||||
};
|
||||
strings.push(val);
|
||||
}
|
||||
return Ok(Self::Version(strings));
|
||||
return Ok(Self::VersionFlags(strings));
|
||||
}
|
||||
_ => panic!("Unrecognized argument {} for option attribute", name),
|
||||
};
|
||||
|
||||
+4
-1
@@ -93,7 +93,10 @@ pub(crate) fn help_string(
|
||||
if flags.len() <= #width {
|
||||
let line = match help_lines.next() {
|
||||
Some(line) => line,
|
||||
None => return s,
|
||||
None => {
|
||||
s.push('\n');
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let help_indent = " ".repeat(#width-flags.len()+2);
|
||||
s.push_str(&help_indent);
|
||||
|
||||
+30
-69
@@ -143,32 +143,20 @@ enum Arg {
|
||||
#[option("--author")]
|
||||
Author,
|
||||
|
||||
// === Time ===
|
||||
#[option("-c")]
|
||||
ChangeTime,
|
||||
|
||||
#[option("-u")]
|
||||
AccessTime,
|
||||
|
||||
#[option("--time=WORD")]
|
||||
#[option("-c", default = Time::Change)]
|
||||
#[option("-u", default = Time::Access)]
|
||||
Time(Time),
|
||||
|
||||
// === Sorting ===
|
||||
// === Sorting ==
|
||||
/// Sort by WORD
|
||||
#[option("--sort=WORD")]
|
||||
#[option("-t", default = Sort::Time, help = "Sort by time")]
|
||||
#[option("-U", default = Sort::None, help = "Do not sort")]
|
||||
#[option("-v", default = Sort::Version, help = "Sort by version")]
|
||||
#[option("-X", default = Sort::Extension, help = "Sort by extension")]
|
||||
Sort(Sort),
|
||||
|
||||
#[option("-t")]
|
||||
SortTime,
|
||||
|
||||
#[option("-U")]
|
||||
SortNone,
|
||||
|
||||
#[option("-v")]
|
||||
SortVersion,
|
||||
|
||||
#[option("-X")]
|
||||
SortExtension,
|
||||
|
||||
// === Miscellaneous ===
|
||||
#[option("-Z", "--context")]
|
||||
SecurityContext,
|
||||
@@ -208,21 +196,13 @@ enum Arg {
|
||||
NoGroup,
|
||||
|
||||
// === Format ===
|
||||
/// Set long format
|
||||
#[option("-l", "--long")]
|
||||
Long,
|
||||
|
||||
/// Set columns format
|
||||
#[option("-C")]
|
||||
Columns,
|
||||
|
||||
/// Set across format
|
||||
#[option("-x")]
|
||||
Across,
|
||||
|
||||
/// Set comma format
|
||||
#[option("-m")]
|
||||
Commas,
|
||||
/// Set format
|
||||
#[option("--format=FORMAT")]
|
||||
#[option("-l", "--long", default = Format::Long, help = "Use long format")]
|
||||
#[option("-C", default = Format::Columns, help = "Use columns format")]
|
||||
#[option("-x", default = Format::Across, help = "Use across format")]
|
||||
#[option("-m", default = Format::Commas, help = "Use comma format")]
|
||||
Format(Format),
|
||||
|
||||
/// Show single column
|
||||
#[option("-1")]
|
||||
@@ -237,20 +217,13 @@ enum Arg {
|
||||
#[option("-n", "--numeric-uid-gid")]
|
||||
LongNumericUidGid,
|
||||
|
||||
/// Set format
|
||||
#[option("--format=FORMAT")]
|
||||
Format(Format),
|
||||
|
||||
// === Indicator style ===
|
||||
#[option("--indicator-style=STYLE")]
|
||||
#[option("-p", default = IndicatorStyle::Slash, help = "Append slash to directories")]
|
||||
#[option("--file-type", default = IndicatorStyle::FileType, help = "Add indicators for file types")]
|
||||
IndicatorStyle(IndicatorStyle),
|
||||
|
||||
#[option("-p")]
|
||||
IndicatorStyleSlash,
|
||||
|
||||
#[option("--file-type")]
|
||||
IndicatorStyleFileType,
|
||||
|
||||
/// Classify items
|
||||
#[option("-F", "--classify[=WHEN]", default = When::Always)]
|
||||
IndicatorStyleClassify(When),
|
||||
|
||||
@@ -279,17 +252,11 @@ enum Arg {
|
||||
|
||||
// === Quoting style ===
|
||||
#[option("--quoting-style=STYLE")]
|
||||
#[option("-N", "--literal", default = QuotingStyle::Literal)]
|
||||
#[option("-h", "--escape", default = QuotingStyle::Escape)]
|
||||
#[option("-Q", "--quote-name", default = todo!())]
|
||||
QuotingStyle(QuotingStyle),
|
||||
|
||||
#[option("-N", "--literal")]
|
||||
Literal,
|
||||
|
||||
#[option("-h", "--escape")]
|
||||
Escape,
|
||||
|
||||
#[option("-Q", "--quote-name")]
|
||||
QuoteName,
|
||||
|
||||
/// Set the color
|
||||
#[option("--color[=WHEN]", default = When::Always)]
|
||||
Color(When),
|
||||
@@ -374,14 +341,8 @@ impl Options for Settings {
|
||||
Arg::All => self.which_files = Files::All,
|
||||
Arg::AlmostAll => self.which_files = Files::AlmostAll,
|
||||
Arg::Author => self.long_author = true,
|
||||
Arg::ChangeTime => self.time = Time::Change,
|
||||
Arg::AccessTime => self.time = Time::Access,
|
||||
Arg::Time(t) => self.time = t,
|
||||
Arg::Sort(s) => self.sort = s,
|
||||
Arg::SortTime => self.sort = Sort::Time,
|
||||
Arg::SortNone => self.sort = Sort::None,
|
||||
Arg::SortVersion => self.sort = Sort::Version,
|
||||
Arg::SortExtension => self.sort = Sort::Extension,
|
||||
Arg::SecurityContext => self.context = true,
|
||||
Arg::IgnoreBackups => self.ignore_backups = true,
|
||||
Arg::Directory => self.directory = true,
|
||||
@@ -394,10 +355,6 @@ impl Options for Settings {
|
||||
Arg::Width(w) => self.width = w,
|
||||
Arg::AllocationSize => todo!(),
|
||||
Arg::NoGroup => self.long_no_group = true,
|
||||
Arg::Long => self.format = Format::Long,
|
||||
Arg::Columns => self.format = Format::Columns,
|
||||
Arg::Across => self.format = Format::Across,
|
||||
Arg::Commas => self.format = Format::Commas,
|
||||
Arg::SingleColumn => self.format = Format::SingleColumn,
|
||||
Arg::LongNoGroup => {
|
||||
self.format = Format::Long;
|
||||
@@ -413,8 +370,6 @@ impl Options for Settings {
|
||||
}
|
||||
Arg::Format(f) => self.format = f,
|
||||
Arg::IndicatorStyle(style) => self.indicator_style = style,
|
||||
Arg::IndicatorStyleSlash => self.indicator_style = IndicatorStyle::Slash,
|
||||
Arg::IndicatorStyleFileType => self.indicator_style = IndicatorStyle::FileType,
|
||||
Arg::IndicatorStyleClassify(when) => {
|
||||
self.indicator_style = if when.to_bool() {
|
||||
IndicatorStyle::Classify
|
||||
@@ -429,9 +384,6 @@ impl Options for Settings {
|
||||
Arg::Kibibytes => todo!(),
|
||||
Arg::Si => todo!(),
|
||||
Arg::QuotingStyle(style) => self.quoting_style = style,
|
||||
Arg::Literal => self.quoting_style = QuotingStyle::Literal,
|
||||
Arg::Escape => self.quoting_style = QuotingStyle::Escape,
|
||||
Arg::QuoteName => todo!(),
|
||||
Arg::Color(when) => self.color = when.to_bool(),
|
||||
Arg::HideControlChars => self.hide_control_chars = true,
|
||||
Arg::ShowControlChars => self.hide_control_chars = false,
|
||||
@@ -559,3 +511,12 @@ fn classify() {
|
||||
let s = Settings::parse(["ls", "-F"]);
|
||||
assert_eq!(s.indicator_style, IndicatorStyle::Classify);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort() {
|
||||
let s = Settings::parse(["ls", "--sort=time"]);
|
||||
assert_eq!(s.sort, Sort::Time);
|
||||
|
||||
let s = Settings::parse(["ls", "-X"]);
|
||||
assert_eq!(s.sort, Sort::Extension);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user