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

Replace groovy script with FreeMarker template engine #2669

Merged
merged 17 commits into from Jan 27, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions cucumber-bom/pom.xml
Expand Up @@ -14,8 +14,10 @@
<properties>
<ci-environment.version>9.1.0</ci-environment.version>
<cucumber-expressions.version>16.1.2</cucumber-expressions.version>
<gherkin.version>26.0.3</gherkin.version>
<html-formatter.version>20.2.1</html-formatter.version>
<junit-xml-formatter.version>0.1.0</junit-xml-formatter.version>
<messages.version>21.0.1</messages.version>
<tag-expressions.version>5.0.1</tag-expressions.version>
</properties>

Expand Down
79 changes: 64 additions & 15 deletions cucumber-java/pom.xml
Expand Up @@ -89,47 +89,96 @@
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Copy FreeMarker template files for code generation-->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>gherkin</artifactId>
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
<version>${gherkin.version}</version>
</dependency>
</dependencies>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>generate-i18n-sources</id>
<id>generate-i18n</id>
<phase>generate-sources</phase>
<goals>
<goal>execute</goal>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/codegen-classes</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/codegen/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Make sure to compile code generator before running it -->
<!-- Note: both the code generator and regular classes are compiled -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<executions>
<execution>
<id>generate-i18n</id>
<phase>generate-sources</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<source>${basedir}/src/main/groovy/generate-annotations.groovy</source>
<classpathScope>compile</classpathScope>
<compileSourceRoots>${project.basedir}/src/codegen/java</compileSourceRoots>
<outputDirectory>${project.build.directory}/codegen-classes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- Run the code generator -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>generate-i18n</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<classpathScope>test</classpathScope>
<addOutputToClasspath>false</addOutputToClasspath>
<addResourcesToClasspath>false</addResourcesToClasspath>
<additionalClasspathElements>${project.build.directory}/codegen-classes</additionalClasspathElements>
<mainClass>GenerateI18n</mainClass>
<arguments>
<argument>${project.build.directory}/generated-sources/i18n</argument>
<argument>io/cucumber/java</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<id>generate-i18n</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${basedir}/target/generated-sources/i18n/java</source>
<source>${project.build.directory}/generated-sources/i18n</source>
</sources>
</configuration>
</execution>
Expand Down
142 changes: 142 additions & 0 deletions cucumber-java/src/codegen/java/GenerateI18n.java
@@ -0,0 +1,142 @@
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import io.cucumber.gherkin.GherkinDialect;
import io.cucumber.gherkin.GherkinDialectProvider;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import static java.nio.file.Files.newBufferedWriter;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;

/* This class generates the cucumber-java Interfaces and package-info
* based on the languages and keywords from the GherkinDialectProvider
* using the FreeMarker template engine and provided templates.
*/
public class GenerateI18n {

// The generated files for and Emoij do not compile :(
private static final List<String> unsupported = Arrays.asList("em", "en-tx");

public static void main(String[] args) throws Exception {
if (args.length != 2) {
throw new IllegalArgumentException("Usage: <baseDirectory> <packagePath>");
}

DialectWriter dialectWriter = new DialectWriter(args[0], args[1]);

// Generate annotation files for each dialect
GherkinDialectProvider dialectProvider = new GherkinDialectProvider();
dialectProvider.getLanguages()
.stream()
.map(dialectProvider::getDialect)
.filter(Optional::isPresent)
.map(Optional::get)
.filter(dialect -> !unsupported.contains(dialect.getLanguage()))
.forEach(dialectWriter::writeDialect);
}

static class DialectWriter {
private final Template templateSource;
private final Template packageInfoSource;
private final String baseDirectory;
private final String packagePath;

DialectWriter(String baseDirectory, String packagePath) throws IOException {
this.baseDirectory = baseDirectory;
this.packagePath = packagePath;

Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
cfg.setClassForTemplateLoading(GenerateI18n.class, "templates");
cfg.setDefaultEncoding("UTF-8");
cfg.setLocale(Locale.US);
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

templateSource = cfg.getTemplate("annotation.java.ftl");
packageInfoSource = cfg.getTemplate("package-info.ftl");
}

void writeDialect(GherkinDialect dialect) {
writeKeyWordAnnotations(dialect);
writePackageInfo(dialect);
}

private void writeKeyWordAnnotations(GherkinDialect dialect) {
dialect.getStepKeywords().stream()
.filter(it -> !it.contains(String.valueOf('*')))
.filter(it -> !it.matches("^\\d.*"))
.distinct()
.forEach(keyword -> writeKeyWordAnnotation(dialect, keyword));
}

private void writeKeyWordAnnotation(GherkinDialect dialect, String keyword) {
String normalizedLanguage = getNormalizedLanguage(dialect);
String normalizedKeyword = getNormalizedKeyWord(keyword);

Map<String, String> binding = new LinkedHashMap<>();
binding.put("lang", normalizedLanguage);
binding.put("kw", normalizedKeyword);

Path path = Paths.get(baseDirectory, packagePath, normalizedLanguage, normalizedKeyword + ".java");

if (Files.exists(path)) {
// Haitian has two translations that only differ by case - Sipozeke and SipozeKe
// Some file systems are unable to distinguish between them and
// overwrite the other one :-(
return;
}

try {
Files.createDirectories(path.getParent());
templateSource.process(binding, newBufferedWriter(path, CREATE, TRUNCATE_EXISTING));
} catch (IOException | TemplateException e) {
throw new RuntimeException(e);
}
}

private static String getNormalizedKeyWord(String keyword) {
return normalize(keyword.replaceAll("[\\s',!\u00AD]", ""));
}

private static String normalize(CharSequence s) {
return Normalizer.normalize(s, Normalizer.Form.NFC);
}

private void writePackageInfo(GherkinDialect dialect) {
String normalizedLanguage = getNormalizedLanguage(dialect);
String languageName = dialect.getName();
if (!dialect.getName().equals(dialect.getNativeName())) {
languageName += " - " + dialect.getNativeName();
}

Map<String, String> binding = new LinkedHashMap<>();
binding.put("normalized_language", normalizedLanguage);
binding.put("language_name", languageName);

Path path = Paths.get(baseDirectory, packagePath, normalizedLanguage, "package-info.java");

try {
Files.createDirectories(path.getParent());
packageInfoSource.process(binding, newBufferedWriter(path, CREATE, TRUNCATE_EXISTING));
} catch (IOException | TemplateException e) {
throw new RuntimeException(e);
}
}

private static String getNormalizedLanguage(GherkinDialect dialect) {
return dialect.getLanguage().replaceAll("[\\s-]", "_").toLowerCase();
}
}
}
42 changes: 0 additions & 42 deletions cucumber-java/src/main/groovy/generate-annotations.groovy

This file was deleted.