Repository: google/argh
Pull request #174 (2023-10-16 11:18:49)

docs: fix `cargo_from_env` documentation
Replace "env variable" with "env argument"

google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/google/argh/pull/174/checks?check_run_id=17736802089) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 87f3c6c..6f2d36b 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -33,7 +33,7 @@ jobs:
     - uses: actions/checkout@v2
 
     - name: Run clippy
-      run: cargo clippy --version && cargo clippy --tests --workspace -- -D warnings
+      run: cargo clippy --version && cargo clippy --tests --workspace -- -D warnings -A clippy::needless-raw-string-hashes
 
   docs:
     name: docs
diff --git a/argh/src/lib.rs b/argh/src/lib.rs
index c419e6a..b191459 100644
--- a/argh/src/lib.rs
+++ b/argh/src/lib.rs
@@ -720,7 +720,7 @@ pub fn from_env<T: TopLevelCommand>() -> T {
 /// Create a `FromArgs` type from the current process's `env::args`.
 ///
 /// This special cases usages where argh is being used in an environment where cargo is
-/// driving the build. We skip the second env variable.
+/// driving the build. We skip the second env argument.
 ///
 /// This function will exit early from the current process if argument parsing
 /// was unsuccessful or if information like `--help` was requested. Error messages will be printed
diff --git a/argh_derive/src/parse_attrs.rs b/argh_derive/src/parse_attrs.rs
index f968ca7..70af45e 100644
--- a/argh_derive/src/parse_attrs.rs
+++ b/argh_derive/src/parse_attrs.rs
@@ -500,7 +500,7 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Desc
         return;
     };
 
-    // Don't replace an existing description.
+    // Don't replace an existing explicit description.
     if slot.as_ref().map(|d| d.explicit).unwrap_or(false) {
         return;
     }
@@ -509,14 +509,43 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Desc
         let lit_str = if let Some(previous) = slot {
             let previous = &previous.content;
             let previous_span = previous.span();
-            syn::LitStr::new(&(previous.value() + &*lit_str.value()), previous_span)
+            syn::LitStr::new(&(previous.value() + &unescape_doc(lit_str.value())), previous_span)
         } else {
-            lit_str.clone()
+            syn::LitStr::new(&unescape_doc(lit_str.value()), lit_str.span())
         };
         *slot = Some(Description { explicit: false, content: lit_str });
     }
 }
 
+/// Replaces escape sequences in doc-comments with the characters they represent.
+///
+/// Rustdoc understands CommonMark escape sequences consisting of a backslash followed by an ASCII
+/// punctuation character. Any other backslash is treated as a literal backslash.
+fn unescape_doc(s: String) -> String {
+    let mut result = String::with_capacity(s.len());
+
+    let mut characters = s.chars().peekable();
+    while let Some(mut character) = characters.next() {
+        if character == '\\' {
+            if let Some(next_character) = characters.peek() {
+                if next_character.is_ascii_punctuation() {
+                    character = *next_character;
+                    characters.next();
+                }
+            }
+        }
+
+        // Braces must be escaped as this string will be used as a format string
+        if character == '{' || character == '}' {
+            result.push(character);
+        }
+
+        result.push(character);
+    }
+
+    result
+}
+
 fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) {
     let lit_str =
         if let Some(lit_str) = errors.expect_lit_str(&m.value) { lit_str } else { return };


Test patch:
diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs
index dbd3b48..0f3998a 100644
--- a/argh/tests/lib.rs
+++ b/argh/tests/lib.rs
@@ -303,6 +303,30 @@ Options:
     );
 }
 
+#[test]
+fn escaped_doc_comment_description() {
+    #[derive(FromArgs)]
+    /// A \description\:
+    /// \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~\
+    struct Cmd {
+        #[argh(switch)]
+        /// a \description\:
+        /// \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~\
+        _s: bool,
+    }
+
+    assert_help_string::<Cmd>(
+        r###"Usage: test_arg_0 [--s]
+
+A \description: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\
+
+Options:
+  --s               a \description: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~\
+  --help            display usage information
+"###,
+    );
+}
+
 #[test]
 fn explicit_long_value_for_option() {
     #[derive(FromArgs, Debug)]