RegexStoryParser.java

  1. package org.jbehave.core.parsers;

  2. import static java.util.Arrays.asList;
  3. import static java.util.regex.Pattern.DOTALL;
  4. import static java.util.regex.Pattern.compile;
  5. import static org.apache.commons.lang3.StringUtils.removeStart;

  6. import java.util.ArrayList;
  7. import java.util.Arrays;
  8. import java.util.List;
  9. import java.util.regex.Matcher;
  10. import java.util.regex.Pattern;

  11. import org.apache.commons.lang3.StringUtils;
  12. import org.jbehave.core.annotations.AfterScenario.Outcome;
  13. import org.jbehave.core.annotations.Scope;
  14. import org.jbehave.core.io.LoadFromClasspath;
  15. import org.jbehave.core.io.ResourceLoader;
  16. import org.jbehave.core.model.Description;
  17. import org.jbehave.core.model.ExamplesTable;
  18. import org.jbehave.core.model.ExamplesTableFactory;
  19. import org.jbehave.core.model.GivenStories;
  20. import org.jbehave.core.model.Lifecycle;
  21. import org.jbehave.core.model.Lifecycle.Steps;
  22. import org.jbehave.core.model.Meta;
  23. import org.jbehave.core.model.Narrative;
  24. import org.jbehave.core.model.Scenario;
  25. import org.jbehave.core.model.Story;
  26. import org.jbehave.core.model.TableTransformers;

  27. /**
  28.  * Pattern-based story parser, which uses the keywords provided to parse the
  29.  * textual story into a {@link Story}.
  30.  */
  31. public class RegexStoryParser extends AbstractRegexParser implements StoryParser {

  32.     public static final ResourceLoader DEFAULT_RESOURCE_LOADER = new LoadFromClasspath();
  33.     public static final TableTransformers DEFAULT_TABLE_TRANSFORMERS = new TableTransformers();
  34.     private final ExamplesTableFactory tableFactory;

  35.     public RegexStoryParser() {
  36.         this(new ExamplesTableFactory(DEFAULT_RESOURCE_LOADER, DEFAULT_TABLE_TRANSFORMERS));
  37.     }

  38.     public RegexStoryParser(ExamplesTableFactory tableFactory) {
  39.         super(tableFactory.keywords());
  40.         this.tableFactory = tableFactory;
  41.     }

  42.     @Override
  43.     public Story parseStory(String storyAsText) {
  44.         return parseStory(storyAsText, null);
  45.     }

  46.     @Override
  47.     public Story parseStory(String storyAsText, String storyPath) {
  48.         Description description = parseDescriptionFrom(storyAsText);
  49.         Meta meta = parseStoryMetaFrom(storyAsText);
  50.         Narrative narrative = parseNarrativeFrom(storyAsText);
  51.         GivenStories givenStories = parseGivenStories(storyAsText);
  52.         Lifecycle lifecycle = parseLifecycle(storyAsText);
  53.         if (lifecycle != null) {
  54.             ExamplesTable storyExamplesTable = lifecycle.getExamplesTable();
  55.             if (!storyExamplesTable.isEmpty()) {
  56.                 useExamplesTableForGivenStories(givenStories, storyExamplesTable);
  57.             }
  58.         }
  59.         List<Scenario> scenarios = parseScenariosFrom(storyAsText);
  60.         return new Story(storyPath, description, meta, narrative, givenStories, lifecycle, scenarios);
  61.     }

  62.     private Description parseDescriptionFrom(String storyAsText) {
  63.         Matcher findingDescription = findingDescription().matcher(storyAsText);
  64.         if (findingDescription.matches()) {
  65.             return new Description(findingDescription.group(1).trim());
  66.         }
  67.         return Description.EMPTY;
  68.     }

  69.     private Meta parseStoryMetaFrom(String storyAsText) {
  70.         Matcher findingMeta = findingStoryMeta().matcher(preScenarioText(storyAsText));
  71.         if (findingMeta.matches()) {
  72.             String meta = findingMeta.group(1).trim();
  73.             return Meta.createMeta(meta, keywords());
  74.         }
  75.         return Meta.EMPTY;
  76.     }

  77.     private String preScenarioText(String storyAsText) {
  78.         String[] split = storyAsText.split(keywords().scenario());
  79.         return split.length > 0 ? split[0] : storyAsText;
  80.     }

  81.     private Narrative parseNarrativeFrom(String storyAsText) {
  82.         Matcher findingNarrative = findingNarrative().matcher(storyAsText);
  83.         if (findingNarrative.matches()) {
  84.             String narrative = findingNarrative.group(1).trim();
  85.             return createNarrative(narrative);
  86.         }
  87.         return Narrative.EMPTY;
  88.     }

  89.     @SuppressWarnings("checkstyle:LocalVariableName")
  90.     private Narrative createNarrative(String narrative) {
  91.         Matcher findingElements = findingNarrativeElements().matcher(narrative);
  92.         if (findingElements.matches()) {
  93.             String inOrderTo = findingElements.group(1).trim();
  94.             String asA = findingElements.group(2).trim();
  95.             String iWantTo = findingElements.group(3).trim();
  96.             return new Narrative(inOrderTo, asA, iWantTo);
  97.         }
  98.         Matcher findingAlternativeElements = findingAlternativeNarrativeElements().matcher(narrative);
  99.         if (findingAlternativeElements.matches()) {            
  100.             String asA = findingAlternativeElements.group(1).trim();
  101.             String iWantTo = findingAlternativeElements.group(2).trim();
  102.             String soThat = findingAlternativeElements.group(3).trim();
  103.             return new Narrative("", asA, iWantTo, soThat);
  104.         }
  105.         return Narrative.EMPTY;
  106.     }
  107.    
  108.     private GivenStories parseGivenStories(String storyAsText) {
  109.         String scenarioKeyword = keywords().scenario();
  110.         // use text before scenario keyword, if found
  111.         String beforeScenario = "";
  112.         if (StringUtils.contains(storyAsText, scenarioKeyword)) {
  113.             beforeScenario = StringUtils.substringBefore(storyAsText, scenarioKeyword);
  114.         }
  115.         Matcher findingGivenStories = findingStoryGivenStories().matcher(beforeScenario);
  116.         String givenStories = findingGivenStories.find() ? findingGivenStories.group(1).trim() : NONE;
  117.         return new GivenStories(givenStories);
  118.     }

  119.     private Lifecycle parseLifecycle(String storyAsText) {
  120.         String scenarioKeyword = keywords().scenario();
  121.         // use text before scenario keyword, if found
  122.         String beforeScenario = "";
  123.         if (StringUtils.contains(storyAsText, scenarioKeyword)) {
  124.             beforeScenario = StringUtils.substringBefore(storyAsText, scenarioKeyword);
  125.         }
  126.         Matcher findingLifecycle = findingLifecycle().matcher(beforeScenario);
  127.         String lifecycle;
  128.         ExamplesTable examplesTable;
  129.         if (findingLifecycle.find()) {
  130.             lifecycle = findingLifecycle.group(1).trim();
  131.             String examplesTableAsString = findExamplesTable(findingLifecycle.group(0));
  132.             examplesTable = parseExamplesTable(examplesTableAsString);
  133.         } else {
  134.             lifecycle = NONE;
  135.             examplesTable = ExamplesTable.EMPTY;
  136.         }
  137.         Matcher findingBeforeAndAfter = compile(
  138.                 ".*" + keywords().before() + "(.*)\\s*" + keywords().after() + "(.*)\\s*", DOTALL).matcher(lifecycle);
  139.         if (findingBeforeAndAfter.matches()) {
  140.             String beforeLifecycle = findingBeforeAndAfter.group(1).trim();
  141.             List<Steps> beforeSteps = parseBeforeLifecycle(beforeLifecycle);
  142.             String afterLifecycle = findingBeforeAndAfter.group(2).trim();
  143.             List<Steps> afterSteps = parseAfterLifecycle(afterLifecycle);
  144.             return new Lifecycle(examplesTable, beforeSteps, afterSteps);
  145.         }
  146.         Matcher findingBefore = compile(".*" + keywords().before() + "(.*)\\s*", DOTALL).matcher(lifecycle);
  147.         if (findingBefore.matches()) {
  148.             String beforeLifecycle = findingBefore.group(1).trim();
  149.             List<Steps> beforeSteps = parseBeforeLifecycle(beforeLifecycle);
  150.             return new Lifecycle(examplesTable, beforeSteps, Arrays.<Steps>asList());
  151.         }
  152.         Matcher findingAfter = compile(".*" + keywords().after() + "(.*)\\s*", DOTALL).matcher(lifecycle);
  153.         if (findingAfter.matches()) {
  154.             List<Steps> beforeSteps = asList();
  155.             String afterLifecycle = findingAfter.group(1).trim();
  156.             List<Steps> afterSteps = parseAfterLifecycle(afterLifecycle);
  157.             return new Lifecycle(examplesTable, beforeSteps, afterSteps);
  158.         }
  159.         return new Lifecycle(examplesTable);
  160.     }

  161.     private List<Steps> parseBeforeLifecycle(String lifecycleAsText) {
  162.         List<Steps> list = new ArrayList<>();
  163.         for (String byScope : lifecycleAsText.split(keywords().scope())) {
  164.             byScope = byScope.trim();
  165.             if (byScope.isEmpty()) {
  166.                 continue;
  167.             }
  168.             Scope scope = parseScope(findScope(keywords().scope() + byScope));
  169.             Steps steps = new Steps(scope, findSteps(startingWithNL(byScope)));
  170.             list.add(steps);
  171.         }
  172.         return list;
  173.     }

  174.     private List<Steps> parseAfterLifecycle(String lifecycleAsText) {
  175.         List<Steps> list = new ArrayList<>();
  176.         for (String byScope : lifecycleAsText.split(keywords().scope())) {
  177.             byScope = byScope.trim();
  178.             if (byScope.isEmpty()) {
  179.                 continue;
  180.             }
  181.             Scope scope = parseScope(findScope(keywords().scope() + byScope));
  182.             for (String byOutcome : byScope.split(keywords().outcome())) {
  183.                 byOutcome = byOutcome.trim();
  184.                 if (byOutcome.isEmpty()) {
  185.                     continue;
  186.                 }
  187.                 String outcomeAsText = findOutcome(byOutcome);
  188.                 String filtersAsText = findFilters(removeStart(byOutcome, outcomeAsText));
  189.                 List<String> steps = findSteps(startingWithNL(removeStart(byOutcome, filtersAsText)));
  190.                 list.add(new Steps(scope, parseOutcome(outcomeAsText), parseFilters(filtersAsText), steps));
  191.             }
  192.         }
  193.         return list;
  194.     }

  195.     private String findScope(String lifecycleAsText) {
  196.         Matcher findingScope = findingLifecycleScope().matcher(lifecycleAsText.trim());
  197.         if (findingScope.matches()) {
  198.             return findingScope.group(1).trim();
  199.         }
  200.         return NONE;
  201.     }

  202.     private Scope parseScope(String scopeAsText) {
  203.         if (scopeAsText.trim().equals(keywords().scopeStep())) {
  204.             return Scope.STEP;
  205.         } else if (scopeAsText.trim().equals(keywords().scopeScenario())) {
  206.             return Scope.SCENARIO;
  207.         } else if (scopeAsText.trim().equals(keywords().scopeStory())) {
  208.             return Scope.STORY;
  209.         }
  210.         return Scope.SCENARIO;
  211.     }

  212.     private String findOutcome(String stepsByOutcome) {
  213.         Matcher findingOutcome = findingLifecycleOutcome().matcher(stepsByOutcome);
  214.         if (findingOutcome.matches()) {
  215.             return findingOutcome.group(1).trim();
  216.         }
  217.         return keywords().outcomeAny();
  218.     }

  219.     private Outcome parseOutcome(String outcomeAsText) {
  220.         if (outcomeAsText.equals(keywords().outcomeSuccess())) {
  221.             return Outcome.SUCCESS;
  222.         } else if (outcomeAsText.equals(keywords().outcomeFailure())) {
  223.             return Outcome.FAILURE;
  224.         }
  225.         return Outcome.ANY;
  226.     }

  227.     private String findFilters(String stepsByFilters) {
  228.         Matcher findingFilters = findingLifecycleFilters().matcher(stepsByFilters.trim());
  229.         if (findingFilters.matches()) {
  230.             return findingFilters.group(1).trim();
  231.         }
  232.         return NONE;
  233.     }

  234.     private String parseFilters(String filtersAsText) {
  235.         return removeStart(filtersAsText, keywords().metaFilter()).trim();
  236.     }

  237.     private List<Scenario> parseScenariosFrom(String storyAsText) {
  238.         List<Scenario> parsed = new ArrayList<>();
  239.         for (String scenarioAsText : splitScenarios(storyAsText)) {
  240.             parsed.add(parseScenario(scenarioAsText));
  241.         }
  242.         return parsed;
  243.     }

  244.     private List<String> splitScenarios(String storyAsText) {
  245.         String scenarioKeyword = keywords().scenario();

  246.         // use text after scenario keyword, if found
  247.         if (StringUtils.contains(storyAsText, scenarioKeyword)) {
  248.             storyAsText = StringUtils.substringAfter(storyAsText, scenarioKeyword);
  249.         }

  250.         return splitElements(storyAsText, scenarioKeyword);
  251.     }

  252.     private Scenario parseScenario(String scenarioAsText) {
  253.         String title = findScenarioTitle(scenarioAsText);
  254.         String scenarioWithoutKeyword = removeStart(scenarioAsText, keywords().scenario()).trim();
  255.         String scenarioWithoutTitle = removeStart(scenarioWithoutKeyword, title);
  256.         scenarioWithoutTitle = startingWithNL(scenarioWithoutTitle);
  257.         Meta meta = findScenarioMeta(scenarioWithoutTitle);
  258.         String examplesTableAsString = findExamplesTable(scenarioWithoutTitle);
  259.         ExamplesTable examplesTable = parseExamplesTable(examplesTableAsString);
  260.         GivenStories givenStories = findScenarioGivenStories(scenarioWithoutTitle);
  261.         useExamplesTableForGivenStories(givenStories, examplesTable);
  262.         List<String> rawSteps = new ArrayList<>();
  263.         if (examplesTableAsString.trim().isEmpty()) {
  264.             rawSteps.addAll(findSteps(scenarioWithoutTitle));
  265.         } else {
  266.             int afterExampleIndex = scenarioWithoutTitle.indexOf(examplesTableAsString)
  267.                     + examplesTableAsString.length();
  268.             rawSteps.addAll(findSteps(scenarioWithoutTitle.substring(0, afterExampleIndex)));
  269.         }
  270.         return new Scenario(title, meta, givenStories, examplesTable, rawSteps);
  271.     }

  272.     private void useExamplesTableForGivenStories(GivenStories givenStories, ExamplesTable examplesTable) {
  273.         if (givenStories.requireParameters()) {
  274.             givenStories.useExamplesTable(examplesTable);
  275.         }
  276.     }

  277.     private String findScenarioTitle(String scenarioAsText) {
  278.         Matcher findingTitle = findingScenarioTitle().matcher(scenarioAsText);
  279.         return findingTitle.find() ? findingTitle.group(1).trim() : NONE;
  280.     }

  281.     private Meta findScenarioMeta(String scenarioAsText) {
  282.         Matcher findingMeta = findingScenarioMeta().matcher(scenarioAsText);
  283.         if (findingMeta.matches()) {
  284.             String meta = findingMeta.group(1).trim();
  285.             return Meta.createMeta(meta, keywords());
  286.         }
  287.         return Meta.EMPTY;
  288.     }

  289.     private String findExamplesTable(String scenarioAsText) {
  290.         Matcher findingTable = findingExamplesTable().matcher(scenarioAsText);
  291.         return findingTable.find() ? findingTable.group(1).trim() : NONE;
  292.     }

  293.     private ExamplesTable parseExamplesTable(String tableInput) {
  294.         return tableFactory.createExamplesTable(tableInput);
  295.     }

  296.     private GivenStories findScenarioGivenStories(String scenarioAsText) {
  297.         Matcher findingGivenStories = findingScenarioGivenStories().matcher(scenarioAsText);
  298.         String givenStories = findingGivenStories.find() ? findingGivenStories.group(1).trim() : NONE;
  299.         return new GivenStories(givenStories);
  300.     }

  301.     // Regex Patterns

  302.     private Pattern findingDescription() {
  303.         String metaOrNarrativeOrLifecycleOrScenario = concatenateWithOr(keywords().meta(), keywords().narrative(),
  304.                 keywords().lifecycle(), keywords().scenario());
  305.         return compile("(.*?)(" + metaOrNarrativeOrLifecycleOrScenario + ").*", DOTALL);
  306.     }

  307.     private Pattern findingStoryMeta() {
  308.         String narrativeOrLifecycleOrGivenStories = concatenateWithOr(keywords().narrative(), keywords().lifecycle(),
  309.                 keywords().givenStories());
  310.         return compile(".*" + keywords().meta() + "(.*?)\\s*(\\Z|" + narrativeOrLifecycleOrGivenStories + ").*",
  311.                 DOTALL);
  312.     }

  313.     private Pattern findingNarrative() {
  314.         String givenStoriesOrLifecycleOrScenario = concatenateWithOr(keywords().givenStories(), keywords().lifecycle(),
  315.                 keywords().scenario());
  316.         return compile(".*" + keywords().narrative() + "(.*?)\\s*(" + givenStoriesOrLifecycleOrScenario + ").*",
  317.                 DOTALL);
  318.     }

  319.     private Pattern findingNarrativeElements() {
  320.         return compile(".*" + keywords().inOrderTo() + "(.*)\\s*" + keywords().asA() + "(.*)\\s*" + keywords().iWantTo()
  321.                 + "(.*)", DOTALL);
  322.     }

  323.     private Pattern findingAlternativeNarrativeElements() {
  324.         return compile(
  325.                 ".*" + keywords().asA() + "(.*)\\s*" + keywords().iWantTo() + "(.*)\\s*" + keywords().soThat() + "(.*)",
  326.                 DOTALL);
  327.     }
  328.    
  329.     private Pattern findingStoryGivenStories() {
  330.         String lifecycleOrScenario = concatenateWithOr(keywords().lifecycle(), keywords().scenario());
  331.         return compile(".*" + keywords().givenStories() + "(.*?)\\s*(\\Z|" + lifecycleOrScenario + ").*", DOTALL);
  332.     }
  333.    
  334.     private Pattern findingLifecycle() {
  335.         return compile(".*" + keywords().lifecycle() + "\\s*(.*)", DOTALL);
  336.     }

  337.     private Pattern findingLifecycleScope() {
  338.         String startingWords = concatenateStartingWords();
  339.         return compile(keywords().scope() + "((.)*?)\\s*(" + keywords().outcome() + "|" + keywords().metaFilter() + "|"
  340.                 + startingWords + ").*", DOTALL);
  341.     }

  342.     private Pattern findingLifecycleOutcome() {
  343.         String startingWords = concatenateStartingWords();
  344.         String outcomes = concatenateWithOr(keywords().outcomeAny(), keywords().outcomeSuccess(),
  345.                 keywords().outcomeFailure());
  346.         return compile("\\s*(" + outcomes + ")\\s*(" + keywords().metaFilter() + "|" + startingWords + ").*", DOTALL);
  347.     }

  348.     private Pattern findingLifecycleFilters() {
  349.         String startingWords = concatenateStartingWords();
  350.         String filters = concatenateWithOr(keywords().metaFilter());
  351.         return compile("\\s*(" + filters + "[\\w\\+\\-\\_\\s]*)(" + startingWords + ").*", DOTALL);
  352.     }

  353.     private Pattern findingScenarioTitle() {
  354.         String startingWords = concatenateStartingWords();
  355.         return compile(keywords().scenario() + "(.*?)\\s*(" + keywords().meta() + "|" + keywords().givenStories() + "|"
  356.                 + startingWords + "|$).*", DOTALL);
  357.     }

  358.     private Pattern findingScenarioMeta() {
  359.         String startingWords = concatenateStartingWords();
  360.         return compile(
  361.                 ".*" + keywords().meta() + "(.*?)\\s*(" + keywords().givenStories() + "|" + startingWords + "|$).*",
  362.                 DOTALL);
  363.     }

  364.     private Pattern findingScenarioGivenStories() {
  365.         String startingWords = concatenateStartingWords();
  366.         return compile("\\n" + keywords().givenStories() + "((.|\\n)*?)\\s*(" + startingWords + ").*", DOTALL);
  367.     }

  368.     private Pattern findingExamplesTable() {
  369.         return compile("\\n" + keywords().examplesTable() + "\\s*(.*?)(?:\\n" + keywords().ignorable() + ".*)?$",
  370.                 DOTALL);
  371.     }

  372.     private String concatenateWithOr(String... keywords) {
  373.         return concatenateWithOr(NONE, asList(keywords));
  374.     }
  375. }