Skip to content

Commit

Permalink
feat: multiline results
Browse files Browse the repository at this point in the history
Co-authored-by: ruben beck <[email protected]>
Co-authored-by: Shahar "Dawn" Or <[email protected]>
  • Loading branch information
3 people committed Jun 2, 2024
1 parent 4838493 commit c8b068c
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 69 deletions.
5 changes: 0 additions & 5 deletions src/app/state/repl_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,6 @@ impl Iterator for ReplSessionLive {

#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref, derive_more::Display)]
pub(crate) struct ExpectedResult(pub(crate) String);
impl ExpectedResult {
pub(crate) fn empty() -> ExpectedResult {
Self(String::new())
}
}

impl From<LFLine> for ExpectedResult {
fn from(expected_result: LFLine) -> Self {
Expand Down
24 changes: 20 additions & 4 deletions src/repl/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ use crate::example_id::ExampleId;
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref, derive_more::Display)]
pub(crate) struct LFLine(String);

impl std::str::FromStr for LFLine {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
impl LFLine {
fn validate_str(s: &str) -> anyhow::Result<()> {
s.chars()
.with_position()
.try_for_each(|(position, character)| {
Expand All @@ -23,10 +21,28 @@ impl std::str::FromStr for LFLine {
(_, _) => Ok(()),
}
})?;
Ok(())
}
}

impl std::str::FromStr for LFLine {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::validate_str(s)?;
Ok(Self(s.to_string()))
}
}

impl TryFrom<String> for LFLine {
type Error = anyhow::Error;

fn try_from(s: String) -> Result<Self, Self::Error> {
Self::validate_str(&s)?;
Ok(Self(s))
}
}

#[derive(Debug, Clone, derive_more::Deref, PartialEq, Eq)]
pub(crate) struct ReplQuery(LFLine);

Expand Down
100 changes: 43 additions & 57 deletions src/repl/example.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::bail;

use crate::{app::state::repl_state::ExpectedResult, example_id::ExampleId};

use super::driver::{LFLine, ReplQuery};
use super::driver::ReplQuery;

#[derive(Debug, Clone)]
pub(crate) struct ReplExample {
Expand All @@ -20,66 +22,29 @@ impl ReplExample {
#[derive(Debug, Clone, derive_more::IntoIterator)]
pub(crate) struct ReplExampleEntries(Vec<ReplEntry>);

#[derive(Debug, Default)]
struct ParseState {
entries: Vec<ReplEntry>,
expecting: Expecting,
}

#[derive(Debug, Default, PartialEq, Eq)]
enum Expecting {
#[default]
PromptAndQuery,
ResultOrBlankLine(ReplQuery),
BlankLine(ReplQuery, ExpectedResult),
}

impl std::str::FromStr for ReplExampleEntries {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let final_state =
s.split_inclusive('\n')
.try_fold(ParseState::default(), |mut state, line| {
let line = LFLine::from_str(line)?;
match state.expecting {
Expecting::PromptAndQuery => {
let Some(line) = line.strip_prefix("nix-repl> ") else {
anyhow::bail!("expected prompt, found {line:?}");
};
let query = LFLine::from_str(line).unwrap();
let query = ReplQuery::new(query);
state.expecting = Expecting::ResultOrBlankLine(query);
}
Expecting::ResultOrBlankLine(query) => {
if line.as_str() == "\n" {
state
.entries
.push(ReplEntry::new(query, ExpectedResult::empty()));
state.expecting = Expecting::PromptAndQuery;
} else {
let expected = ExpectedResult::from(line);
state.expecting = Expecting::BlankLine(query, expected);
}
}
Expecting::BlankLine(query, expected) => {
anyhow::ensure!(
line.as_str() == "\n",
"expected blank line, found {line:?}"
);
state.entries.push(ReplEntry::new(query, expected));
state.expecting = Expecting::PromptAndQuery;
}
}
Ok(state)
})?;

anyhow::ensure!(
final_state.expecting == Expecting::PromptAndQuery,
"failed to parse"
);

Ok(Self(final_state.entries))
let Some(rest) = s.strip_prefix("nix-repl> ") else {
bail!("repl example must start with a prompt")
};

let entries = rest
.split("\nnix-repl> ")
.map(|pair| {
let Some((query, expected)) = pair.split_once('\n') else {
bail!("query must be followed by a line feed")
};

Ok(ReplEntry::new(
ReplQuery::new(format!("{query}\n").try_into().unwrap()),
ExpectedResult(expected.trim_end().to_owned()),
))
})
.collect::<Result<_, _>>()?;

Ok(Self(entries))
}
}

Expand Down Expand Up @@ -236,5 +201,26 @@ mod test {
},
],
},
{
input: indoc! {r#"
nix-repl> { a=1; b=2; }
{
a = 1
b = 2
}
"#},
expected_output: [
{
query: "{ a=1; b=2; }\n",
expected_result: indoc! {"
{
a = 1
b = 2
}\
"},
},
],
},
];
}
29 changes: 26 additions & 3 deletions tests/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ fn fails_to_parse() {
let file_path = file.path().to_str().unwrap();

eelco.assert().failure().stderr(
predicates::str::starts_with(format!("Error: {file_path}:1")).and(
predicates::str::contains("expected prompt, found LFLine(\"nix-shnepl> nope\\n\")"),
),
predicates::str::starts_with(format!("Error: {file_path}:1"))
.and(predicates::str::contains("prompt")),
);
});
}
Expand Down Expand Up @@ -111,3 +110,27 @@ fn pass_subsequent_query() {
.stderr(format!("PASS: {file_path}:1\n"));
});
}

#[test]
fn multiline_result() {
with_eelco(|file, eelco| {
file.write_str(indoc! {"
```nix-repl
nix-repl> { a = 2; b = 3; }
{
a = 2;
b = 3;
}
```
"})
.unwrap();

let file_path = file.path().to_str().unwrap();

eelco
.assert()
.success()
.stderr(format!("PASS: {file_path}:1\n"));
});
}

0 comments on commit c8b068c

Please sign in to comment.