Skip to content

Commit

Permalink
feat: Override DeriveDisplayOrder behavior with App::next_display_order
Browse files Browse the repository at this point in the history
For the derive API, you can only call `next_display_order` when dealing
with a flatten.  Until we offer app attributes on arguments, the user can workaround with
this no-op flattens.

This is a part of clap-rs#1807
  • Loading branch information
epage committed Feb 8, 2022
1 parent 16bf834 commit 5290f82
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 11 deletions.
18 changes: 17 additions & 1 deletion clap_derive/src/attrs.rs
Expand Up @@ -46,6 +46,7 @@ pub struct Attrs {
author: Option<Method>,
version: Option<Method>,
verbatim_doc_comment: Option<Ident>,
next_display_order: Option<Method>,
next_help_heading: Option<Method>,
help_heading: Option<Method>,
is_enum: bool,
Expand Down Expand Up @@ -380,6 +381,7 @@ impl Attrs {
author: None,
version: None,
verbatim_doc_comment: None,
next_display_order: None,
next_help_heading: None,
help_heading: None,
is_enum: false,
Expand Down Expand Up @@ -534,6 +536,10 @@ impl Attrs {
self.methods.push(Method::new(raw_ident, val));
}

NextDisplayOrder(ident, expr) => {
self.next_display_order = Some(Method::new(ident, quote!(#expr)));
}

HelpHeading(ident, expr) => {
self.help_heading = Some(Method::new(ident, quote!(#expr)));
}
Expand Down Expand Up @@ -628,9 +634,14 @@ impl Attrs {

/// generate methods from attributes on top of struct or enum
pub fn initial_top_level_methods(&self) -> TokenStream {
let next_display_order = self.next_display_order.as_ref().into_iter();
let next_help_heading = self.next_help_heading.as_ref().into_iter();
let help_heading = self.help_heading.as_ref().into_iter();
quote!( #(#next_help_heading)* #(#help_heading)* )
quote!(
#(#next_display_order)*
#(#next_help_heading)*
#(#help_heading)*
)
}

pub fn final_top_level_methods(&self) -> TokenStream {
Expand Down Expand Up @@ -661,6 +672,11 @@ impl Attrs {
}
}

pub fn next_display_order(&self) -> TokenStream {
let next_display_order = self.next_display_order.as_ref().into_iter();
quote!( #(#next_display_order)* )
}

pub fn next_help_heading(&self) -> TokenStream {
let next_help_heading = self.next_help_heading.as_ref().into_iter();
let help_heading = self.help_heading.as_ref().into_iter();
Expand Down
5 changes: 3 additions & 2 deletions clap_derive/src/derives/args.rs
Expand Up @@ -214,17 +214,18 @@ pub fn gen_augment(
let ty = &field.ty;
let old_heading_var = format_ident!("__clap_old_heading");
let next_help_heading = attrs.next_help_heading();
let next_display_order = attrs.next_display_order();
if override_required {
Some(quote_spanned! { kind.span()=>
let #old_heading_var = #app_var.get_next_help_heading();
let #app_var = #app_var #next_help_heading;
let #app_var = #app_var #next_help_heading #next_display_order;
let #app_var = <#ty as clap::Args>::augment_args_for_update(#app_var);
let #app_var = #app_var.next_help_heading(#old_heading_var);
})
} else {
Some(quote_spanned! { kind.span()=>
let #old_heading_var = #app_var.get_next_help_heading();
let #app_var = #app_var #next_help_heading;
let #app_var = #app_var #next_help_heading #next_display_order;
let #app_var = <#ty as clap::Args>::augment_args(#app_var);
let #app_var = #app_var.next_help_heading(#old_heading_var);
})
Expand Down
5 changes: 3 additions & 2 deletions clap_derive/src/derives/subcommand.rs
Expand Up @@ -188,17 +188,18 @@ fn gen_augment(
let ty = &unnamed[0];
let old_heading_var = format_ident!("__clap_old_heading");
let next_help_heading = attrs.next_help_heading();
let next_display_order = attrs.next_display_order();
let subcommand = if override_required {
quote! {
let #old_heading_var = #app_var.get_next_help_heading();
let #app_var = #app_var #next_help_heading;
let #app_var = #app_var #next_help_heading #next_display_order;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var);
let #app_var = #app_var.next_help_heading(#old_heading_var);
}
} else {
quote! {
let #old_heading_var = #app_var.get_next_help_heading();
let #app_var = #app_var #next_help_heading;
let #app_var = #app_var #next_help_heading #next_display_order;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var);
let #app_var = #app_var.next_help_heading(#old_heading_var);
}
Expand Down
11 changes: 11 additions & 0 deletions clap_derive/src/parse.rs
Expand Up @@ -54,6 +54,7 @@ pub enum ClapAttr {
NameExpr(Ident, Expr),
DefaultValueT(Ident, Option<Expr>),
DefaultValueOsT(Ident, Option<Expr>),
NextDisplayOrder(Ident, Expr),
NextHelpHeading(Ident, Expr),
HelpHeading(Ident, Expr),

Expand Down Expand Up @@ -115,6 +116,15 @@ impl Parse for ClapAttr {
Ok(Skip(name, Some(expr)))
}

"next_display_order" => {
let expr = ExprLit {
attrs: vec![],
lit: Lit::Str(lit),
};
let expr = Expr::Lit(expr);
Ok(NextDisplayOrder(name, expr))
}

"next_help_heading" => {
let expr = ExprLit {
attrs: vec![],
Expand All @@ -140,6 +150,7 @@ impl Parse for ClapAttr {
"skip" => Ok(Skip(name, Some(expr))),
"default_value_t" => Ok(DefaultValueT(name, Some(expr))),
"default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))),
"next_display_order" => Ok(NextDisplayOrder(name, expr)),
"next_help_heading" => Ok(NextHelpHeading(name, expr)),
"help_heading" => Ok(HelpHeading(name, expr)),
_ => Ok(NameExpr(name, expr)),
Expand Down
1 change: 1 addition & 0 deletions examples/derive_ref/README.md
Expand Up @@ -130,6 +130,7 @@ In addition to the raw attributes, the following magic attributes are supported:
- `long_about = <expr>`: `clap::App::long_about`
- When not present: [Doc comment](#doc-comments) if there is a blank line, else nothing
- `verbatim_doc_comment`: Minimizes pre-processing when converting doc comments to `about` / `long_about`
- `next_display_order`: `clap::App::next_display_order`
- `next_help_heading`: `clap::App::next_help_heading`
- When `flatten`ing `Args`, this is scoped to just the args in this struct and any struct `flatten`ed into it
- `rename_all = <expr>`: Override default field / variant name case conversion for `App::name` / `Arg::name`
Expand Down
22 changes: 19 additions & 3 deletions src/build/app/mod.rs
Expand Up @@ -95,6 +95,7 @@ pub struct App<'help> {
pub(crate) replacers: HashMap<&'help str, &'help [&'help str]>,
pub(crate) groups: Vec<ArgGroup<'help>>,
pub(crate) current_help_heading: Option<&'help str>,
pub(crate) current_disp_ord: usize,
pub(crate) subcommand_value_name: Option<&'help str>,
pub(crate) subcommand_heading: Option<&'help str>,
}
Expand Down Expand Up @@ -169,6 +170,12 @@ impl<'help> App<'help> {
#[must_use]
pub fn arg<A: Into<Arg<'help>>>(mut self, a: A) -> Self {
let mut arg = a.into();
if !arg.is_positional() && arg.provider != ArgProvider::Generated {
let current = self.current_disp_ord;
arg.disp_ord.set_implicit(current);
self.current_disp_ord = current + 1;
}

arg.help_heading.get_or_insert(self.current_help_heading);
self.args.push(arg);
self
Expand Down Expand Up @@ -1362,6 +1369,16 @@ impl<'help> App<'help> {
self
}

/// Change the starting value for assigning future display orders for ags.
///
/// This will be used for any arg that hasn't had [`Arg::display_order`] called.
#[inline]
#[must_use]
pub fn next_display_order(mut self, disp_ord: usize) -> Self {
self.current_disp_ord = disp_ord;
self
}

/// Sets the terminal width at which to wrap help messages.
///
/// Using `0` will ignore terminal widths and use source formatting.
Expand Down Expand Up @@ -3023,14 +3040,13 @@ impl<'help> App<'help> {
debug!("App::_derive_display_order:{}", self.name);

if self.settings.is_set(AppSettings::DeriveDisplayOrder) {
for (i, a) in self
for a in self
.args
.args_mut()
.filter(|a| !a.is_positional())
.filter(|a| a.provider != ArgProvider::Generated)
.enumerate()
{
a.disp_ord.set_explicit(i);
a.disp_ord.make_explicit();
}
for (i, sc) in &mut self.subcommands.iter_mut().enumerate() {
sc.disp_ord.get_or_insert(i);
Expand Down
15 changes: 12 additions & 3 deletions src/build/arg/mod.rs
Expand Up @@ -5189,20 +5189,29 @@ where
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum DisplayOrder {
None,
Implicit(usize),
Explicit(usize),
}

impl DisplayOrder {
pub(crate) fn set_explicit(&mut self, explicit: usize) {
*self = Self::Explicit(explicit)
}

pub(crate) fn set_implicit(&mut self, implicit: usize) {
*self = (*self).max(Self::Implicit(implicit))
}

pub(crate) fn make_explicit(&mut self) {
match *self {
Self::None => *self = Self::Explicit(explicit),
Self::Explicit(_) => {}
Self::None | Self::Explicit(_) => {}
Self::Implicit(disp) => self.set_explicit(disp),
}
}

pub(crate) fn get_explicit(self) -> usize {
match self {
Self::None => 999,
Self::None | Self::Implicit(_) => 999,
Self::Explicit(disp) => disp,
}
}
Expand Down
39 changes: 39 additions & 0 deletions tests/builder/derive_order.rs
Expand Up @@ -133,6 +133,45 @@ fn derive_order() {
));
}

#[test]
fn derive_order_next_order() {
static HELP: &str = "test 1.2
USAGE:
test [OPTIONS]
OPTIONS:
--flag_b first flag
--option_b <option_b> first option
-h, --help Print help information
-V, --version Print version information
--flag_a second flag
--option_a <option_a> second option
";

let app = App::new("test")
.setting(AppSettings::DeriveDisplayOrder)
.version("1.2")
.next_display_order(10000)
.arg(Arg::new("flag_a").long("flag_a").help("second flag"))
.arg(
Arg::new("option_a")
.long("option_a")
.takes_value(true)
.help("second option"),
)
.next_display_order(10)
.arg(Arg::new("flag_b").long("flag_b").help("first flag"))
.arg(
Arg::new("option_b")
.long("option_b")
.takes_value(true)
.help("first option"),
);

assert!(utils::compare_output(app, "test --help", HELP, false));
}

#[test]
fn derive_order_subcommand_propagate() {
let app = App::new("test")
Expand Down
114 changes: 114 additions & 0 deletions tests/derive/help.rs
Expand Up @@ -228,3 +228,117 @@ For more information try --help
"#;
assert_eq!(result.unwrap_err().to_string(), expected);
}

#[test]
fn derive_order_next_order() {
static HELP: &str = "test 1.2
USAGE:
test [OPTIONS]
OPTIONS:
--flag-b first flag
--option-b <OPTION_B> first option
-h, --help Print help information
-V, --version Print version information
--flag-a second flag
--option-a <OPTION_A> second option
";

#[derive(Parser, Debug)]
#[clap(name = "test", version = "1.2")]
#[clap(setting = AppSettings::DeriveDisplayOrder)]
struct Args {
#[clap(flatten)]
a: A,
#[clap(flatten)]
b: B,
}

#[derive(Args, Debug)]
#[clap(next_display_order = 10000)]
struct A {
/// second flag
#[clap(long)]
flag_a: bool,
/// second option
#[clap(long)]
option_a: Option<String>,
}

#[derive(Args, Debug)]
#[clap(next_display_order = 10)]
struct B {
/// first flag
#[clap(long)]
flag_b: bool,
/// first option
#[clap(long)]
option_b: Option<String>,
}

use clap::IntoApp;
let mut app = Args::into_app();

let mut buffer: Vec<u8> = Default::default();
app.write_help(&mut buffer).unwrap();
let help = String::from_utf8(buffer).unwrap();
assert_eq!(help, HELP);
}

#[test]
fn derive_order_next_order_flatten() {
static HELP: &str = "test 1.2
USAGE:
test [OPTIONS]
OPTIONS:
--flag-b first flag
--option-b <OPTION_B> first option
-h, --help Print help information
-V, --version Print version information
--flag-a second flag
--option-a <OPTION_A> second option
";

#[derive(Parser, Debug)]
#[clap(setting = AppSettings::DeriveDisplayOrder)]
#[clap(name = "test", version = "1.2")]
struct Args {
#[clap(flatten)]
#[clap(next_display_order = 10000)]
a: A,
#[clap(flatten)]
#[clap(next_display_order = 10)]
b: B,
}

#[derive(Args, Debug)]
struct A {
/// second flag
#[clap(long)]
flag_a: bool,
/// second option
#[clap(long)]
option_a: Option<String>,
}

#[derive(Args, Debug)]
struct B {
/// first flag
#[clap(long)]
flag_b: bool,
/// first option
#[clap(long)]
option_b: Option<String>,
}

use clap::IntoApp;
let mut app = Args::into_app();

let mut buffer: Vec<u8> = Default::default();
app.write_help(&mut buffer).unwrap();
let help = String::from_utf8(buffer).unwrap();
assert_eq!(help, HELP);
}

0 comments on commit 5290f82

Please sign in to comment.