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