Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Remove cucumber.options property #1958

Merged
merged 6 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion core/src/main/java/io/cucumber/core/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import io.cucumber.core.runtime.Runtime;
import org.apiguardian.api.API;

import java.util.Optional;

/**
* Cucumber Main. Runs Cucumber as a CLI.
* <p>
Expand Down Expand Up @@ -52,14 +54,20 @@ public static byte run(String[] argv, ClassLoader classLoader) {
.parse(CucumberProperties.fromSystemProperties())
.build(environmentOptions);

RuntimeOptions runtimeOptions = new CommandlineOptionsParser()
CommandlineOptionsParser commandlineOptionsParser = new CommandlineOptionsParser(System.out);
RuntimeOptions runtimeOptions = commandlineOptionsParser
.parse(argv)
.addDefaultGlueIfAbsent()
.addDefaultFeaturePathIfAbsent()
.addDefaultFormatterIfAbsent()
.addDefaultSummaryPrinterIfAbsent()
.build(systemOptions);

Optional<Byte> exitStatus = commandlineOptionsParser.exitStatus();
if (exitStatus.isPresent()) {
return exitStatus.get();
}

if (!runtimeOptions.isStrict()) {
log.warn(() -> "By default Cucumber is running in --non-strict mode.\n" +
"This default will change to --strict and --non-strict will be removed.\n" +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,249 @@
package io.cucumber.core.options;

import io.cucumber.core.exception.CucumberException;
import io.cucumber.core.feature.FeatureWithLines;
import io.cucumber.core.feature.GluePath;
import io.cucumber.datatable.DataTable;
import io.cucumber.gherkin.GherkinDialect;
import io.cucumber.gherkin.GherkinDialectProvider;
import io.cucumber.gherkin.IGherkinDialectProvider;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static io.cucumber.core.options.ObjectFactoryParser.parseObjectFactory;
import static io.cucumber.core.options.OptionsFileParser.parseFeatureWithLinesFile;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;

public final class CommandlineOptionsParser {

public RuntimeOptionsBuilder parse(List<String> args) {
RuntimeOptionsParser parser = new RuntimeOptionsParser();
return parser.parse(args);
private static final String VERSION = ResourceBundle.getBundle("io.cucumber.core.version").getString("cucumber-jvm.version");
// IMPORTANT! Make sure USAGE.txt is always uptodate if this class changes.
private static final String USAGE_RESOURCE = "/io/cucumber/core/options/USAGE.txt";

private final PrintWriter out;
private Byte exitCode = null;

public CommandlineOptionsParser(OutputStream outputStream) {
out = new PrintWriter(outputStream, true);
}

public Optional<Byte> exitStatus() {
return Optional.ofNullable(exitCode);
}

public RuntimeOptionsBuilder parse(String... args) {
return parse(Arrays.asList(args));
}

private RuntimeOptionsBuilder parse(List<String> args) {
args = new ArrayList<>(args);
RuntimeOptionsBuilder parsedOptions = new RuntimeOptionsBuilder();

while (!args.isEmpty()) {
String arg = args.remove(0).trim();

if (arg.equals("--help") || arg.equals("-h")) {
printUsage();
exitCode = 0;
return parsedOptions;
} else if (arg.equals("--version") || arg.equals("-v")) {
out.println(VERSION);
exitCode = 0;
return parsedOptions;
} else if (arg.equals("--i18n")) {
String nextArg = removeArgFor(arg, args);
exitCode = printI18n(nextArg);
return parsedOptions;
} else if (arg.equals("--threads")) {
int threads = Integer.parseInt(removeArgFor(arg, args));
if (threads < 1) {
out.println("--threads must be > 0");
exitCode = 1;
return parsedOptions;
}
parsedOptions.setThreads(threads);
} else if (arg.equals("--glue") || arg.equals("-g")) {
String gluePath = removeArgFor(arg, args);
URI parse = GluePath.parse(gluePath);
parsedOptions.addGlue(parse);
} else if (arg.equals("--tags") || arg.equals("-t")) {
parsedOptions.addTagFilter(removeArgFor(arg, args));
} else if (arg.equals("--plugin") || arg.equals("-p")) {
parsedOptions.addPluginName(removeArgFor(arg, args));
} else if (arg.equals("--no-dry-run") || arg.equals("--dry-run") || arg.equals("-d")) {
parsedOptions.setDryRun(!arg.startsWith("--no-"));
} else if (arg.equals("--no-strict") || arg.equals("--strict") || arg.equals("-s")) {
parsedOptions.setStrict(!arg.startsWith("--no-"));
} else if (arg.equals("--no-monochrome") || arg.equals("--monochrome") || arg.equals("-m")) {
parsedOptions.setMonochrome(!arg.startsWith("--no-"));
} else if (arg.equals("--snippets")) {
String nextArg = removeArgFor(arg, args);
parsedOptions.setSnippetType(SnippetTypeParser.parseSnippetType(nextArg));
} else if (arg.equals("--name") || arg.equals("-n")) {
String nextArg = removeArgFor(arg, args);
Pattern pattern = Pattern.compile(nextArg);
parsedOptions.addNameFilter(pattern);
} else if (arg.equals("--wip") || arg.equals("-w")) {
parsedOptions.setWip(true);
} else if (arg.equals("--order")) {
parsedOptions.setPickleOrder(PickleOrderParser.parse(removeArgFor(arg, args)));
} else if (arg.equals("--count")) {
int count = Integer.parseInt(removeArgFor(arg, args));
if (count < 1) {
out.println("--count must be > 0");
exitCode = 1;
return parsedOptions;
}
parsedOptions.setCount(count);
} else if (arg.equals("--object-factory")) {
String objectFactoryClassName = removeArgFor(arg, args);
parsedOptions.setObjectFactoryClass(parseObjectFactory(objectFactoryClassName));
} else if (arg.startsWith("-")) {
out.println("Unknown option: " + arg);
printUsage();
exitCode = 1;
return parsedOptions;
} else if (!arg.isEmpty()) {
if (arg.startsWith("@")) {
Path rerunFile = Paths.get(arg.substring(1));
parsedOptions.addRerun(parseFeatureWithLinesFile(rerunFile));
} else {
FeatureWithLines featureWithLines = FeatureWithLines.parse(arg);
parsedOptions.addFeature(featureWithLines);
}
}
}
return parsedOptions;
}

private String removeArgFor(String arg, List<String> args) {
if (!args.isEmpty()) {
return args.remove(0);
}
printUsage();
throw new CucumberException("Missing argument for " + arg);
}

private void printUsage() {
out.println(loadUsageText());
}

private String loadUsageText() {
try (
InputStream usageResourceStream = CommandlineOptionsParser.class.getResourceAsStream(USAGE_RESOURCE);
BufferedReader br = new BufferedReader(new InputStreamReader(usageResourceStream, UTF_8))
) {
return br.lines().collect(joining(System.lineSeparator()));
} catch (Exception e) {
return "Could not load usage text: " + e.toString();
}
}

private byte printI18n(String language) {
IGherkinDialectProvider dialectProvider = new GherkinDialectProvider();
List<String> languages = dialectProvider.getLanguages();

if (language.equalsIgnoreCase("help")) {
if (language.equalsIgnoreCase("help")) {
List<GherkinDialect> dialects = new ArrayList<>();
for (String code : languages) {
GherkinDialect dialect = dialectProvider.getDialect(code, null);
dialects.add(dialect);
}

int widestLanguage = findWidest(dialects, GherkinDialect::getLanguage);
int widestName = findWidest(dialects, GherkinDialect::getName);
int widestNativeName = findWidest(dialects, GherkinDialect::getNativeName);

for (GherkinDialect dialect : dialects) {
printDialect(dialect, widestLanguage, widestName, widestNativeName);
}
return 0x0;
}
}
if (languages.contains(language)) {
return printKeywordsFor(dialectProvider.getDialect(language, null));
}

out.println("Unrecognised ISO language code");
return 0x1;
}

private int findWidest(List<GherkinDialect> dialects, Function<GherkinDialect, String> getNativeName) {
return dialects.stream()
.map(getNativeName)
.mapToInt(String::length)
.max()
.orElse(0);
}

private byte printKeywordsFor(GherkinDialect dialect) {
StringBuilder builder = new StringBuilder();
List<List<String>> table = new ArrayList<>();
addKeywordRow(table, "feature", dialect.getFeatureKeywords());
addKeywordRow(table, "background", dialect.getBackgroundKeywords());
addKeywordRow(table, "scenario", dialect.getScenarioKeywords());
addKeywordRow(table, "scenario outline", dialect.getScenarioOutlineKeywords());
addKeywordRow(table, "examples", dialect.getExamplesKeywords());
addKeywordRow(table, "given", dialect.getGivenKeywords());
addKeywordRow(table, "when", dialect.getWhenKeywords());
addKeywordRow(table, "then", dialect.getThenKeywords());
addKeywordRow(table, "and", dialect.getAndKeywords());
addKeywordRow(table, "but", dialect.getButKeywords());
addCodeKeywordRow(table, "given", dialect.getGivenKeywords());
addCodeKeywordRow(table, "when", dialect.getWhenKeywords());
addCodeKeywordRow(table, "then", dialect.getThenKeywords());
addCodeKeywordRow(table, "and", dialect.getAndKeywords());
addCodeKeywordRow(table, "but", dialect.getButKeywords());
DataTable.create(table).print(builder);
out.println(builder.toString());
return 0x0;
}

private void addCodeKeywordRow(List<List<String>> table, String key, List<String> keywords) {
List<String> codeKeywordList = new ArrayList<>(keywords);
codeKeywordList.remove("* ");

List<String> codeWords = codeKeywordList.stream()
.map(keyword -> keyword.replaceAll("[\\s',!]", ""))
.collect(Collectors.toList());

addKeywordRow(table, key + " (code)", codeWords);
}

private void addKeywordRow(List<List<String>> table, String key, List<String> keywords) {
table.add(asList(key, keywords.stream().map(o -> '"' + o + '"').collect(joining(", "))));
}

private void printDialect(GherkinDialect dialect, int widestLanguage, int widestName, int widestNativeName) {
String langCode = rightPad(dialect.getLanguage(), widestLanguage);
String name = rightPad(dialect.getName(), widestName);
String nativeName = rightPad(dialect.getNativeName(), widestNativeName);

out.println(langCode + name + nativeName);
}

private String rightPad(String text, int maxWidth) {
int padding = 7;
int width = maxWidth + padding;

return String.format("%" + -width + "s", text);
}
}
9 changes: 0 additions & 9 deletions core/src/main/java/io/cucumber/core/options/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,6 @@ public final class Constants {
*/
public static final String OBJECT_FACTORY_PROPERTY_NAME = "cucumber.object-factory";

/**
* Property name used to pass command line options: {@value}
* <p>
* When available it is recommended to use a property based alternative.
*
* @see RuntimeOptionsParser
*/
public static final String OPTIONS_PROPERTY_NAME = "cucumber.options";

/**
* Property name to enable plugins: {@value}
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private void addTags(CucumberOptions options, RuntimeOptionsBuilder args) {

private void addPlugins(CucumberOptions options, RuntimeOptionsBuilder args) {
for (String plugin : options.plugin()) {
args.addPluginName(plugin, false);
args.addPluginName(plugin);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
import io.cucumber.core.exception.CucumberException;
import io.cucumber.core.feature.FeatureWithLines;
import io.cucumber.core.feature.GluePath;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
Expand All @@ -28,7 +24,6 @@
import static io.cucumber.core.options.Constants.FILTER_TAGS_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.GLUE_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.OBJECT_FACTORY_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.OPTIONS_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.PLUGIN_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.SNIPPET_TYPE_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.WIP_PROPERTY_NAME;
Expand All @@ -38,24 +33,6 @@

public final class CucumberPropertiesParser {

private static final Logger log = LoggerFactory.getLogger(CucumberPropertiesParser.class);
private static final List<String> COMMANDLINE_OPTION_ALTERNATIVES = Arrays.asList(
ANSI_COLORS_DISABLED_PROPERTY_NAME,
EXECUTION_DRY_RUN_PROPERTY_NAME,
EXECUTION_LIMIT_PROPERTY_NAME,
EXECUTION_ORDER_PROPERTY_NAME,
EXECUTION_STRICT_PROPERTY_NAME,
WIP_PROPERTY_NAME,
FEATURES_PROPERTY_NAME,
FILTER_NAME_PROPERTY_NAME,
FILTER_TAGS_PROPERTY_NAME,
GLUE_PROPERTY_NAME,
OBJECT_FACTORY_PROPERTY_NAME,
PLUGIN_PROPERTY_NAME,
SNIPPET_TYPE_PROPERTY_NAME
);


private static <T> Function<String, Collection<T>> splitAndMap(Function<String, T> parse) {
return combined -> stream(combined.split(","))
.map(String::trim)
Expand All @@ -73,17 +50,7 @@ private static <T> Function<String, Collection<T>> splitAndThenFlatMap(Function<
}

public RuntimeOptionsBuilder parse(Map<String, String> properties) {
final RuntimeOptionsBuilder builder;
String cucumberOptions = properties.get(OPTIONS_PROPERTY_NAME);
if (cucumberOptions != null) {
builder = parseCucumberOptions(cucumberOptions);
log.warn(() ->
"Passing commandline options via the property `" + OPTIONS_PROPERTY_NAME + "` has been deprecated" +
"in favour of explicitly using property names.\nPlease use these instead: " + COMMANDLINE_OPTION_ALTERNATIVES
);
} else {
builder = new RuntimeOptionsBuilder();
}
RuntimeOptionsBuilder builder = new RuntimeOptionsBuilder();

parse(properties,
ANSI_COLORS_DISABLED_PROPERTY_NAME,
Expand Down Expand Up @@ -153,7 +120,7 @@ public RuntimeOptionsBuilder parse(Map<String, String> properties) {
parseAll(properties,
PLUGIN_PROPERTY_NAME,
splitAndMap(Function.identity()),
plugin -> builder.addPluginName(plugin, true)
builder::addPluginName
);

parse(properties,
Expand Down Expand Up @@ -202,12 +169,4 @@ private <T> void parse(Map<String, String> properties, String propertyName, Func
parseAll(properties, propertyName, parser.andThen(Collections::singletonList), setter);
}

private RuntimeOptionsBuilder parseCucumberOptions(String cucumberOptions) {
RuntimeOptionsBuilder builder;
RuntimeOptionsParser parser = new RuntimeOptionsParser();
List<String> args = ShellWords.parse(cucumberOptions);
builder = parser.parse(args);
return builder;
}

}
Loading