Skip to content

Commit

Permalink
parser: add [] syntax for command tags
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgrinaker committed Jun 13, 2024
1 parent 0b62d19 commit 2bd0058
Show file tree
Hide file tree
Showing 18 changed files with 107 additions and 68 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

**Improvements**

* Add `[]` syntax for command tags, exposed as `Command.tags`.
* Add `\x` escape sequence for hex bytes.
* Add `\u{}` escape sequence for Unicode characters.
* Allow empty commands, keys, and prefixes.
Expand Down
2 changes: 2 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct Command {
pub args: Vec<Argument>,
/// The command prefix, if given.
pub prefix: Option<String>,
/// Any command tags, if given.
pub tags: Vec<String>,
/// Silences the output of this command. This is handled automatically, the
/// [`Runner`](crate::Runner) does not have to take this into account.
pub silent: bool,
Expand Down
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@
//! client1: put ok
//! client2: get key=value
//! ```
//! * [**Tags:**](Command::tags) an optional comma- or space-separated list of
//! tags (strings) enclosed in [] after the command and argumentss. This can
//! be used by the runner e.g. to modify the execution of a command.
//!
//! ```text
//! command [tag]
//! command arg key=value [a,b c]
//! ---
//! ```
//!
//! * [**Silencing:**](Command::silent) a command wrapped in `()` will have its
//! output suppressed. This can be useful e.g. for setup commands whose output
Expand Down
20 changes: 15 additions & 5 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use crate::command::{Argument, Block, Command};
use nom::branch::alt;
use nom::bytes::complete::{escaped_transform, is_not, tag, take, take_while_m_n};
use nom::character::complete::{
alphanumeric1, anychar, char, line_ending, not_line_ending, space0, space1,
alphanumeric1, anychar, char, line_ending, not_line_ending, one_of, space0, space1,
};
use nom::combinator::{consumed, eof, map_res, opt, peek, recognize, value, verify};
use nom::error::ErrorKind;
use nom::multi::{many0, many0_count, many_till};
use nom::multi::{many0, many0_count, many_till, separated_list1};
use nom::sequence::{delimited, pair, preceded, separated_pair, terminated};
use nom::Finish as _;

Expand Down Expand Up @@ -104,10 +104,11 @@ fn command(input: Span) -> IResult<Command> {
let (input, maybe_fail) = opt(terminated(char('!'), space0))(input)?;
let fail = maybe_fail.is_some();

// The command itself.
// The command itself, and any trailing tags.
let line_number = input.location_line();
let (input, name) = string(input)?;
let (mut input, args) = many0(preceded(space1, argument))(input)?;
let (input, args) = many0(preceded(space1, argument))(input)?;
let (mut input, tags) = tags(input)?;

// If silenced, look for the closing brace.
if silent {
Expand All @@ -119,7 +120,7 @@ fn command(input: Span) -> IResult<Command> {
let (input, _) = opt(comment)(input)?;
let (input, _) = line_ending(input)?;

Ok((input, Command { name, args, prefix, silent, fail, line_number }))
Ok((input, Command { name, args, tags, prefix, silent, fail, line_number }))
}

/// Parses a single command argument, consisting of an argument value and
Expand All @@ -132,6 +133,15 @@ fn argument(input: Span) -> IResult<Argument> {
Ok((input, Argument { key: None, value }))
}

/// Parses a list of []-delimited command tags separated by comma or whitespace.
fn tags(input: Span) -> IResult<Vec<String>> {
let (input, tags) = opt(preceded(
space1,
delimited(tag("["), separated_list1(one_of(", "), string), tag("]")),
))(input)?;
Ok((input, tags.unwrap_or_default()))
}

/// Parses a command/output separator: --- followed by a line ending.
fn separator(input: Span) -> IResult<()> {
value((), terminated(tag("---"), alt((line_ending, eof))))(input)
Expand Down
3 changes: 3 additions & 0 deletions tests/errors/bare_tag.error
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parse error at line 1 column 1 for Tag:
[tag]
^
2 changes: 2 additions & 0 deletions tests/errors/bare_tag.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tag]
---
3 changes: 3 additions & 0 deletions tests/errors/empty_tag.error
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parse error at line 1 column 9 for CrLf:
command []
^
2 changes: 2 additions & 0 deletions tests/errors/empty_tag.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
command []
---
6 changes: 3 additions & 3 deletions tests/generate/empty_output.out
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
# an empty block at the end of the file.
command
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 3 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 3 }

command
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 6 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 6 }

command
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 9 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 9 }
31 changes: 19 additions & 12 deletions tests/scripts/commands
Original file line number Diff line number Diff line change
@@ -1,44 +1,51 @@
# A bare command.
command
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 2 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 2 }

# Commands with arguments.
foo arg
bar key=value
baz arg key=value
---
Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 7 }
Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 8 }
Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 9 }
Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, tags: [], silent: false, fail: false, line_number: 7 }
Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, tags: [], silent: false, fail: false, line_number: 8 }
Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, tags: [], silent: false, fail: false, line_number: 9 }

# Commands with tags.
foo [tag]
foo arg key=value [a,b c]
---
Command { name: "foo", args: [], prefix: None, tags: ["tag"], silent: false, fail: false, line_number: 16 }
Command { name: "foo", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, tags: ["a", "b", "c"], silent: false, fail: false, line_number: 17 }

# Commands with prefixes.
a: foo arg
b: bar key=value
---
a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), silent: false, fail: false, line_number: 16 }
b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), silent: false, fail: false, line_number: 17 }
a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), tags: [], silent: false, fail: false, line_number: 23 }
b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), tags: [], silent: false, fail: false, line_number: 24 }

# Failing commands.
! foo bar
---
Error: Command { name: "foo", args: [Argument { key: None, value: "bar" }], prefix: None, silent: false, fail: true, line_number: 23 }
Error: Command { name: "foo", args: [Argument { key: None, value: "bar" }], prefix: None, tags: [], silent: false, fail: true, line_number: 30 }

# Prefixes, commands, and keys can be empty.
"": "" ""=""
---
: Command { name: "", args: [Argument { key: Some(""), value: "" }], prefix: Some(""), silent: false, fail: false, line_number: 28 }
: Command { name: "", args: [Argument { key: Some(""), value: "" }], prefix: Some(""), tags: [], silent: false, fail: false, line_number: 35 }

# Prefixes, commands, and keys can be whitespace.
" ": " " " "=" "
---
: Command { name: " ", args: [Argument { key: Some(" "), value: " " }], prefix: Some(" "), silent: false, fail: false, line_number: 33 }
: Command { name: " ", args: [Argument { key: Some(" "), value: " " }], prefix: Some(" "), tags: [], silent: false, fail: false, line_number: 40 }

# Empty argument keys and values are fine.
command ""
command arg=""
command arg=
---
Command { name: "command", args: [Argument { key: None, value: "" }], prefix: None, silent: false, fail: false, line_number: 38 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 39 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 40 }
Command { name: "command", args: [Argument { key: None, value: "" }], prefix: None, tags: [], silent: false, fail: false, line_number: 45 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, tags: [], silent: false, fail: false, line_number: 46 }
Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, tags: [], silent: false, fail: false, line_number: 47 }
10 changes: 5 additions & 5 deletions tests/scripts/comments
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@
# Comment before command.
command id=1
---
Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 6 }
Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, tags: [], silent: false, fail: false, line_number: 6 }

command id=2 # Comment beside command.
---
Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 10 }
Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, tags: [], silent: false, fail: false, line_number: 10 }

command id=3
# Comment after command.
---
Command { name: "command", args: [Argument { key: Some("id"), value: "3" }], prefix: None, silent: false, fail: false, line_number: 14 }
Command { name: "command", args: [Argument { key: Some("id"), value: "3" }], prefix: None, tags: [], silent: false, fail: false, line_number: 14 }

# Comment between blocks.

command id=4
---
Command { name: "command", args: [Argument { key: Some("id"), value: "4" }], prefix: None, silent: false, fail: false, line_number: 21 }
Command { name: "command", args: [Argument { key: Some("id"), value: "4" }], prefix: None, tags: [], silent: false, fail: false, line_number: 21 }

command id=5 // Comment using //.
---
Command { name: "command", args: [Argument { key: Some("id"), value: "5" }], prefix: None, silent: false, fail: false, line_number: 25 }
Command { name: "command", args: [Argument { key: Some("id"), value: "5" }], prefix: None, tags: [], silent: false, fail: false, line_number: 25 }

# Comment at end.
12 changes: 6 additions & 6 deletions tests/scripts/dos_line_endings
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@

command
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 3 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 3 }

// Comment.

foo arg
bar key=value
baz arg key=value
---
Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 9 }
Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 10 }
Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 11 }
Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, tags: [], silent: false, fail: false, line_number: 9 }
Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, tags: [], silent: false, fail: false, line_number: 10 }
Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, tags: [], silent: false, fail: false, line_number: 11 }

# Comment.
a: foo arg
# Comment.
b: bar key=value
# Comment.
---
a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), silent: false, fail: false, line_number: 18 }
b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), silent: false, fail: false, line_number: 20 }
a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), tags: [], silent: false, fail: false, line_number: 18 }
b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), tags: [], silent: false, fail: false, line_number: 20 }

# Comment.
2 changes: 1 addition & 1 deletion tests/scripts/fail
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# the runner expects an error.
! command arg
---
Error: Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: true, line_number: 3 }
Error: Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, tags: [], silent: false, fail: true, line_number: 3 }

# Errors and panics are handled when ! is given.
! _error foo
Expand Down
20 changes: 10 additions & 10 deletions tests/scripts/hooks
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
_set start_block="start" end_block="end"
command
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 3 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 3 }
end

command
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 8 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 8 }
end

# Newlines in block hooks should be handled appropriately.
_set start_block="start\n" end_block="end\n"
command
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 16 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 16 }
end

command
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 22 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 22 }
end

# As should empty lines.
_set start_block="start\n\n" end_block="end\n\n"
command
---
> start
> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 30 }
> Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 30 }
> end
>

Expand All @@ -39,7 +39,7 @@ command
---
> start
>
> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 38 }
> Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 38 }
> end

_set start_block="" end_block=""
Expand All @@ -55,10 +55,10 @@ prefix: command
(command)
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 53 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 53 }
end
prefix: start
prefix: Command { name: "command", args: [], prefix: Some("prefix"), silent: false, fail: false, line_number: 54 }
prefix: Command { name: "command", args: [], prefix: Some("prefix"), tags: [], silent: false, fail: false, line_number: 54 }
prefix: end

# They should also be called after commands that were expected to fail.
Expand All @@ -73,14 +73,14 @@ end
command
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 73 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 73 }
end

(_set start_command="start\n\n" end_command="end\n\n")
command
---
> start
>
> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 80 }
> Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 80 }
> end
>
18 changes: 9 additions & 9 deletions tests/scripts/output
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
command
command arg
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 2 }
Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 3 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 2 }
Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, tags: [], silent: false, fail: false, line_number: 3 }

# Output that contains empty lines should automatically be prefixed with >.
# This should be the case for empty lines at the start and end of the output,
Expand All @@ -13,28 +13,28 @@ command id=1
command id=2
---
>
> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 12 }
> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, tags: [], silent: false, fail: false, line_number: 12 }
>
> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 13 }
> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, tags: [], silent: false, fail: false, line_number: 13 }

_set prefix="" suffix="\n\n"
command id=1
command id=2
---
> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 21 }
> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, tags: [], silent: false, fail: false, line_number: 21 }
>
> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 22 }
> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, tags: [], silent: false, fail: false, line_number: 22 }
>

_set prefix="\n" suffix="\n\n"
command id=1
command id=2
---
>
> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 30 }
> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, tags: [], silent: false, fail: false, line_number: 30 }
>
>
> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 31 }
> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, tags: [], silent: false, fail: false, line_number: 31 }
>

# Empty output blocks should default to "ok", but only once, and only if none of
Expand All @@ -54,7 +54,7 @@ ok
command
(command)
---
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 54 }
Command { name: "command", args: [], prefix: None, tags: [], silent: false, fail: false, line_number: 54 }

_set start_block="" end_block="end"
---
Expand Down
Loading

0 comments on commit 2bd0058

Please sign in to comment.