Coverage Report - org.jbehave.core.parsers.RegexStoryParser
 
Classes in this File Line Coverage Branch Coverage Complexity
RegexStoryParser
98%
152/154
98%
55/56
2.056
 
 1  
 package org.jbehave.core.parsers;
 2  
 
 3  
 import java.io.File;
 4  
 import java.util.ArrayList;
 5  
 import java.util.List;
 6  
 import java.util.regex.Matcher;
 7  
 import java.util.regex.Pattern;
 8  
 
 9  
 import org.apache.commons.lang.StringUtils;
 10  
 import org.jbehave.core.configuration.Keywords;
 11  
 import org.jbehave.core.i18n.LocalizedKeywords;
 12  
 import org.jbehave.core.model.Description;
 13  
 import org.jbehave.core.model.ExamplesTable;
 14  
 import org.jbehave.core.model.ExamplesTableFactory;
 15  
 import org.jbehave.core.model.GivenStories;
 16  
 import org.jbehave.core.model.Lifecycle;
 17  
 import org.jbehave.core.model.Meta;
 18  
 import org.jbehave.core.model.Narrative;
 19  
 import org.jbehave.core.model.Scenario;
 20  
 import org.jbehave.core.model.Story;
 21  
 
 22  
 import static java.util.regex.Pattern.DOTALL;
 23  
 import static java.util.regex.Pattern.compile;
 24  
 import static org.apache.commons.lang.StringUtils.removeStart;
 25  
 
 26  
 /**
 27  
  * Pattern-based story parser, which uses the keywords provided to parse the
 28  
  * textual story into a {@link Story}.
 29  
  */
 30  
 public class RegexStoryParser implements StoryParser {
 31  
 
 32  
     private static final String NONE = "";
 33  
     private final Keywords keywords;
 34  
     private final ExamplesTableFactory tableFactory;
 35  
 
 36  
     public RegexStoryParser() {
 37  36
         this(new LocalizedKeywords());
 38  36
     }
 39  
 
 40  
     public RegexStoryParser(Keywords keywords) {
 41  594
         this(keywords, new ExamplesTableFactory());
 42  594
     }
 43  
 
 44  
     public RegexStoryParser(ExamplesTableFactory tableFactory) {
 45  0
         this(new LocalizedKeywords(), tableFactory);
 46  0
     }
 47  
 
 48  594
     public RegexStoryParser(Keywords keywords, ExamplesTableFactory tableFactory) {
 49  594
         this.keywords = keywords;
 50  594
         this.tableFactory = tableFactory;
 51  594
     }
 52  
 
 53  
     public Story parseStory(String storyAsText) {
 54  4
         return parseStory(storyAsText, null);
 55  
     }
 56  
 
 57  
     public Story parseStory(String storyAsText, String storyPath) {
 58  35
         Description description = parseDescriptionFrom(storyAsText);
 59  35
         Meta meta = parseStoryMetaFrom(storyAsText);
 60  35
         Narrative narrative = parseNarrativeFrom(storyAsText);
 61  35
         GivenStories givenStories = parseGivenStories(storyAsText);
 62  35
         Lifecycle lifecycle = parseLifecycle(storyAsText);
 63  35
         List<Scenario> scenarios = parseScenariosFrom(storyAsText);
 64  35
         Story story = new Story(storyPath, description, meta, narrative, givenStories, lifecycle, scenarios);
 65  35
         if (storyPath != null) {
 66  28
             story.namedAs(new File(storyPath).getName());
 67  
         }
 68  35
         return story;
 69  
     }
 70  
 
 71  
     private Description parseDescriptionFrom(String storyAsText) {
 72  35
         Matcher findingDescription = patternToPullDescriptionIntoGroupOne().matcher(storyAsText);
 73  35
         if (findingDescription.matches()) {
 74  24
             return new Description(findingDescription.group(1).trim());
 75  
         }
 76  11
         return Description.EMPTY;
 77  
     }
 78  
 
 79  
     private Meta parseStoryMetaFrom(String storyAsText) {
 80  35
         Matcher findingMeta = patternToPullStoryMetaIntoGroupOne().matcher(preScenarioText(storyAsText));
 81  35
         if (findingMeta.matches()) {
 82  4
             String meta = findingMeta.group(1).trim();
 83  4
             return Meta.createMeta(meta, keywords);
 84  
         }
 85  31
         return Meta.EMPTY;
 86  
     }
 87  
 
 88  
     private String preScenarioText(String storyAsText) {
 89  35
         String[] split = storyAsText.split(keywords.scenario());
 90  35
         return split.length > 0 ? split[0] : storyAsText;
 91  
     }
 92  
 
 93  
     private Narrative parseNarrativeFrom(String storyAsText) {
 94  35
         Matcher findingNarrative = patternToPullNarrativeIntoGroupOne().matcher(storyAsText);
 95  35
         if (findingNarrative.matches()) {
 96  4
             String narrative = findingNarrative.group(1).trim();
 97  4
             return createNarrative(narrative);
 98  
         }
 99  31
         return Narrative.EMPTY;
 100  
     }
 101  
 
 102  
     private Narrative createNarrative(String narrative) {
 103  4
         Matcher findingElements = patternToPullNarrativeElementsIntoGroups().matcher(narrative);
 104  4
         if (findingElements.matches()) {
 105  2
             String inOrderTo = findingElements.group(1).trim();
 106  2
             String asA = findingElements.group(2).trim();
 107  2
             String iWantTo = findingElements.group(3).trim();
 108  2
             return new Narrative(inOrderTo, asA, iWantTo);
 109  
         }
 110  2
         Matcher findingAlternativeElements = patternToPullAlternativeNarrativeElementsIntoGroups().matcher(narrative);
 111  2
         if (findingAlternativeElements.matches()) {            
 112  1
             String asA = findingAlternativeElements.group(1).trim();
 113  1
             String iWantTo = findingAlternativeElements.group(2).trim();
 114  1
             String soThat = findingAlternativeElements.group(3).trim();
 115  1
             return new Narrative("", asA, iWantTo, soThat);
 116  
         }
 117  1
         return Narrative.EMPTY;
 118  
     }
 119  
     
 120  
     private GivenStories parseGivenStories(String storyAsText) {
 121  35
         String scenarioKeyword = keywords.scenario();
 122  
         // use text before scenario keyword, if found
 123  35
         String beforeScenario = "";
 124  35
         if (StringUtils.contains(storyAsText, scenarioKeyword)) {
 125  23
             beforeScenario = StringUtils.substringBefore(storyAsText, scenarioKeyword);
 126  
         }
 127  35
         Matcher findingGivenStories = patternToPullStoryGivenStoriesIntoGroupOne().matcher(beforeScenario);
 128  35
         String givenStories = findingGivenStories.find() ? findingGivenStories.group(1).trim() : NONE;
 129  35
         return new GivenStories(givenStories);
 130  
     }
 131  
 
 132  
     private Lifecycle parseLifecycle(String storyAsText) {
 133  35
         String scenarioKeyword = keywords.scenario();
 134  
         // use text before scenario keyword, if found
 135  35
         String beforeScenario = "";
 136  35
         if (StringUtils.contains(storyAsText, scenarioKeyword)) {
 137  23
             beforeScenario = StringUtils.substringBefore(storyAsText, scenarioKeyword);
 138  
         }
 139  35
         Matcher findingLifecycle = patternToPullLifecycleIntoGroupOne().matcher(beforeScenario);
 140  35
         String lifecycle = findingLifecycle.find() ? findingLifecycle.group(1).trim() : NONE;
 141  35
         Matcher findingBeforeAndAfter = compile(".*" + keywords.before() + "(.*)\\s*" + keywords.after() + "(.*)\\s*" , DOTALL).matcher(lifecycle);
 142  35
         if ( findingBeforeAndAfter.matches() ){
 143  2
             List<String> beforeSteps = findSteps(startingWithNL(findingBeforeAndAfter.group(1).trim()));
 144  2
             List<String> afterSteps = findSteps(startingWithNL(findingBeforeAndAfter.group(2).trim()));
 145  2
             return new Lifecycle(beforeSteps, afterSteps);
 146  
         }
 147  33
         Matcher findingBefore = compile(".*" + keywords.before() + "(.*)\\s*" , DOTALL).matcher(lifecycle);
 148  33
         if ( findingBefore.matches() ){
 149  1
             List<String> beforeSteps = findSteps(startingWithNL(findingBefore.group(1).trim()));
 150  1
             List<String> afterSteps = new ArrayList<String>();
 151  1
             return new Lifecycle(beforeSteps, afterSteps);
 152  
         }
 153  32
         Matcher findingAfter = compile(".*" + keywords.after() + "(.*)\\s*" , DOTALL).matcher(lifecycle);
 154  32
         if ( findingAfter.matches() ){
 155  1
             List<String> beforeSteps = new ArrayList<String>();
 156  1
             List<String> afterSteps = findSteps(startingWithNL(findingAfter.group(1).trim()));
 157  1
             return new Lifecycle(beforeSteps, afterSteps);
 158  
         }
 159  31
         return Lifecycle.EMPTY;
 160  
     }
 161  
 
 162  
     private List<Scenario> parseScenariosFrom(String storyAsText) {
 163  35
         List<Scenario> parsed = new ArrayList<Scenario>();
 164  35
         for (String scenarioAsText : splitScenarios(storyAsText)) {
 165  136
             parsed.add(parseScenario(scenarioAsText));
 166  136
         }
 167  35
         return parsed;
 168  
     }
 169  
 
 170  
     private List<String> splitScenarios(String storyAsText) {
 171  35
         List<String> scenarios = new ArrayList<String>();
 172  35
         String scenarioKeyword = keywords.scenario();
 173  
 
 174  
         // use text after scenario keyword, if found
 175  35
         if (StringUtils.contains(storyAsText, scenarioKeyword)) {
 176  23
             storyAsText = StringUtils.substringAfter(storyAsText, scenarioKeyword);
 177  
         }
 178  
 
 179  173
         for (String scenarioAsText : storyAsText.split(scenarioKeyword)) {
 180  138
             if (scenarioAsText.trim().length() > 0) {
 181  136
                 scenarios.add(scenarioKeyword + "\n" + scenarioAsText);
 182  
             }
 183  
         }
 184  
         
 185  35
         return scenarios;
 186  
     }
 187  
 
 188  
     private Scenario parseScenario(String scenarioAsText) {
 189  136
         String title = findScenarioTitle(scenarioAsText);
 190  136
         String scenarioWithoutKeyword = removeStart(scenarioAsText, keywords.scenario()).trim();
 191  136
         String scenarioWithoutTitle = removeStart(scenarioWithoutKeyword, title);
 192  136
         scenarioWithoutTitle = startingWithNL(scenarioWithoutTitle);
 193  136
         Meta meta = findScenarioMeta(scenarioWithoutTitle);
 194  136
         ExamplesTable examplesTable = findExamplesTable(scenarioWithoutTitle);
 195  136
         GivenStories givenStories = findScenarioGivenStories(scenarioWithoutTitle);
 196  136
         if (givenStories.requireParameters()) {
 197  1
             givenStories.useExamplesTable(examplesTable);
 198  
         }
 199  136
         List<String> steps = findSteps(scenarioWithoutTitle);
 200  136
         return new Scenario(title, meta, givenStories, examplesTable, steps);
 201  
     }
 202  
 
 203  
     private String startingWithNL(String text) {
 204  142
         if ( !text.startsWith("\n") ){ // always ensure starts with newline
 205  29
             return "\n" + text;
 206  
         }
 207  113
         return text;
 208  
     }
 209  
 
 210  
     private String findScenarioTitle(String scenarioAsText) {
 211  136
         Matcher findingTitle = patternToPullScenarioTitleIntoGroupOne().matcher(scenarioAsText);
 212  136
         return findingTitle.find() ? findingTitle.group(1).trim() : NONE;
 213  
     }
 214  
 
 215  
     private Meta findScenarioMeta(String scenarioAsText) {
 216  136
         Matcher findingMeta = patternToPullScenarioMetaIntoGroupOne().matcher(scenarioAsText);
 217  136
         if (findingMeta.matches()) {
 218  5
             String meta = findingMeta.group(1).trim();
 219  5
             return Meta.createMeta(meta, keywords);
 220  
         }
 221  131
         return Meta.EMPTY;
 222  
     }
 223  
 
 224  
     private ExamplesTable findExamplesTable(String scenarioAsText) {
 225  136
         Matcher findingTable = patternToPullExamplesTableIntoGroupOne().matcher(scenarioAsText);
 226  136
         String tableInput = findingTable.find() ? findingTable.group(1).trim() : NONE;
 227  136
         return tableFactory.createExamplesTable(tableInput);
 228  
     }
 229  
 
 230  
     private GivenStories findScenarioGivenStories(String scenarioAsText) {
 231  136
         Matcher findingGivenStories = patternToPullScenarioGivenStoriesIntoGroupOne().matcher(scenarioAsText);
 232  136
         String givenStories = findingGivenStories.find() ? findingGivenStories.group(1).trim() : NONE;
 233  136
         return new GivenStories(givenStories);
 234  
     }
 235  
 
 236  
     private List<String> findSteps(String scenarioAsText) {
 237  142
         Matcher matcher = patternToPullStepsIntoGroupOne().matcher(scenarioAsText);
 238  142
         List<String> steps = new ArrayList<String>();
 239  142
         int startAt = 0;
 240  15232
         while (matcher.find(startAt)) {
 241  15090
             steps.add(StringUtils.substringAfter(matcher.group(1), "\n"));
 242  15090
             startAt = matcher.start(4);
 243  
         }
 244  142
         return steps;
 245  
     }
 246  
 
 247  
     // Regex Patterns
 248  
 
 249  
     private Pattern patternToPullDescriptionIntoGroupOne() {
 250  35
         String metaOrNarrativeOrLifecycleOrScenario = concatenateWithOr(keywords.meta(), keywords.narrative(), keywords.lifecycle(), keywords.scenario());
 251  35
         return compile("(.*?)(" + metaOrNarrativeOrLifecycleOrScenario + ").*", DOTALL);
 252  
     }
 253  
 
 254  
     private Pattern patternToPullStoryMetaIntoGroupOne() {
 255  35
         String narrativeOrGivenStories = concatenateWithOr(keywords.narrative(), keywords.givenStories());
 256  35
         return compile(".*" + keywords.meta() + "(.*?)\\s*(\\Z|" + narrativeOrGivenStories + ").*", DOTALL);
 257  
     }
 258  
 
 259  
     private Pattern patternToPullNarrativeIntoGroupOne() {
 260  35
         String givenStoriesOrLifecycleOrScenario = concatenateWithOr(keywords.givenStories(), keywords.lifecycle(), keywords.scenario());
 261  35
         return compile(".*" + keywords.narrative() + "(.*?)\\s*(" + givenStoriesOrLifecycleOrScenario + ").*", DOTALL);
 262  
     }
 263  
 
 264  
     private Pattern patternToPullNarrativeElementsIntoGroups() {
 265  4
         return compile(".*" + keywords.inOrderTo() + "(.*)\\s*" + keywords.asA() + "(.*)\\s*" + keywords.iWantTo()
 266  
                 + "(.*)", DOTALL);
 267  
     }
 268  
 
 269  
     private Pattern patternToPullAlternativeNarrativeElementsIntoGroups() {
 270  2
         return compile(".*" + keywords.asA() + "(.*)\\s*" + keywords.iWantTo() + "(.*)\\s*" + keywords.soThat()
 271  
                 + "(.*)", DOTALL);
 272  
     }
 273  
     
 274  
     private Pattern patternToPullStoryGivenStoriesIntoGroupOne() {
 275  35
         String lifecycleOrScenario = concatenateWithOr(keywords.lifecycle(), keywords.scenario());
 276  35
         return compile(".*" + keywords.givenStories() + "(.*?)\\s*(\\Z|" + lifecycleOrScenario + ").*", DOTALL);
 277  
     }
 278  
     
 279  
     private Pattern patternToPullLifecycleIntoGroupOne() {
 280  35
         return compile(".*" + keywords.lifecycle() + "\\s*(.*)", DOTALL);
 281  
     }
 282  
     
 283  
     private Pattern patternToPullScenarioTitleIntoGroupOne() {
 284  136
         String startingWords = concatenateWithOr("\\n", "", keywords.startingWords());
 285  136
         return compile(keywords.scenario() + "((.)*?)\\s*(" + keywords.meta() + "|" + startingWords + ").*", DOTALL);
 286  
     }
 287  
 
 288  
     private Pattern patternToPullScenarioMetaIntoGroupOne() {
 289  136
         String startingWords = concatenateWithOr("\\n", "", keywords.startingWords());
 290  136
         return compile(".*" + keywords.meta() + "(.*?)\\s*(" + keywords.givenStories() + "|" + startingWords + ").*",
 291  
                 DOTALL);
 292  
     }
 293  
 
 294  
     private Pattern patternToPullScenarioGivenStoriesIntoGroupOne() {
 295  136
         String startingWords = concatenateWithOr("\\n", "", keywords.startingWords());
 296  136
         return compile("\\n" + keywords.givenStories() + "((.|\\n)*?)\\s*(" + startingWords + ").*", DOTALL);
 297  
     }
 298  
 
 299  
     private Pattern patternToPullStepsIntoGroupOne() {
 300  142
         String initialStartingWords = concatenateWithOr("\\n", "", keywords.startingWords());
 301  142
         String followingStartingWords = concatenateWithOr("\\n", "\\s", keywords.startingWords());
 302  142
         return compile(
 303  
                 "((" + initialStartingWords + ")\\s(.)*?)\\s*(\\Z|" + followingStartingWords + "|\\n"
 304  
                         + keywords.examplesTable() + ")", DOTALL);
 305  
     }
 306  
 
 307  
     private Pattern patternToPullExamplesTableIntoGroupOne() {
 308  136
         return compile("\\n" + keywords.examplesTable() + "\\s*(.*)", DOTALL);
 309  
     }
 310  
 
 311  
     private String concatenateWithOr(String... keywords) {
 312  140
         return concatenateWithOr(null, null, keywords);
 313  
     }
 314  
 
 315  
     private String concatenateWithOr(String beforeKeyword, String afterKeyword, String[] keywords) {
 316  832
         StringBuilder builder = new StringBuilder();
 317  832
         String before = beforeKeyword != null ? beforeKeyword : NONE;
 318  832
         String after = afterKeyword != null ? afterKeyword : NONE;
 319  4687
         for (String keyword : keywords) {
 320  3855
             builder.append(before).append(keyword).append(after).append("|");
 321  
         }
 322  832
         return StringUtils.chomp(builder.toString(), "|"); // chop off the last
 323  
                                                            // "|"
 324  
     }
 325  
 
 326  
 }