diff --git a/Dockerfile b/Dockerfile index e53ca77d84a8..6264997fd610 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ COPY ./modules/openapi-generator-gradle-plugin ${GEN_DIR}/modules/openapi-genera COPY ./modules/openapi-generator-maven-plugin ${GEN_DIR}/modules/openapi-generator-maven-plugin COPY ./modules/openapi-generator-online ${GEN_DIR}/modules/openapi-generator-online COPY ./modules/openapi-generator-cli ${GEN_DIR}/modules/openapi-generator-cli +COPY ./modules/openapi-generator-core ${GEN_DIR}/modules/openapi-generator-core COPY ./modules/openapi-generator ${GEN_DIR}/modules/openapi-generator COPY ./pom.xml ${GEN_DIR} diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java index 8f1082039453..00f79f285e26 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java @@ -65,6 +65,10 @@ public class Generate implements Runnable { description = "folder containing the template files") private String templateDir; + @Option(name = {"-e", "--engine"}, title = "templating engine", + description = "templating engine, for now \"mustache\" and \"handlebars\" are supported") + private String templatingEngine; + @Option( name = {"-a", "--auth"}, title = "authorization", @@ -273,6 +277,10 @@ public void run() { configurator.setTemplateDir(templateDir); } + if (isNotEmpty(templatingEngine)) { + configurator.setTemplatingEngineName(templatingEngine); + } + if (isNotEmpty(apiPackage)) { configurator.setApiPackage(apiPackage); } diff --git a/modules/openapi-generator-core/pom.xml b/modules/openapi-generator-core/pom.xml new file mode 100644 index 000000000000..5e2b77ab81c6 --- /dev/null +++ b/modules/openapi-generator-core/pom.xml @@ -0,0 +1,16 @@ + + + + openapi-generator-project + org.openapitools + 4.0.0-SNAPSHOT + ../.. + + 4.0.0 + + openapi-generator-core + openapi-generator-core + https://github.com/openapitools/openapi-generator + \ No newline at end of file diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java new file mode 100644 index 000000000000..6340bd2b282c --- /dev/null +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingEngineAdapter.java @@ -0,0 +1,30 @@ +package org.openapitools.codegen.api; + +import java.io.IOException; +import java.util.Map; + +/** + * Each templating engine is called by an Adapter, selected at runtime + */ +public interface TemplatingEngineAdapter{ + + /** + * Compiles a template into a string + * + * @param generator From where we can fetch the templates content (e.g. an instance of DefaultGenerator) + * @param bundle The map of values to pass to the template + * @param templateFile The name of the template (e.g. model.mustache ) + * @return the processed template result + * @throws IOException an error ocurred in the template processing + */ + String compileTemplate(TemplatingGenerator generator, Map bundle, + String templateFile) throws IOException; + + /** + * During generation, if a supporting file has a file extension that is + * inside that array, then it is considered a templated supporting file + * and we use the templating engine adapter to generate it + * @return string array of the valid file extensions for this templating engine + */ + String[] getFileExtensions(); +} diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java new file mode 100644 index 000000000000..498067e51354 --- /dev/null +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java @@ -0,0 +1,17 @@ +package org.openapitools.codegen.api; + +/** + * interface to the full template content + * implementers might take into account the -t cli option, + * look in the resources for a language specific template, etc + */ +public interface TemplatingGenerator { + + /** + * returns the template content by name + * @param name the template name (e.g. model.mustache) + * @return the contents of that template + */ + String getFullTemplateContents(String name); + +} diff --git a/modules/openapi-generator/pom.xml b/modules/openapi-generator/pom.xml index 49d287cea7e4..7da9422289c1 100644 --- a/modules/openapi-generator/pom.xml +++ b/modules/openapi-generator/pom.xml @@ -215,6 +215,11 @@ jmustache ${jmustache-version} + + com.github.jknack + handlebars + ${handlebars.java-version} + commons-io commons-io @@ -300,6 +305,11 @@ jackson-datatype-threetenbp ${jackson-threetenbp-version} + + org.openapitools + openapi-generator-core + 4.0.0-SNAPSHOT + diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java index 1a0109b9f4d6..2f2f47138918 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java @@ -18,6 +18,7 @@ package org.openapitools.codegen; import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.api.TemplatingGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +27,7 @@ import java.util.Scanner; import java.util.regex.Pattern; -public abstract class AbstractGenerator { +public abstract class AbstractGenerator implements TemplatingGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractGenerator.class); @SuppressWarnings("static-method") diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java index 5d554cf97137..21e58a0ef67f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.openapitools.codegen.api.TemplatingEngineAdapter; public interface CodegenConfig { CodegenType getTag(); @@ -149,6 +150,8 @@ public interface CodegenConfig { Compiler processCompiler(Compiler compiler); + TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter templatingEngine); + String sanitizeTag(String tag); String toApiFilename(String name); @@ -260,4 +263,7 @@ public interface CodegenConfig { */ void setOpenAPI(OpenAPI openAPI); + void setTemplatingEngine(TemplatingEngineAdapter s); + + TemplatingEngineAdapter getTemplatingEngine(); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index c07e4c00728e..808d52355f46 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -181,6 +181,9 @@ public class CodegenConstants { public static final String DOTNET_FRAMEWORK = "targetFramework"; public static final String DOTNET_FRAMEWORK_DESC = "The target .NET framework version."; + public static final String TEMPLATING_ENGINE = "templatingEngine"; + public static final String TEMPLATING_ENGINE_DESC = "The templating engine plugin to use"; + public static enum MODEL_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original} public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original, UPPERCASE} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index e973ce6329e0..f8e57d5449ac 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -41,9 +41,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.openapitools.codegen.CodegenDiscriminator.MappedModel; +import org.openapitools.codegen.api.TemplatingEngineAdapter; import org.openapitools.codegen.config.GeneratorProperties; import org.openapitools.codegen.examples.ExampleGenerator; import org.openapitools.codegen.serializer.SerializerUtils; +import org.openapitools.codegen.templating.MustacheEngineAdapter; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,6 +113,7 @@ public class DefaultCodegen implements CodegenConfig { protected String ignoreFilePathOverride; // flag to indicate whether to use environment variable to post process file protected boolean enablePostProcessFile = false; + private TemplatingEngineAdapter templatingEngine = new MustacheEngineAdapter(); // make openapi available to all methods protected OpenAPI openAPI; @@ -460,6 +463,12 @@ public Compiler processCompiler(Compiler compiler) { return compiler; } + // override with any special handling for the templating engine + @SuppressWarnings("unused") + public TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter templatingEngine) { + return templatingEngine; + } + // override with any special text escaping logic @SuppressWarnings("static-method") public String escapeText(String input) { @@ -3804,6 +3813,16 @@ public String sanitizeName(String name) { return sanitizeName(name, "\\W"); } + @Override + public void setTemplatingEngine(TemplatingEngineAdapter templatingEngine) { + this.templatingEngine = templatingEngine; + } + + @Override + public TemplatingEngineAdapter getTemplatingEngine() { + return this.templatingEngine; + } + /** * Sanitize name (parameter, property, method, etc) * diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 41d8c6daa5b1..47bde2a5ef2b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -17,8 +17,6 @@ package org.openapitools.codegen; -import com.samskivert.mustache.Mustache; -import com.samskivert.mustache.Template; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; @@ -36,7 +34,10 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.config.GeneratorProperties; +import org.openapitools.codegen.api.TemplatingEngineAdapter; import org.openapitools.codegen.ignore.CodegenIgnoreProcessor; +import org.openapitools.codegen.templating.MustacheEngineAdapter; +import org.openapitools.codegen.config.GeneratorProperties; import org.openapitools.codegen.utils.ImplementationVersion; import org.openapitools.codegen.utils.ModelUtils; import org.openapitools.codegen.utils.URLPathUtils; @@ -54,6 +55,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { protected ClientOptInput opts; protected OpenAPI openAPI; protected CodegenIgnoreProcessor ignoreProcessor; + protected TemplatingEngineAdapter templatingEngine; private Boolean generateApis = null; private Boolean generateModels = null; private Boolean generateSupportingFiles = null; @@ -73,6 +75,7 @@ public Generator opts(ClientOptInput opts) { this.openAPI = opts.getOpenAPI(); this.config = opts.getConfig(); this.config.additionalProperties().putAll(opts.getOpts().getProperties()); + this.templatingEngine = this.config.getTemplatingEngine(); String ignoreFileLocation = this.config.getIgnoreFilePathOverride(); if (ignoreFileLocation != null) { @@ -91,6 +94,13 @@ public Generator opts(ClientOptInput opts) { return this; } + private void configPostProcessMustacheCompiler() { + if (this.templatingEngine instanceof MustacheEngineAdapter) { + MustacheEngineAdapter mustacheEngineAdapter = (MustacheEngineAdapter) this.templatingEngine; + mustacheEngineAdapter.setCompiler(this.config.processCompiler(mustacheEngineAdapter.getCompiler())); + } + } + /** * Programmatically disable the output of .openapi-generator/VERSION, .openapi-generator-ignore, * or other metadata files used by OpenAPI Generator. @@ -698,21 +708,9 @@ private void generateSupportingFiles(List files, Map bundl } if (ignoreProcessor.allowsFile(new File(outputFilename))) { - if (templateFile.endsWith("mustache")) { - String template = readTemplate(templateFile); - Mustache.Compiler compiler = Mustache.compiler(); - compiler = config.processCompiler(compiler); - Template tmpl = compiler - .withLoader(new Mustache.TemplateLoader() { - @Override - public Reader getTemplate(String name) { - return getTemplateReader(getFullTemplateFile(config, name + ".mustache")); - } - }) - .defaultValue("") - .compile(template); - - writeToFile(outputFilename, tmpl.execute(bundle)); + if (Arrays.stream(templatingEngine.getFileExtensions()).anyMatch(templateFile::endsWith)) { + String templateContent = templatingEngine.compileTemplate(this, bundle, support.templateFile); + writeToFile(outputFilename, templateContent); File written = new File(outputFilename); files.add(written); if (config.isEnablePostProcessFile()) { @@ -890,6 +888,9 @@ public List generate() { configureGeneratorProperties(); configureOpenAPIInfo(); + // If the template adapter is mustache, we'll set the config-modified Compiler. + configPostProcessMustacheCompiler(); + List files = new ArrayList(); // models List filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI); @@ -910,24 +911,16 @@ public List generate() { return files; } + @Override + public String getFullTemplateContents(String templateName) { + return readTemplate(getFullTemplateFile(config, templateName)); + } + protected File processTemplateToFile(Map templateData, String templateName, String outputFilename) throws IOException { String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar); if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) { - String templateFile = getFullTemplateFile(config, templateName); - String template = readTemplate(templateFile); - Mustache.Compiler compiler = Mustache.compiler(); - compiler = config.processCompiler(compiler); - Template tmpl = compiler - .withLoader(new Mustache.TemplateLoader() { - @Override - public Reader getTemplate(String name) { - return getTemplateReader(getFullTemplateFile(config, name + ".mustache")); - } - }) - .defaultValue("") - .compile(template); - - writeToFile(adjustedOutputFilename, tmpl.execute(templateData)); + String templateContent = templatingEngine.compileTemplate(this, templateData, templateName); + writeToFile(adjustedOutputFilename, templateContent); return new File(adjustedOutputFilename); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java index a1bdb827ea50..94055fa94ab2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; +import org.openapitools.codegen.api.TemplatingEngineAdapter; import io.swagger.parser.OpenAPIParser; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.OpenAPI; @@ -29,6 +30,8 @@ import org.openapitools.codegen.*; import org.openapitools.codegen.auth.AuthParser; import org.openapitools.codegen.languages.*; +import org.openapitools.codegen.templating.HandlebarsEngineAdapter; +import org.openapitools.codegen.templating.MustacheEngineAdapter; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +42,7 @@ import java.nio.file.Paths; import java.util.*; +import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotEmpty; /** @@ -77,6 +81,7 @@ public class CodegenConfigurator implements Serializable { private boolean validateSpec; private boolean enablePostProcessFile; private String templateDir; + private String templatingEngineName; private String auth; private String apiPackage; private String modelPackage; @@ -525,6 +530,7 @@ public ClientOptInput toClientOptInput() { checkAndSetAdditionalProperty(artifactId, CodegenConstants.ARTIFACT_ID); checkAndSetAdditionalProperty(artifactVersion, CodegenConstants.ARTIFACT_VERSION); checkAndSetAdditionalProperty(templateDir, toAbsolutePathStr(templateDir), CodegenConstants.TEMPLATE_DIR); + checkAndSetAdditionalProperty(templatingEngineName, CodegenConstants.TEMPLATING_ENGINE); checkAndSetAdditionalProperty(modelNamePrefix, CodegenConstants.MODEL_NAME_PREFIX); checkAndSetAdditionalProperty(modelNameSuffix, CodegenConstants.MODEL_NAME_SUFFIX); checkAndSetAdditionalProperty(gitUserId, CodegenConstants.GIT_USER_ID); @@ -538,6 +544,13 @@ public ClientOptInput toClientOptInput() { config.setLibrary(library); } + // Built-in templates are mustache, but allow users to use a simplified handlebars engine for their custom templates. + if (isEmpty(templatingEngineName) || templatingEngineName.equals("mustache")) { + config.setTemplatingEngine(new MustacheEngineAdapter()); + } else if (templatingEngineName.equals("handlebars")) { + config.setTemplatingEngine(new HandlebarsEngineAdapter()); + } + config.additionalProperties().putAll(additionalProperties); ClientOptInput input = new ClientOptInput() @@ -669,4 +682,8 @@ public static CodegenConfigurator fromFile(String configFile) { return null; } + public CodegenConfigurator setTemplatingEngineName(String templatingEngineName) { + this.templatingEngineName = templatingEngineName; + return this; + } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/HandlebarsEngineAdapter.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/HandlebarsEngineAdapter.java new file mode 100644 index 000000000000..38c1bcd26dc1 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/HandlebarsEngineAdapter.java @@ -0,0 +1,51 @@ +package org.openapitools.codegen.templating; + +import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.Template; +import com.github.jknack.handlebars.io.AbstractTemplateLoader; +import com.github.jknack.handlebars.io.StringTemplateSource; +import com.github.jknack.handlebars.io.TemplateLoader; +import com.github.jknack.handlebars.io.TemplateSource; +import org.openapitools.codegen.api.TemplatingEngineAdapter; +import org.openapitools.codegen.api.TemplatingGenerator; + +import java.io.IOException; +import java.util.Map; + + +public class HandlebarsEngineAdapter implements TemplatingEngineAdapter { + + public String[] extensions = new String[]{"handlebars", "hbs"}; + + public String compileTemplate(TemplatingGenerator generator, + Map bundle, String templateFile) throws IOException { + TemplateLoader loader = new AbstractTemplateLoader() { + @Override + public TemplateSource sourceAt(String location) { + return findTemplate(generator, location); + } + }; + + Handlebars handlebars = new Handlebars(loader); + handlebars.registerHelperMissing((context, options) -> ""); + Template tmpl = handlebars.compile(templateFile); + return tmpl.apply(bundle); + } + + public TemplateSource findTemplate(TemplatingGenerator generator, String name) { + for (String extension : extensions) { + try { + String location = name + "." + extension; + return new StringTemplateSource(location, generator.getFullTemplateContents(location)); + } catch (Exception ignored) { + } + } + throw new RuntimeException("couldnt find a subtemplate " + name); + } + + @Override + public String[] getFileExtensions() { + return extensions; + } +} + diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/MustacheEngineAdapter.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/MustacheEngineAdapter.java new file mode 100644 index 000000000000..37e85d1ecdeb --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/MustacheEngineAdapter.java @@ -0,0 +1,52 @@ +package org.openapitools.codegen.templating; + +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; +import org.openapitools.codegen.api.TemplatingEngineAdapter; +import org.openapitools.codegen.api.TemplatingGenerator; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Map; + + +public class MustacheEngineAdapter implements TemplatingEngineAdapter { + + public String[] extensions = new String[]{"mustache"}; + Mustache.Compiler compiler = Mustache.compiler(); + + @Override + public String compileTemplate(TemplatingGenerator generator, Map bundle, + String templateFile) throws IOException { + Template tmpl = compiler + .withLoader(name -> findTemplate(generator, name)) + .defaultValue("") + .compile(generator.getFullTemplateContents(templateFile)); + + return tmpl.execute(bundle); + } + + public Reader findTemplate(TemplatingGenerator generator, String name) { + for (String extension : extensions) { + try { + return new StringReader(generator.getFullTemplateContents(name + "." + extension)); + } catch (Exception ignored) { + } + } + throw new RuntimeException("couldnt find a subtemplate " + name); + } + + public Mustache.Compiler getCompiler() { + return compiler; + } + + public void setCompiler(Mustache.Compiler compiler) { + this.compiler = compiler; + } + + @Override + public String[] getFileExtensions() { + return extensions; + } +} diff --git a/modules/openapi-generator/src/main/resources/typescript-axios/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/typescript-axios/git_push.sh.mustache old mode 100644 new mode 100755 diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJerseyServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJerseyServerCodegenTest.java index 6844b943d691..b0d459103c09 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJerseyServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/jaxrs/JavaJerseyServerCodegenTest.java @@ -5,6 +5,7 @@ import org.openapitools.codegen.*; import org.openapitools.codegen.MockDefaultGenerator.WrittenTemplateBasedFile; import org.openapitools.codegen.languages.JavaJerseyServerCodegen; +import org.openapitools.codegen.templating.MustacheEngineAdapter; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/pom.xml b/pom.xml index a0ba2f2f8b0d..f503ad60952b 100644 --- a/pom.xml +++ b/pom.xml @@ -1298,6 +1298,7 @@ modules/openapi-generator-maven-plugin modules/openapi-generator-gradle-plugin modules/openapi-generator-online + modules/openapi-generator-core target/site @@ -1377,6 +1378,7 @@ 1.7.12 3.2.1 1.14 + 4.1.0 6.9.6 2.22.0 1.43