Repository: google/argh (language: 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



Discussion / hints:
I worked around this by specifying the help for positional arguments manually (relying on #22):

```rust
/** pump

Positional arguments:
  <topic>
  <fields...>
*/
#[derive(Debug, FromArgs)]
struct Args {
    /// topic of message to send
    #[argh(positional)]
    topic: String,
    ...
}
```

Fix patch:
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_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
 "###,