Repository: google/argh (rust)

Issue:
Positional arguments do not have a help 
```
Running `/work/pump --help`
Usage: /work/pump <topic> [<fields...>] [-w]

pump

Options:
  -w, --wait-for-reply
                    wait for reply
  --help            display usage information
```
```
/// pump
#[derive(Debug, FromArgs)]
struct Args {
    /// topic of message to send
    #[argh(positional)]
    topic: String,
    ...
}
```
Shouldn't positional arguments like topic and fields also have their description displayed in a section such as `Options`? If not, how would the user know the meaning of `topic` and `fields`? Thanks


Pull request:
Print help for positional arguments
Fixes #63

This adds the positional arguments names and descriptions to the help.

Patch:
diff --git a/argh/Cargo.toml b/argh/Cargo.toml
index 053798c..80f71d3 100644
--- a/argh/Cargo.toml
+++ b/argh/Cargo.toml
@@ -1,14 +1,14 @@
 [package]
 name = "argh"
-version = "0.1.5"
+version = "0.1.6"
 authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"]
 edition = "2018"
 keywords = ["args", "arguments", "derive", "cli"]
-license-file = "LICENSE"
+license = "BSD-3-Clause"
 description = "Derive-based argument parser optimized for code size"
 repository = "https://github.com/google/argh"
 readme = "README.md"
 
 [dependencies]
-argh_shared = { version = "0.1.5", path = "../argh_shared" }
-argh_derive = { version = "0.1.5", path = "../argh_derive" }
+argh_shared = { version = "0.1.6", path = "../argh_shared" }
+argh_derive = { version = "0.1.6", path = "../argh_derive" }
diff --git a/argh_derive/Cargo.toml b/argh_derive/Cargo.toml
index dea0d38..39712d5 100644
--- a/argh_derive/Cargo.toml
+++ b/argh_derive/Cargo.toml
@@ -1,9 +1,9 @@
 [package]
 name = "argh_derive"
-version = "0.1.5"
+version = "0.1.6"
 authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"]
 edition = "2018"
-license-file = "LICENSE"
+license = "BSD-3-Clause"
 description = "Derive-based argument parsing optimized for code size"
 repository = "https://github.com/google/argh"
 readme = "README.md"
diff --git a/argh_derive/src/help.rs b/argh_derive/src/help.rs
index 9a2c5b9..a8bbe49 100644
--- a/argh_derive/src/help.rs
+++ b/argh_derive/src/help.rs
@@ -30,7 +30,9 @@ pub(crate) fn help(
     let mut format_lit = "Usage: {command_name}".to_string();
 
     let positional = fields.iter().filter(|f| f.kind == FieldKind::Positional);
-    for arg in positional {
+    let mut has_positional = false;
+    for arg in positional.clone() {
+        has_positional = true;
         format_lit.push(' ');
         positional_usage(&mut format_lit, arg);
     }
@@ -58,6 +60,14 @@ pub(crate) fn help(
     let description = require_description(errors, Span::call_site(), &ty_attrs.description, "type");
     format_lit.push_str(&description);
 
+    if has_positional {
+        format_lit.push_str(SECTION_SEPARATOR);
+        format_lit.push_str("Positional Arguments:");
+        for arg in positional {
+            positional_description(&mut format_lit, arg);
+        }
+    }
+
     format_lit.push_str(SECTION_SEPARATOR);
     format_lit.push_str("Options:");
     for option in options {
@@ -121,14 +131,22 @@ fn lits_section(out: &mut String, heading: &str, lits: &[syn::LitStr]) {
     }
 }
 
+fn get_positional_name(field: &StructField<'_>) -> String {
+    return field
+        .attrs
+        .arg_name
+        .as_ref()
+        .map(LitStr::value)
+        .unwrap_or_else(|| field.name.to_string());
+}
+
 /// Add positional arguments like `[<foo>...]` to a help format string.
 fn positional_usage(out: &mut String, field: &StructField<'_>) {
     if !field.optionality.is_required() {
         out.push('[');
     }
     out.push('<');
-    let name =
-        field.attrs.arg_name.as_ref().map(LitStr::value).unwrap_or_else(|| field.name.to_string());
+    let name = get_positional_name(field);
     out.push_str(&name);
     if field.optionality == Optionality::Repeating {
         out.push_str("...");
@@ -198,6 +216,23 @@ Add a doc comment or an `#[argh(description = \"...\")]` attribute.",
     })
 }
 
+/// Describes a positional argument like this:
+///  hello       positional argument description
+fn positional_description(out: &mut String, field: &StructField<'_>) {
+    let field_name = get_positional_name(field);
+
+    let mut description = String::from("");
+    if let Some(desc) = &field.attrs.description {
+        description = desc.content.value().trim().to_owned();
+    }
+    positional_description_format(out, &field_name, &description)
+}
+
+fn positional_description_format(out: &mut String, name: &str, description: &str) {
+    let info = argh_shared::CommandInfo { name: &*name, description };
+    argh_shared::write_description(out, &info);
+}
+
 /// Describes an option like this:
 ///  -f, --force       force, ignore minor errors. This description
 ///                    is so long that it wraps to the next line.
diff --git a/argh_derive/src/lib.rs b/argh_derive/src/lib.rs
index 94b617f..221425f 100644
--- a/argh_derive/src/lib.rs
+++ b/argh_derive/src/lib.rs
@@ -155,7 +155,7 @@ impl<'a> StructField<'a> {
                     let tokens: TokenStream = tokens
                         .into_iter()
                         .map(|mut tree| {
-                            tree.set_span(default.span().clone());
+                            tree.set_span(default.span());
                             tree
                         })
                         .collect();
@@ -257,7 +257,7 @@ fn impl_from_args_struct(
         #top_or_sub_cmd_impl
     };
 
-    trait_impl.into()
+    trait_impl
 }
 
 fn impl_from_args_struct_from_args<'a>(
@@ -297,7 +297,7 @@ fn impl_from_args_struct_from_args<'a>(
 
     let impl_span = Span::call_site();
 
-    let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span.clone());
+    let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
 
     let append_missing_requirements =
         append_missing_requirements(&missing_requirements_ident, &fields);
@@ -319,7 +319,7 @@ fn impl_from_args_struct_from_args<'a>(
     };
 
     // Identifier referring to a value containing the name of the current command as an `&[&str]`.
-    let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span.clone());
+    let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
     let help = help::help(errors, cmd_name_str_array_ident, type_attrs, &fields, subcommand);
 
     let method_impl = quote_spanned! { impl_span =>
@@ -362,7 +362,7 @@ fn impl_from_args_struct_from_args<'a>(
         }
     };
 
-    method_impl.into()
+    method_impl
 }
 
 fn impl_from_args_struct_redact_arg_values<'a>(
@@ -403,7 +403,7 @@ fn impl_from_args_struct_redact_arg_values<'a>(
 
     let impl_span = Span::call_site();
 
-    let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span.clone());
+    let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
 
     let append_missing_requirements =
         append_missing_requirements(&missing_requirements_ident, &fields);
@@ -431,7 +431,7 @@ fn impl_from_args_struct_redact_arg_values<'a>(
     };
 
     // Identifier referring to a value containing the name of the current command as an `&[&str]`.
-    let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span.clone());
+    let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
     let help = help::help(errors, cmd_name_str_array_ident, type_attrs, &fields, subcommand);
 
     let method_impl = quote_spanned! { impl_span =>
@@ -476,7 +476,7 @@ fn impl_from_args_struct_redact_arg_values<'a>(
         }
     };
 
-    method_impl.into()
+    method_impl
 }
 
 /// Ensures that only the last positional arg is non-required.
diff --git a/argh_shared/Cargo.toml b/argh_shared/Cargo.toml
index d579980..2c24892 100644
--- a/argh_shared/Cargo.toml
+++ b/argh_shared/Cargo.toml
@@ -1,9 +1,9 @@
 [package]
 name = "argh_shared"
-version = "0.1.5"
+version = "0.1.6"
 authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"]
 edition = "2018"
-license-file = "LICENSE"
+license = "BSD-3-Clause"
 description = "Derive-based argument parsing optimized for code size"
 repository = "https://github.com/google/argh"
 readme = "README.md"
diff --git a/argh_shared/src/lib.rs b/argh_shared/src/lib.rs
index 1c1edf9..c6e7a5c 100644
--- a/argh_shared/src/lib.rs
+++ b/argh_shared/src/lib.rs
@@ -23,6 +23,11 @@ pub fn write_description(out: &mut String, cmd: &CommandInfo<'_>) {
     let mut current_line = INDENT.to_string();
     current_line.push_str(cmd.name);
 
+    if cmd.description.is_empty() {
+        new_line(&mut current_line, out);
+        return;
+    }
+
     if !indent_description(&mut current_line) {
         // Start the description on a new line if the flag names already
         // add up to more than DESCRIPTION_INDENT.


Test patch:
diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs
index 963540e..5b1ac7a 100644
--- a/argh/tests/lib.rs
+++ b/argh/tests/lib.rs
@@ -299,6 +299,10 @@ mod positional {
 
 Woot
 
+Positional Arguments:
+  a                 fooey
+  b                 fooey
+
 Options:
   --help            display usage information
 "###,
@@ -891,6 +895,9 @@ Error codes:
 
 Destroy the contents of <file>.
 
+Positional Arguments:
+  name
+
 Options:
   --help            display usage information
 "###,