Skip to content

Commit

Permalink
Replace groovy script with FreeMarker template engine (#2669)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlvandijk authored Jan 27, 2023
1 parent 5567a43 commit 477acb4
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 115 deletions.
2 changes: 2 additions & 0 deletions cucumber-bom/pom.xml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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>
<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
Original file line number Diff line number Diff line change
@@ -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.

Loading

0 comments on commit 477acb4

Please sign in to comment.