diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs index c4e58a9..4743284 100644 --- a/argh/tests/lib.rs +++ b/argh/tests/lib.rs @@ -213,23 +213,53 @@ fn multiline_doc_comment_description() { /// Short description struct Cmd { #[argh(switch)] - /// a switch with a description + /// A switch with a description /// that is spread across /// a number of /// lines of comments. + /// + /// It can have multiple paragraphs + /// too and those should be + /// collapsed into one line and + /// reflowed. + /// + /// * It can also: have lists + /// * That are: not reflowed + /// + /// | And | Tables | + /// |------|--------| + /// | work | too | + /// + /// The basic rule is that lines that start with + /// an alphabetic character (a-zA-Z) are joined + /// to the previous line. _s: bool, } + // The \x20s are so that editors don't strip the trailing spaces. assert_help_string::( - r###"Usage: test_arg_0 [--s] + "Usage: test_arg_0 [--s] Short description Options: - --s a switch with a description that is spread across a number + --s A switch with a description that is spread across a number of lines of comments. + \x20 + It can have multiple paragraphs too and those should be + collapsed into one line and reflowed. + \x20 + * It can also: have lists + * That are: not reflowed + \x20 + | And | Tables | + |------|--------| + | work | too | + \x20 + The basic rule is that lines that start with an alphabetic + character (a-zA-Z) are joined to the previous line. --help display usage information -"###, +", ); } diff --git a/argh_derive/src/parse_attrs.rs b/argh_derive/src/parse_attrs.rs index f229862..c605dc7 100644 --- a/argh_derive/src/parse_attrs.rs +++ b/argh_derive/src/parse_attrs.rs @@ -506,6 +506,40 @@ fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut V } } +/// Extend the string `value` with the `new_line`. `new_line` is expected to +/// not end with a newline. +/// +/// This is used for constructing a description of some command/options from +/// its docstring by processing the docstring lines one at a time (because +/// single-line docstring comments get converted to separate attributes). +/// +/// The logic implemented here is intended to join paragraphs into a single +/// line, while leaving other content like indented code, lists, tables, etc. +/// untouched. Roughly like Markdown. +/// +fn extend_docstring(doc: &mut String, new_line: &str) { + // Skip the space immediately after the `///` if present. + let new_line = match new_line.bytes().next() { + Some(b' ') => &new_line[1..], + _ => new_line, + }; + let line_starts_with_letter = new_line.bytes().next().map(|c| c.is_ascii_alphabetic()).unwrap_or(false); + if line_starts_with_letter { + if !doc.is_empty() { + if doc.ends_with('\n') { + doc.push('\n'); + } else { + doc.push(' '); + } + } + } else { + doc.push('\n'); + } + + doc.push_str(new_line); +} + + fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option) { let nv = if let Some(nv) = attr_to_meta_name_value(errors, attr) { nv @@ -520,9 +554,10 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option) { new_line(&mut current_line, out); } - let mut words = cmd.description.split(' ').peekable(); - while let Some(first_word) = words.next() { - indent_description(&mut current_line); - current_line.push_str(first_word); + for line in cmd.description.lines() { + let mut words = line.split(' ').peekable(); + while let Some(first_word) = words.next() { + indent_description(&mut current_line); + current_line.push_str(first_word); - 'inner: while let Some(&word) = words.peek() { - if (char_len(¤t_line) + char_len(word) + 1) > WRAP_WIDTH { - new_line(&mut current_line, out); - break 'inner; - } else { - // advance the iterator - let _ = words.next(); - current_line.push(' '); - current_line.push_str(word); + while let Some(&word) = words.peek() { + if (char_len(¤t_line) + char_len(word) + 1) > WRAP_WIDTH { + new_line(&mut current_line, out); + break; + } else { + // advance the iterator + let _ = words.next(); + current_line.push(' '); + current_line.push_str(word); + } } } + new_line(&mut current_line, out); } - new_line(&mut current_line, out); } // Indent the current line in to DESCRIPTION_INDENT chars.