Skip to content

Commit 38177f7

Browse files
committed
Merge pull request #1 from guardian/javaSnippetGenerator
Java snippet generator
2 parents f63f6c3 + 90272f1 commit 38177f7

File tree

15 files changed

+208
-9
lines changed

15 files changed

+208
-9
lines changed

core/src/main/java/cucumber/runtime/Runtime.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private boolean hasErrors() {
162162
}
163163

164164
public List<String> getSnippets() {
165-
return undefinedStepsTracker.getSnippets(backends);
165+
return undefinedStepsTracker.getSnippets(backends, runtimeOptions.getSnippetType());
166166
}
167167

168168
public Glue getGlue() {

core/src/main/java/cucumber/runtime/RuntimeOptions.java

+8
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class RuntimeOptions {
3737
private boolean dryRun;
3838
private boolean strict = false;
3939
private boolean monochrome = false;
40+
private SnippetType snippet = SnippetType.getDefault();
4041

4142
public RuntimeOptions(Properties properties, String... argv) {
4243
/* IMPORTANT! Make sure USAGE.txt is always uptodate if this class changes */
@@ -97,6 +98,9 @@ private void parse(List<String> args) {
9798
strict = !arg.startsWith("--no-");
9899
} else if (arg.equals("--no-monochrome") || arg.equals("--monochrome") || arg.equals("-m")) {
99100
monochrome = !arg.startsWith("--no-");
101+
} else if (arg.equals("--snippets")) {
102+
String nextArg = args.remove(0);
103+
snippet = SnippetType.fromString(nextArg);
100104
} else if (arg.equals("--name") || arg.equals("-n")) {
101105
String nextArg = args.remove(0);
102106
Pattern patternFilter = Pattern.compile(nextArg);
@@ -202,4 +206,8 @@ public List<Object> getFilters() {
202206
public boolean isMonochrome() {
203207
return monochrome;
204208
}
209+
210+
public SnippetType getSnippetType() {
211+
return snippet;
212+
}
205213
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package cucumber.runtime;
2+
3+
public enum SnippetType {
4+
UNDERSCORE("underscore"),
5+
CAMELCASE("camelcase");
6+
7+
private String type;
8+
9+
SnippetType(String type) {
10+
this.type = type;
11+
}
12+
13+
public static SnippetType fromString(String type) {
14+
if (type == null) {
15+
throw new IllegalArgumentException();
16+
}
17+
for (SnippetType snippetType : SnippetType.values()) {
18+
if (type.equalsIgnoreCase(snippetType.type)) {
19+
return snippetType;
20+
}
21+
}
22+
throw new IllegalArgumentException();
23+
}
24+
25+
public static SnippetType getDefault() {
26+
return UNDERSCORE;
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package cucumber.runtime;
2+
3+
public interface SnippetTypeAwareBackend extends Backend {
4+
5+
public void setSnippetType(SnippetType type);
6+
7+
}

core/src/main/java/cucumber/runtime/UndefinedStepsTracker.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ public void reset() {
2222
* @return a list of code snippets that the developer can use to implement undefined steps.
2323
* This should be displayed after a run.
2424
*/
25-
public List<String> getSnippets(Iterable<? extends Backend> backends) {
25+
public List<String> getSnippets(Iterable<? extends Backend> backends, SnippetType type) {
2626
// TODO: Convert "And" and "But" to the Given/When/Then keyword above in the Gherkin source.
2727
List<String> snippets = new ArrayList<String>();
2828
for (Step step : undefinedSteps) {
2929
for (Backend backend : backends) {
30+
if(backend instanceof SnippetTypeAwareBackend) {
31+
((SnippetTypeAwareBackend) backend).setSnippetType(type);
32+
}
3033
String snippet = backend.getSnippet(step);
3134
if (snippet == null) {
3235
throw new NullPointerException("null snippet");
@@ -39,6 +42,15 @@ public List<String> getSnippets(Iterable<? extends Backend> backends) {
3942
return snippets;
4043
}
4144

45+
/**
46+
* @param backends what backends we want snippets for
47+
* @return a list of code snippets that the developer can use to implement undefined steps.
48+
* This should be displayed after a run.
49+
*/
50+
public List<String> getSnippets(Iterable<? extends Backend> backends) {
51+
return getSnippets(backends, SnippetType.getDefault());
52+
}
53+
4254
public void storeStepKeyword(Step step, I18n i18n) {
4355
String keyword = step.getKeyword();
4456
if (isGivenWhenThenKeyword(keyword, i18n)) {

core/src/main/java/cucumber/runtime/snippets/SnippetGenerator.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cucumber.runtime.snippets;
22

33
import cucumber.api.DataTable;
4+
import cucumber.runtime.SnippetType;
45
import gherkin.I18n;
56
import gherkin.formatter.model.Step;
67

@@ -10,7 +11,7 @@
1011
import java.util.regex.Matcher;
1112
import java.util.regex.Pattern;
1213

13-
public final class SnippetGenerator {
14+
public class SnippetGenerator {
1415
private static final ArgumentPattern[] DEFAULT_ARGUMENT_PATTERNS = new ArgumentPattern[]{
1516
new ArgumentPattern(Pattern.compile("\"([^\"]*)\""), String.class),
1617
new ArgumentPattern(Pattern.compile("(\\d+)"), Integer.TYPE)
@@ -73,7 +74,7 @@ private String functionName(String name) {
7374
return functionName;
7475
}
7576

76-
String sanitizeFunctionName(String functionName) {
77+
protected String sanitizeFunctionName(String functionName) {
7778
StringBuilder sanitized = new StringBuilder();
7879

7980
String trimmedFunctionName = functionName.trim();
@@ -154,4 +155,4 @@ public static String untypedArguments(List<Class<?>> argumentTypes) {
154155
}
155156
return sb.toString();
156157
}
157-
}
158+
}

core/src/main/resources/cucumber/runtime/USAGE.txt

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Options:
1111
-d, --[no-]-dry-run Skip execution of glue code.
1212
-m, --[no-]-monochrome Don't colour terminal output.
1313
-s, --[no-]-strict Treat undefined and pending steps as errors.
14+
--snippets Snippet type: underscore, camelcase
1415
--dotcucumber PATH_OR_URL Where to write out runtime information. PATH_OR_URL can be a file system
1516
path or a URL.
1617
-v, --version Print version.

core/src/test/java/cucumber/runtime/RuntimeOptionsTest.java

+16
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,20 @@ public void set_strict_on_strict_aware_formatters() throws Exception {
196196

197197
verify((StrictAware)strictAwareFormatter).setStrict(true);
198198
}
199+
200+
@Test
201+
public void default_snippet_type() {
202+
Properties properties = new Properties();
203+
RuntimeOptions runtimeOptions = new RuntimeOptions(properties);
204+
assertEquals(SnippetType.UNDERSCORE, runtimeOptions.getSnippetType());
205+
}
206+
207+
@Test
208+
public void set_snippet_type() {
209+
Properties properties = new Properties();
210+
properties.setProperty("cucumber.options", "--snippets camelcase");
211+
RuntimeOptions runtimeOptions = new RuntimeOptions(properties);
212+
assertEquals(SnippetType.CAMELCASE, runtimeOptions.getSnippetType());
213+
}
214+
199215
}

core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
import gherkin.I18n;
66
import gherkin.formatter.model.Step;
77
import org.junit.Test;
8+
import org.mockito.Mockito;
89

910
import java.util.List;
1011

1112
import static java.util.Arrays.asList;
1213
import static org.junit.Assert.assertEquals;
13-
import static org.junit.Assert.assertFalse;
1414
import static org.junit.Assert.assertTrue;
15+
import static org.junit.Assert.assertFalse;
1516

1617
public class UndefinedStepsTrackerTest {
1718

@@ -76,6 +77,19 @@ public void snippets_are_generated_for_correct_locale() throws Exception {
7677
assertEquals("[Если ^Б$]", tracker.getSnippets(asList(backend)).toString());
7778
}
7879

80+
@Test
81+
public void set_snippet_type_on_capable_backends() {
82+
Backend backend = Mockito.mock(SnippetTypeAwareBackend.class);
83+
Mockito.when(backend.getSnippet(Mockito.any(Step.class))).thenReturn("");
84+
85+
UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
86+
undefinedStepsTracker.addUndefinedStep(new Step(null, "Given ", "A", 1, null, null), ENGLISH);
87+
undefinedStepsTracker.getSnippets(asList(backend));
88+
Mockito.verify((SnippetTypeAwareBackend) backend, Mockito.atLeastOnce()).setSnippetType(SnippetType.getDefault());
89+
90+
}
91+
92+
7993
private class TestBackend implements Backend {
8094
@Override
8195
public void loadGlue(Glue glue, List<String> gluePaths) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cucumber.runtime.java;
2+
3+
import cucumber.api.DataTable;
4+
import cucumber.runtime.snippets.ArgumentPattern;
5+
import cucumber.runtime.snippets.Snippet;
6+
import cucumber.runtime.snippets.SnippetGenerator;
7+
import gherkin.I18n;
8+
import gherkin.formatter.model.Step;
9+
10+
import java.text.MessageFormat;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
15+
16+
public class CamelCaseSnippetGenerator extends SnippetGenerator {
17+
18+
public CamelCaseSnippetGenerator(Snippet snippet) {
19+
super(snippet);
20+
}
21+
22+
@Override
23+
protected String sanitizeFunctionName(String functionName) {
24+
25+
StringBuilder sanitized = new StringBuilder();
26+
27+
String trimmedFunctionName = functionName.trim();
28+
29+
if(!Character.isJavaIdentifierStart(trimmedFunctionName.charAt(0))) {
30+
sanitized.append("_");
31+
}
32+
33+
String[] words = trimmedFunctionName.split(" ");
34+
35+
sanitized.append(words[0].toLowerCase());
36+
37+
for(int i=1; i<words.length; i++) {
38+
sanitized.append(capitalize(words[i].toLowerCase()));
39+
}
40+
41+
return sanitized.toString();
42+
}
43+
44+
private String capitalize(String line)
45+
{
46+
return Character.toUpperCase(line.charAt(0)) + line.substring(1);
47+
}
48+
}

java/src/main/java/cucumber/runtime/java/JavaBackend.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
import java.util.List;
1313
import java.util.regex.Pattern;
1414

15-
public class JavaBackend implements Backend {
16-
private final SnippetGenerator snippetGenerator = new SnippetGenerator(new JavaSnippet());
15+
public class JavaBackend implements SnippetTypeAwareBackend {
16+
private SnippetGenerator snippetGenerator = new SnippetGenerator(new JavaSnippet());
1717
private final ObjectFactory objectFactory;
1818
private final Reflections reflections;
1919

2020
private final MethodScanner methodScanner;
2121
private Glue glue;
22+
private SnippetType snippetType = SnippetType.getDefault();
2223

2324
/**
2425
* The constructor called by reflection by default.
@@ -129,4 +130,18 @@ void addHook(Annotation annotation, Method method) {
129130
glue.addAfterHook(new JavaHookDefinition(method, tagExpressions, ((After) annotation).order(), timeout, objectFactory));
130131
}
131132
}
133+
134+
@Override
135+
public void setSnippetType(SnippetType type) {
136+
switch (type) {
137+
case CAMELCASE:
138+
snippetGenerator = new CamelCaseSnippetGenerator(new JavaSnippet());
139+
break;
140+
default:
141+
snippetGenerator = new SnippetGenerator(new JavaSnippet());
142+
break;
143+
144+
}
145+
this.snippetType = type;
146+
}
132147
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cucumber.runtime.java;
2+
3+
import org.junit.Test;
4+
5+
import static org.junit.Assert.assertEquals;
6+
7+
public class CamelCaseSnippetGeneratorTest {
8+
9+
@Test
10+
public void testSanitizeFunctionName() {
11+
12+
CamelCaseSnippetGenerator generator = new CamelCaseSnippetGenerator(new JavaSnippet());
13+
14+
String functionName = "I am a function name";
15+
String expected = "iAmAFunctionName";
16+
String actual = generator.sanitizeFunctionName(functionName);
17+
18+
assertEquals(expected, actual);
19+
}
20+
}

junit/src/main/java/cucumber/api/junit/Cucumber.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import cucumber.runtime.Runtime;
44
import cucumber.runtime.RuntimeOptions;
5+
import cucumber.runtime.SnippetType;
56
import cucumber.runtime.io.MultiLoader;
67
import cucumber.runtime.io.ResourceLoader;
78
import cucumber.runtime.junit.Assertions;
@@ -141,5 +142,10 @@ private void addChildren(List<CucumberFeature> cucumberFeatures) throws Initiali
141142
String[] name() default {};
142143

143144
String dotcucumber() default "";
145+
146+
/**
147+
* @return what format should the snippets use. underscore, camelcase
148+
*/
149+
String snippets() default "underscore";
144150
}
145-
}
151+
}

junit/src/main/java/cucumber/runtime/junit/RuntimeOptionsFactory.java

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public RuntimeOptions create() {
2828
addStrict(options, args);
2929
addName(options, args);
3030
addDotCucumber(options, args);
31+
addSnippets(options, args);
3132

3233
RuntimeOptions runtimeOptions = new RuntimeOptions(System.getProperties(), args.toArray(new String[args.size()]));
3334

@@ -54,6 +55,15 @@ private void addName(Cucumber.Options options, List<String> args) {
5455
}
5556
}
5657

58+
private void addSnippets(Cucumber.Options options, List<String> args) {
59+
if (options != null) {
60+
if (!options.snippets().isEmpty()) {
61+
args.add("--snippets");
62+
args.add(options.snippets());
63+
}
64+
}
65+
}
66+
5767
private Cucumber.Options getOptions(Class<?> clazz) {
5868
return clazz.getAnnotation(Cucumber.Options.class);
5969
}

junit/src/test/java/cucumber/runtime/junit/RuntimeOptionsFactoryTest.java

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import cucumber.api.junit.Cucumber;
44
import cucumber.runtime.RuntimeOptions;
5+
import cucumber.runtime.SnippetType;
56
import org.junit.Test;
67

78
import java.net.MalformedURLException;
@@ -72,6 +73,13 @@ public void create_with_dotcucumber_url() throws MalformedURLException {
7273
assertEquals(new URL("https://some.where/.cucumber/"), runtimeOptions.getDotCucumber());
7374
}
7475

76+
@Test
77+
public void create_with_snippets() {
78+
RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(Snippets.class);
79+
RuntimeOptions runtimeOptions = runtimeOptionsFactory.create();
80+
assertEquals(SnippetType.CAMELCASE, runtimeOptions.getSnippetType());
81+
}
82+
7583
private String getRegexpPattern(Object pattern) {
7684
return ((Pattern) pattern).pattern();
7785
}
@@ -86,6 +94,11 @@ public void finds_path_for_class_in_toplevel_package() {
8694
assertEquals("", packageName("TopLevelClass"));
8795
}
8896

97+
@Cucumber.Options(snippets = "camelcase")
98+
static class Snippets {
99+
// empty
100+
}
101+
89102
@Cucumber.Options(strict = true)
90103
static class Strict {
91104
// empty

0 commit comments

Comments
 (0)