Skip to content

Commit

Permalink
feat: support multi-line doc (#103)
Browse files Browse the repository at this point in the history
* feat: support multi-line doc

close #97

* update test
  • Loading branch information
sigoden committed May 18, 2023
1 parent 6a8bec6 commit 423d502
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 28 deletions.
15 changes: 9 additions & 6 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn export(source: &str, name: &str) -> Result<serde_json::Value> {
pub struct Command {
pub(crate) name: Option<String>,
pub(crate) fn_name: Option<String>,
pub(crate) describe: Option<String>,
pub(crate) describe: String,
pub(crate) flag_option_params: Vec<FlagOptionParam>,
pub(crate) positional_params: Vec<PositionalParam>,
pub(crate) positional_pos: Vec<Position>,
Expand Down Expand Up @@ -132,7 +132,7 @@ impl Command {
match data {
EventData::Describe(value) => {
let cmd = Self::get_cmd(&mut root_cmd, "@describe", position)?;
cmd.describe = Some(value);
cmd.describe = value;
}
EventData::Version(value) => {
let cmd = Self::get_cmd(&mut root_cmd, "@version", position)?;
Expand All @@ -150,7 +150,7 @@ impl Command {
root_data.borrow_mut().scope = EventScope::CmdStart;
let mut subcmd = root_cmd.create_cmd();
if !value.is_empty() {
subcmd.describe = Some(value.clone());
subcmd.describe = value.clone();
}
}
EventData::Aliases(values) => {
Expand Down Expand Up @@ -265,8 +265,8 @@ impl Command {
if let Some(author) = &self.author {
output.push(author.to_string());
}
if let Some(describe) = &self.describe {
output.push(describe.to_string());
if !&self.describe.is_empty() {
output.push(self.describe.to_string());
}
if !output.is_empty() {
output.push(String::new());
Expand Down Expand Up @@ -436,7 +436,10 @@ impl Command {
}

pub(crate) fn render_subcommand_describe(&self) -> String {
let mut output = self.describe.clone().unwrap_or_default();
let mut output = match self.describe.split_once('\n') {
Some((v, _)) => v.to_string(),
None => self.describe.clone(),
};
if self.aliases.is_empty() {
return output;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ fn comp_subcommands_positional(cmd: &Command, values: &[Vec<&str>]) -> Vec<(Stri
fn comp_subcomands(cmd: &Command) -> Vec<(String, String)> {
let mut output = vec![];
for subcmd in cmd.subcommands.iter() {
let describe = subcmd.describe.clone().unwrap_or_default();
let describe = subcmd.describe.clone();
for v in subcmd.list_names() {
output.push((v, describe.clone()))
}
Expand Down
59 changes: 57 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ use crate::param::{FlagOptionParam, ParamData, PositionalParam};
use crate::utils::{is_choice_value_terminate, is_default_value_terminate};
use crate::Result;
use anyhow::bail;
use nom::character::complete::one_of;
use nom::{
branch::alt,
bytes::complete::{escaped, tag, take_till, take_while1},
character::{
complete::{anychar, char, satisfy, space0, space1},
streaming::none_of,
},
combinator::{eof, fail, map, opt, peek, rest, success},
combinator::{eof, fail, map, not, opt, peek, rest, success},
multi::{many0, many1, separated_list1},
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
};
Expand Down Expand Up @@ -59,12 +60,36 @@ impl Default for EventScope {
/// Tokenize shell script
pub fn parse(source: &str) -> Result<Vec<Event>> {
let mut result = vec![];
for (line_idx, line) in source.lines().enumerate() {
let lines: Vec<&str> = source.lines().collect();
let mut line_idx = 0;
while line_idx < lines.len() {
let line = lines[line_idx];
let position = line_idx + 1;
match parse_line(line) {
Ok((_, maybe_token)) => {
if let Some(maybe_data) = maybe_token {
if let Some(data) = maybe_data {
let data = match data {
EventData::Describe(mut text) => {
line_idx += take_comment_lines(&lines, line_idx + 1, &mut text);
EventData::Describe(text)
}
EventData::Cmd(mut text) => {
line_idx += take_comment_lines(&lines, line_idx + 1, &mut text);
EventData::Cmd(text)
}
EventData::FlagOption(mut param) => {
line_idx +=
take_comment_lines(&lines, line_idx + 1, &mut param.describe);
EventData::FlagOption(param)
}
EventData::Positional(mut param) => {
line_idx +=
take_comment_lines(&lines, line_idx + 1, &mut param.describe);
EventData::Positional(param)
}
v => v,
};
result.push(Event { position, data });
} else {
bail!("syntax error at line {}", position)
Expand All @@ -75,6 +100,7 @@ pub fn parse(source: &str) -> Result<Vec<Event>> {
bail!("fail to parse at line {}, {}", position, err)
}
}
line_idx += 1;
}
Ok(result)
}
Expand Down Expand Up @@ -485,6 +511,20 @@ fn parse_notation_text(input: &str) -> nom::IResult<&str, &str> {
Ok((&input[size - 1..], &input[0..size - 1]))
}

fn parse_normal_comment(input: &str) -> nom::IResult<&str, &str> {
alt((
map(tuple((many1(char('#')), space0, eof)), |_| ""),
map(
tuple((
many1(char('#')),
opt(one_of(" \t")),
not(pair(space0, char('@'))),
)),
|_| "",
),
))(input)
}

fn notation_text(input: &str, balances: usize) -> nom::IResult<&str, usize> {
let (i1, c1) = anychar(input)?;
match c1 {
Expand Down Expand Up @@ -550,6 +590,21 @@ fn is_short_char(c: char) -> bool {
c.is_ascii() && is_not_fn_name_char(c) && !matches!(c, '-')
}

fn take_comment_lines(lines: &[&str], idx: usize, output: &mut String) -> usize {
let mut count = 0;
for line in lines.iter().skip(idx) {
if let Ok((text, _)) = parse_normal_comment(line) {
output.push('\n');
output.push_str(text);
count += 1;
} else {
break;
}
}
*output = output.trim().to_string();
count
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
7 changes: 4 additions & 3 deletions tests/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ macro_rules! snapshot {
(
$path:expr,
$source:expr,
$args:expr
$args:expr,
$width:expr
) => {
let args: Vec<String> = $args.iter().map(|v| v.to_string()).collect();
let values = argc::eval($path, $source, &args, None).unwrap();
let values = argc::eval($path, $source, &args, $width).unwrap();
let output = argc::ArgcValue::to_shell(values);
let args = $args.join(" ");
let output = format!(
Expand All @@ -26,7 +27,7 @@ OUTPUT
macro_rules! snapshot_spec {
($args:expr) => {
let (path, source) = $crate::fixtures::get_spec();
snapshot!(Some(path.as_str()), source.as_str(), $args);
snapshot!(Some(path.as_str()), source.as_str(), $args, None);
};
}

Expand Down
6 changes: 3 additions & 3 deletions tests/main_fn_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ main() {
}
"###;
plain!(script, &["prog", "cmd"], "cmd");
snapshot!(None, script, &["prog", "-h"]);
snapshot!(None, script, &["prog", "-h"], None);
}

#[test]
Expand All @@ -48,7 +48,7 @@ cmd() {
"###;
plain!(script, &["prog", "cmd"], "cmd");
snapshot!(None, script, &["prog"]);
snapshot!(None, script, &["prog"], None);
}

#[test]
Expand All @@ -62,5 +62,5 @@ cmd() {
}
"###;
snapshot!(None, script, &["prog", "-h"]);
snapshot!(None, script, &["prog", "-h"], None);
}
17 changes: 16 additions & 1 deletion tests/snapshots/integration__wrap_test__wrap.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@
source: tests/wrap_test.rs
expression: output
---
RUN
prog -h

OUTPUT
cat >&2 <<-'EOF'
USAGE: prog --help [OPTIONS] [TARGET] <COMMAND>
A simple cli

Extra lines after the @cmd or @describe, which don't start with an @, are
treated as the long description. A line which is not a comment ends
the block.

USAGE: prog [OPTIONS] [TARGET] <COMMAND>

ARGS:
[TARGET] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed
viverra tellus in hac habitasse platea.
Use '-' for standard input.

OPTIONS:
--foo <FOO> Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Neque laoreet suspendisse libero id.
* default: enables recommended style components (default).
* full: enables all available components.
* auto: same as 'default', unless the output is piped.
-h, --help Print help

COMMANDS:
Expand All @@ -23,3 +37,4 @@ COMMANDS:

EOF
exit 0

20 changes: 20 additions & 0 deletions tests/snapshots/integration__wrap_test__wrap2.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: tests/wrap_test.rs
expression: output
---
RUN
prog foo -h

OUTPUT
cat >&2 <<-'EOF'
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu cursus euismod quis viverra.

Extra lines after the @cmd or @describe, which don't start with an @, are
treated as the long description. A line which is not a comment ends
the block.

USAGE: prog foo

EOF
exit 0

35 changes: 23 additions & 12 deletions tests/wrap_test.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
macro_rules! snapshort_wrap {
($source:expr, $width:expr) => {
let args: Vec<String> = vec!["prog --help".into()];
let values = argc::eval(None, $source, &args, Some($width)).unwrap();
let output = argc::ArgcValue::to_shell(values);
insta::assert_snapshot!(output);
};
}
const SCRIPT: &str = r###"
# @describe A simple cli
#
# Extra lines after the @cmd or @describe, which don't start with an @, are
# treated as the long description. A line which is not a comment ends
# the block.
#[test]
fn test_wrap() {
let script: &str = r###"
# @option --foo Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Neque laoreet suspendisse libero id.
# * default: enables recommended style components (default).
# * full: enables all available components.
# * auto: same as 'default', unless the output is piped.
# @arg target Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed viverra tellus in hac habitasse platea.
# Use '-' for standard input.
# @cmd Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu cursus euismod quis viverra.
#
# Extra lines after the @cmd or @describe, which don't start with an @, are
# treated as the long description. A line which is not a comment ends
# the block.
foo() {
}
"###;
snapshort_wrap!(script, 80);

#[test]
fn test_wrap() {
snapshot!(None, SCRIPT, &["prog", "-h"], Some(80));
}

#[test]
fn test_wrap2() {
snapshot!(None, SCRIPT, &["prog", "foo", "-h"], Some(80));
}

0 comments on commit 423d502

Please sign in to comment.