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