Allow help argument to option and multiple option attributes

This commit is contained in:
Terts Diepraam
2023-01-23 22:01:31 +01:00
parent 065eb17b19
commit c237b06cfc
4 changed files with 93 additions and 114 deletions
+47 -36
View File
@@ -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 {
+12 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}