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