public void IgnoresCommentsAnywhereTheyAreEncountered() { var result = parser.Parse(@" # comment outside of anything Feature: Distribution with energy constraints # comment in the story text Verifies that the energy constraints are taken into account when distributing. # comment in the story text Background: # comment in background Given that the test site exists Scenario: Test Scenario # comment in scenario Given the following analog local units: | Alias | TechMin | TechMax | ForecastMin | ForecastMax | EstMaxCap | ObsMin | ObsMax | ProdOrConsCost | Efficiency | | REG_T1_001 | 9 kW | 12 kW | 0 kW | 12 kW | 2 kWh | 0 m | 2 m | 10 | 1 | | REG_T1_002 | 9 kW | 12 kW | 0 kW | 12 kW | 2 kWh | 0 m | 2 m | 100 | 1 | # comment in scenario # comment in scenario # comment in scenario and the following signals: | Tag | Value | | REG_T1_001.CurObs | 2 | | REG_T1_002.CurObs | 2 |" ); var features = result.Features; Assert.AreEqual(1, features.Count); var feature = features[0]; Assert.AreEqual("Distribution with energy constraints", feature.Headline); Assert.AreEqual("Verifies that the energy constraints are taken into account when distributing.\r\n", feature.Description); var scenarios = feature.Scenarios; Assert.AreEqual(1, scenarios.Count); var scenario = scenarios[0]; Assert.AreEqual("Test Scenario", scenario.Headline); var steps = scenario.Steps; Assert.AreEqual(2, steps.Count); Assert.AreEqual(Step.Given("the following analog local units:").ToString(), steps[0].ToString()); Assert.AreEqual(Step.And("the following signals:", StepType.Given).ToString(), steps[1].ToString()); }
///<summary> /// Call this function to parse a piece of text, specifying its file origin (which will generate better error messages). ///</summary> ///<param name="fileName">Name of file origin of the Gherkin-text</param> ///<param name="text">Gherkin-text to parse</param> ///<returns>A parse result including the AST</returns> public ParseResult Parse(string fileName, string text) { var features = new List <Feature>(); using (var reader = new StringReader(text)) { var accumulatedTags = new List <string>(); string line; Feature currentFeature = null; Scenario currentScenario = null; StepType?mostRecentStepType = null; var tableColumnNames = new List <string>(); var parsingExamples = false; var parsingBackground = false; var lineNumber = 0; while ((line = reader.ReadLine()) != null) { line = line.Trim(); lineNumber++; if (ShouldBeIgnored(line)) { continue; } if (line.StartsWith("@")) { var tokens = line.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); var invalidTag = tokens.FirstOrDefault(t => !t.StartsWith("@")); if (invalidTag != null) { throw new GherkinParseException(fileName, lineNumber, line, "'{0}' is not a valid tag - tags must begin with '@', e.g. '@important'", invalidTag); } var tags = tokens.Select(t => t.TrimStart('@')); accumulatedTags.AddRange(tags); continue; } if (line.StartsWith(FeatureIntroduction, Comparison)) { var featureText = line.Substring(FeatureIntroduction.Length).Trim(); currentFeature = new Feature(featureText, accumulatedTags); accumulatedTags.Clear(); features.Add(currentFeature); currentScenario = null; mostRecentStepType = null; continue; } if (currentFeature == null) { currentFeature = Feature.NewAnonymousFeature(accumulatedTags); accumulatedTags.Clear(); features.Add(currentFeature); } if (line.StartsWith("Background:", Comparison)) { parsingBackground = true; continue; } if (line.StartsWith(ScenarioIntroduction, Comparison)) { var scenarioText = line.Substring(line.IndexOf(":") + 1).Trim(); currentScenario = new ExecutableScenario(scenarioText, accumulatedTags.Concat(currentFeature.Tags)); accumulatedTags.Clear(); tableColumnNames.Clear(); currentFeature.Scenarios.Add(currentScenario); mostRecentStepType = null; parsingExamples = parsingBackground = false; continue; } if (line.StartsWith(ScenarioOutlineIntroduction, Comparison)) { var scenarioText = line.Substring(line.IndexOf(":") + 1).Trim(); currentScenario = new ScenarioOutline(scenarioText, accumulatedTags.Concat(currentFeature.Tags)); accumulatedTags.Clear(); tableColumnNames.Clear(); currentFeature.Scenarios.Add(currentScenario); mostRecentStepType = null; parsingExamples = parsingBackground = false; continue; } if (line.StartsWith(ExamplesIntroduction, Comparison)) { if (!(currentScenario is ScenarioOutline)) { throw new GherkinParseException(fileName, lineNumber, line, "Cannot specify examples in an ordinary scenario. Please use the 'Scenario outline:' introduction if you mean to specify examples"); } parsingExamples = true; continue; } if (parsingBackground) { if (string.IsNullOrEmpty(line)) { continue; } if (line.StartsWith("and", Comparison)) { if (mostRecentStepType == null) { throw new GherkinParseException(fileName, lineNumber, line, @"Lines can only be introduced with ""and"" when it's preceded by either ""given"", ""when"", or ""then""."); } currentFeature.BackgroundSteps.Add(Step.And(line.Substring("and".Length).Trim(), mostRecentStepType.Value)); } else if (line.StartsWith("given", Comparison)) { currentFeature.BackgroundSteps.Add(Step.Given(line.Substring("given".Length).Trim())); mostRecentStepType = StepType.Given; tableColumnNames.Clear(); } else if (line.StartsWith("|")) { var tokens = line.Split('|').Select(s => s.Trim()).ToArray(); if (!tableColumnNames.Any()) { tableColumnNames.AddRange(tokens); } else { var dict = new Dictionary <string, string>(); for (var index = 0; index < tokens.Length; index++) { var key = tableColumnNames[index]; if (string.IsNullOrEmpty(key)) { continue; } dict[key] = tokens[index]; } currentFeature.BackgroundSteps.Last().Parameters.Add(dict); } } else { throw new GherkinParseException(fileName, lineNumber, line, @"Expected line to start with either ""given"", or ""|"". Please note that ""when"" or ""then"" steps may not appear inside the background element."); } continue; } if (currentScenario != null) { if (string.IsNullOrEmpty(line)) { continue; } if (line.StartsWith("and", Comparison)) { if (mostRecentStepType == null) { throw new GherkinParseException(fileName, lineNumber, line, @"Lines can only be introduced with ""and"" when it's preceded by either ""given"", ""when"", ""then"", or ""|""."); } currentScenario.Steps.Add(Step.And(line.Substring("and".Length).Trim(), mostRecentStepType.Value)); tableColumnNames.Clear(); } else if (line.StartsWith("given", Comparison)) { currentScenario.Steps.Add(Step.Given(line.Substring("given".Length).Trim())); mostRecentStepType = StepType.Given; tableColumnNames.Clear(); } else if (line.StartsWith("when", Comparison)) { currentScenario.Steps.Add(Step.When(line.Substring("when".Length).Trim())); mostRecentStepType = StepType.When; tableColumnNames.Clear(); } else if (line.StartsWith("then", Comparison)) { currentScenario.Steps.Add(Step.Then(line.Substring("then".Length).Trim())); mostRecentStepType = StepType.Then; tableColumnNames.Clear(); } else if (line.StartsWith("|")) { var tokens = line.Split('|').Select(s => s.Trim()).ToArray(); if (!tableColumnNames.Any()) { tableColumnNames.AddRange(tokens); } else { var dict = new Dictionary <string, string>(); for (var index = 0; index < tokens.Length; index++) { var key = tableColumnNames[index]; if (string.IsNullOrEmpty(key)) { continue; } dict[key] = tokens[index]; } if (parsingExamples) { ((ScenarioOutline)currentScenario).Examples.Add(dict); } else { currentScenario.Steps.Last().Parameters.Add(dict); } } } else { throw new GherkinParseException(fileName, lineNumber, line, @"Expected line to start with either ""given"", ""when"", or ""then""."); } continue; } currentFeature.AddText(line); continue; } } features.ForEach(feature => AssertConsistency(feature, fileName)); return(new ParseResult(features)); }