Skip to content

Commit

Permalink
Change parser to accomodate for all fixture good data
Browse files Browse the repository at this point in the history
  • Loading branch information
mladedav committed Jan 31, 2022
1 parent c7b5c93 commit 77cd3ce
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 43 deletions.
36 changes: 22 additions & 14 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,30 @@ impl<'a> Keywords<'a> {
v
}

pub fn exclude_in_description(&self) -> Vec<&'a str> {
let mut v = [
self.feature,
self.background,
self.rule,
self.scenario,
self.scenario_outline,
self.examples,
]
.iter()
.flat_map(|s| s.iter().map(Deref::deref))
.collect::<Vec<_>>();
pub fn excluded_feature(&'a self) -> Vec<&'a str> {
[self.background, self.rule, self.scenario, self.scenario_outline].concat()
}

v.sort_unstable();
pub fn excluded_rule(&'a self) -> Vec<&'a str> {
[self.background, self.scenario, self.scenario_outline].concat()
}

v
pub fn excluded_background(&'a self) -> Vec<&'a str> {
[self.given, self.when, self.then, self.and, self.but].concat()
}

pub fn excluded_scenario(&'a self) -> Vec<&'a str> {
[self.given, self.when, self.then, self.and, self.but].concat()
}

pub fn excluded_scenario_outline(&'a self) -> Vec<&'a str> {
[self.given, self.when, self.then, self.and, self.but].concat()
}

pub fn excluded_examples(&'a self) -> Vec<&'a str> {
let mut r = [self.given, self.when, self.then, self.and, self.but].concat();
r.push("|");
r
}
}

Expand Down
16 changes: 14 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ pub struct Background {
pub keyword: String,
/// The name of the background.
pub name: Option<String>,
/// The description of the background, if found.
#[cfg_attr(feature = "parser", builder(default))]
pub description: Option<String>,
/// The parsed steps from the background directive.
pub steps: Vec<Step>,
/// The `(start, end)` offset the background directive was found in the .feature file.
Expand All @@ -136,6 +139,9 @@ pub struct Examples {
pub keyword: String,
/// The name of the examples.
pub name: Option<String>,
/// The description of the examples, if found.
#[cfg_attr(feature = "parser", builder(default))]
pub description: Option<String>,
/// The data table from the examples directive.
pub table: Table,
/// The tags for the examples directive if provided.
Expand All @@ -153,7 +159,7 @@ pub struct Examples {
#[cfg_attr(feature = "parser", derive(TypedBuilder))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
#[derive(Debug, Clone, PartialEq, Hash, Eq, Default)]
pub struct Feature {
/// The raw keyword used in the original source.
pub keyword: String,
Expand Down Expand Up @@ -263,6 +269,9 @@ pub struct Rule {
pub keyword: String,
/// The name of the scenario.
pub name: String,
/// The description of the rule, if found.
#[cfg_attr(feature = "parser", builder(default))]
pub description: Option<String>,
/// The background of the rule, if found.
#[cfg_attr(feature = "parser", builder(default))]
pub background: Option<Background>,
Expand All @@ -289,6 +298,9 @@ pub struct Scenario {
pub keyword: String,
/// The name of the scenario.
pub name: String,
/// The description of the scenario, if found.
#[cfg_attr(feature = "parser", builder(default))]
pub description: Option<String>,
/// The parsed steps from the scenario directive.
pub steps: Vec<Step>,
// The parsed examples from the scenario directive if found.
Expand Down Expand Up @@ -361,7 +373,7 @@ pub enum StepType {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "parser", derive(TypedBuilder))]
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
#[derive(Debug, Clone, PartialEq, Hash, Eq, Default)]
pub struct Table {
/// The rows of the data table. Each row is always the same length as the first row.
pub rows: Vec<Vec<String>>,
Expand Down
69 changes: 42 additions & 27 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl Default for GherkinEnv {
peg::parser! { pub(crate) grammar gherkin_parser(env: &GherkinEnv) for str {

rule _() = quiet!{[' ' | '\t']*}
rule __() = quiet!{[' ' | '\t']+}
rule __() = quiet!{[' ' | '\t' | '\n']*}

rule nl0() = quiet!{"\r"? "\n"}
rule nl() = quiet!{nl0() p:position!() comment()* {
Expand All @@ -161,7 +161,7 @@ rule nl_eof() = quiet!{(nl() / [' ' | '\t'])+ / eof()}
rule comment() = quiet!{[' ' | '\t']* "#" $((!nl0()[_])*) nl_eof()}
rule not_nl() -> &'input str = n:$((!nl0()[_])+) { n }

rule keyword1(list: &[&'static str]) -> &'static str
rule keyword1(list: &[&str]) -> &'input str
= input:$([_]*<
{list.iter().map(|x| x.chars().count()).min().unwrap()},
{list.iter().map(|x| x.chars().count()).max().unwrap()}
Expand All @@ -179,22 +179,22 @@ rule keyword1(list: &[&'static str]) -> &'static str
}
}

rule keyword0(list: &[&'static str]) -> usize
rule keyword0(list: &[&str]) -> usize
= keyword1(list)? {?
match env.last_keyword().as_ref() {
Some(v) => Ok(v.chars().count()),
None => Err("no match")
}
}

pub(crate) rule keyword(list: &[&'static str]) -> &'static str
pub(crate) rule keyword<'a>(list: &[&'a str]) -> &'a str
= comment()* len:keyword0(list) [_]*<{len}> {
let kw = env.take_keyword();
list.iter().find(|x| **x == &*kw).unwrap()
list.clone().iter().find(|x| **x == &*kw).unwrap()
}

rule language_directive() -> ()
= "#" _ "language:" _ l:$(not_nl()+) _ nl() {?
= _ "#" _ "language" _ ":" _ l:$(not_nl()+) _ nl() {?
env.set_language(l)
}

Expand Down Expand Up @@ -242,7 +242,7 @@ pub(crate) rule table() -> Table
}

pub(crate) rule step() -> Step
= comment()* pa:position!() k:keyword((env.keywords().given)) __ n:not_nl() pb:position!() _ nl_eof() _
= comment()* pa:position!() k:keyword((env.keywords().given)) _ n:not_nl() pb:position!() _ nl_eof() _
d:docstring()? t:table()?
{
env.set_last_step(StepType::Given);
Expand All @@ -255,7 +255,7 @@ pub(crate) rule step() -> Step
.position(env.position(pa))
.build()
}
/ pa:position!() k:keyword((env.keywords().when)) __ n:not_nl() pb:position!() _ nl_eof() _
/ pa:position!() k:keyword((env.keywords().when)) _ n:not_nl() pb:position!() _ nl_eof() _
d:docstring()? t:table()?
{
env.set_last_step(StepType::When);
Expand All @@ -268,7 +268,7 @@ pub(crate) rule step() -> Step
.position(env.position(pa))
.build()
}
/ pa:position!() k:keyword((env.keywords().then)) __ n:not_nl() pb:position!() _ nl_eof() _
/ pa:position!() k:keyword((env.keywords().then)) _ n:not_nl() pb:position!() _ nl_eof() _
d:docstring()? t:table()?
{
env.set_last_step(StepType::Then);
Expand All @@ -281,7 +281,7 @@ pub(crate) rule step() -> Step
.position(env.position(pa))
.build()
}
/ pa:position!() k:keyword((env.keywords().and)) __ n:not_nl() pb:position!() _ nl_eof() _
/ pa:position!() k:keyword((env.keywords().and)) _ n:not_nl() pb:position!() _ nl_eof() _
d:docstring()? t:table()?
{?
match env.last_step() {
Expand All @@ -300,7 +300,7 @@ pub(crate) rule step() -> Step
}
}
}
/ pa:position!() k:keyword((env.keywords().but)) __ n:not_nl() pb:position!() _ nl_eof() _
/ pa:position!() k:keyword((env.keywords().but)) _ n:not_nl() pb:position!() _ nl_eof() _
d:docstring()? t:table()?
{?
match env.last_step() {
Expand Down Expand Up @@ -329,12 +329,14 @@ pub(crate) rule steps() -> Vec<Step>
rule background() -> Background
= comment()* _ pa:position!()
k:keyword((env.keywords().background)) ":" _ n:not_nl()? nl_eof()
d:description((&env.keywords().excluded_background()))? nl()*
s:steps()?
pb:position!()
{
Background::builder()
.keyword(k.into())
.name(n.map(str::to_string))
.description(d.flatten())
.steps(s.unwrap_or_default())
.span(Span { start: pa, end: pb })
.position(env.position(pa))
Expand All @@ -346,16 +348,16 @@ rule any_directive() -> &'static str
k
}

rule description_line() -> &'input str
rule description_line(excluded: &[&str]) -> &'input str
= _
!"@" !keyword((&*env.keywords().exclude_in_description()))
!"@" !keyword((excluded))
_ n:not_nl() nl_eof()
{
n
}

rule description() -> Option<String>
= d:(description_line() ** _) {
rule description(excluded: &[&str]) -> Option<String>
= d:(description_line(excluded) ** _) {
let d = d.join("\n");
if d.trim() == "" {
None
Expand All @@ -371,14 +373,16 @@ rule examples() -> Examples
_
pa:position!()
k:keyword((env.keywords().examples)) ":" _ n:not_nl()? nl_eof()
tb:table()
d:description((&env.keywords().excluded_examples()))? nl()*
tb:table()?
pb:position!()
{
Examples::builder()
.keyword(k.into())
.name(n.map(str::to_owned))
.description(d.flatten())
.tags(t)
.table(tb)
.table(tb.unwrap_or_default())
.span(Span { start: pa, end: pb })
.position(env.position(pa))
.build()
Expand All @@ -390,14 +394,16 @@ rule scenario() -> Scenario
t:tags()
_
pa:position!()
k:keyword((env.keywords().scenario)) ":" _ n:not_nl() _ nl_eof()
k:keyword((env.keywords().scenario)) ":" _ n:not_nl()? _ nl_eof()
d:description((&env.keywords().excluded_scenario()))? nl()*
s:steps()?
e:examples()*
pb:position!()
{
Scenario::builder()
.keyword(k.into())
.name(n.to_string())
.name(n.unwrap_or_default().to_string())
.description(d.flatten())
.tags(t)
.steps(s.unwrap_or_default())
.examples(e)
Expand All @@ -410,14 +416,16 @@ rule scenario() -> Scenario
t:tags()
_
pa:position!()
k:keyword((env.keywords().scenario_outline)) ":" _ n:not_nl() _ nl_eof()
k:keyword((env.keywords().scenario_outline)) ":" _ n:not_nl()? _ nl_eof()
d:description((&env.keywords().excluded_scenario_outline()))? nl()*
s:steps()?
e:examples()*
pb:position!()
{
Scenario::builder()
.keyword(k.into())
.name(n.to_string())
.name(n.unwrap_or_default().to_string())
.description(d.flatten())
.tags(t)
.steps(s.unwrap_or_default())
.examples(e)
Expand Down Expand Up @@ -470,23 +478,25 @@ pub(crate) rule tag_in_expr() -> String
}

pub(crate) rule tags() -> Vec<String>
= t:(tag() ** _) _ nl()* { t }
= t:(tag() ** __) _ nl()* { t }
/ { vec![] }

rule rule_() -> Rule
= _
t:tags()
_
pa:position!()
k:keyword((env.keywords().rule)) ":" _ n:not_nl() _ nl_eof()
k:keyword((env.keywords().rule)) ":" _ n:not_nl()? _ nl_eof()
d:description((&env.keywords().excluded_rule()))? nl()*
b:background()? nl()*
s:scenarios()? nl()*
// e:examples()?
pb:position!()
{
Rule::builder()
.keyword(k.into())
.name(n.to_string())
.name(n.unwrap_or_default().to_string())
.description(d.flatten())
.tags(t)
.background(b)
.scenarios(s.unwrap_or_default())
Expand All @@ -506,20 +516,21 @@ pub(crate) rule feature() -> Feature
nl()*
t:tags() nl()*
pa:position!()
k:keyword((env.keywords().feature)) ":" _ n:not_nl() _ nl()+
d:description()? nl()*
k:keyword((env.keywords().feature)) ":" _ n:not_nl()? _ nl()+
d:description((&env.keywords().excluded_feature()))? nl()*
b:background()? nl()*
s:scenarios() nl()*
r:rules() pb:position!()
nl()*
{?
if let Err(e) = env.assert_no_error() {
println!("{:?}", e);
Err(e)
} else {
Ok(Feature::builder()
.keyword(k.into())
.tags(t)
.name(n.to_string())
.name(n.unwrap_or_default().to_string())
.description(d.flatten())
.background(b)
.scenarios(s)
Expand All @@ -529,6 +540,10 @@ pub(crate) rule feature() -> Feature
.build())
}
}
/ (comment() / [' ' | '\t' | '\n']+)*
{
Feature::default()
}

pub(crate) rule tag_operation() -> TagOperation = precedence!{
x:@ _ "and" _ y:(@) { TagOperation::And(Box::new(x), Box::new(y)) }
Expand Down
3 changes: 3 additions & 0 deletions tests/cucumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ fn main() {
// You may even have an async main, it doesn't matter. The point is that
// Cucumber is composable. :)
executor::block_on(runner);

let runner = MyWorld::cucumber().run_and_exit("tests/fixtures/data/good");
executor::block_on(runner);
}

0 comments on commit 77cd3ce

Please sign in to comment.