Feature: Experimental Handlebars support (rienafairefr) (#2657)

* new module: openapi-generator-core
* templating engine adapters to support extension (currently only Handlebars)
* new `-e` templating engine CLI option
* adapt Generator to process Template with the passed TemplatingEngineAdpater
* add a MustacheEngineAdapter to the codegen in the unit tests
* force default MustacheEngineAdapter
* copy new core module in the root Dockerfile
* add processTemplatingEngine to CodegenConfig, to be overriden by Codegen classes if needed
* support multiple file extensions per templating engine adapter
* Extends handlebars experimental adapter with explicit contextual resolvers (e.g. map processing)
* Add new openapi-generator-core/pom.xml to release_version_update.sh
* A detailed message will be logged on missing handlebars helper
* Adds README documentation around template default and beta options
* Moves mustache package under new templating package
* Include built-in handlebars helpers which require explicit registration, and custom `startsWith` helper.
This commit is contained in:
Jim Schubert 2019-04-25 21:52:50 -04:00 committed by GitHub
parent 61ed2eecef
commit 8bbeb8b7e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 517 additions and 84 deletions

View File

@ -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}

View File

@ -426,9 +426,12 @@ SYNOPSIS
[--artifact-version <artifact version>]
[(-c <configuration file> | --config <configuration file>)]
[-D <system properties>...]
[(-e <templating engine> | --engine <templating engine>)]
[--enable-post-process-file]
[(-g <generator name> | --generator-name <generator name>)]
[--git-repo-id <git repo id>] [--git-user-id <git user id>]
[--group-id <group id>] [--http-user-agent <http user agent>]
[--generate-alias-as-model] [--git-repo-id <git repo id>]
[--git-user-id <git user id>] [--group-id <group id>]
[--http-user-agent <http user agent>]
(-i <spec file> | --input-spec <spec file>)
[--ignore-file-override <ignore file override location>]
[--import-mappings <import mappings>...]
@ -584,6 +587,9 @@ OpenAPI Generator core team members are contributors who have been making signif
:heart: = Link to support the contributor directly
#### Template Creator
**NOTE**: Embedded templates are only supported in _Mustache_ format. Support for all other formats is experimental and subject to change at any time.
Here is a list of template creators:
* API Clients:
* Ada: @stcarrez

View File

@ -36,6 +36,7 @@ echo "Release preparation: replacing $FROM with $TO in different files"
declare -a files=("modules/openapi-generator-cli/pom.xml"
"modules/openapi-generator-gradle-plugin/gradle.properties"
"modules/openapi-generator-gradle-plugin/pom.xml"
"modules/openapi-generator-core/pom.xml"
"modules/openapi-generator-maven-plugin/pom.xml"
"modules/openapi-generator-online/pom.xml"
"modules/openapi-generator/pom.xml"

View File

@ -70,6 +70,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: \"mustache\" (default) or \"handlebars\" (beta)")
private String templatingEngine;
@Option(
name = {"-a", "--auth"},
title = "authorization",
@ -284,6 +288,10 @@ public class Generate implements Runnable {
configurator.setTemplateDir(templateDir);
}
if (isNotEmpty(templatingEngine)) {
configurator.setTemplatingEngineName(templatingEngine);
}
if (isNotEmpty(apiPackage)) {
configurator.setApiPackage(apiPackage);
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>openapi-generator-project</artifactId>
<groupId>org.openapitools</groupId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openapi-generator-core</artifactId>
<name>openapi-generator-core</name>
<url>https://github.com/openapitools/openapi-generator</url>
</project>

View File

@ -0,0 +1,40 @@
package org.openapitools.codegen.api;
import java.util.Locale;
/**
* Provides abstractions around the template engine adapter interface, for reuse by implementers.
*/
public abstract class AbstractTemplatingEngineAdapter implements TemplatingEngineAdapter {
/**
* Gets all possible template paths for a given location.
*
* @param location The full location of the template.
*
* @return A new array of locations, modified according to the extensions or other adapter rules.
*/
protected String[] getModifiedFileLocation(String location) {
String[] extensions = getFileExtensions();
String[] result = new String[extensions.length];
for (int i = 0; i < extensions.length; i++) {
String extension = extensions[i];
result[i] = String.format(Locale.ROOT, "%s.%s", getPathWithoutExtension(location), extension);
}
return result;
}
/**
* Returns the path without an extension for an input location.
*
* @param location The location of the file, with original file extension intact.
*
* @return The full path, without extension (e.g. /path/to/file.txt => /path/to/file)
*/
private String getPathWithoutExtension(String location) {
if (location == null) return null;
int idx = location.lastIndexOf('.');
if (idx == -1) return location;
return location.substring(0, idx);
}
}

View File

@ -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<String, Object> 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();
}

View File

@ -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);
}

View File

@ -215,6 +215,16 @@
<artifactId>jmustache</artifactId>
<version>${jmustache-version}</version>
</dependency>
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>${handlebars.java-version}</version>
</dependency>
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-jackson2</artifactId>
<version>${handlebars.java-version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
@ -300,6 +310,11 @@
<artifactId>jackson-datatype-threetenbp</artifactId>
<version>${jackson-threetenbp-version}</version>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-core</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<repositories>
<repository>

View File

@ -22,6 +22,7 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.api.TemplatingGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -30,7 +31,7 @@ import java.nio.file.Paths;
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);
/**

View File

@ -24,6 +24,7 @@ import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariable;
import org.openapitools.codegen.api.TemplatingEngineAdapter;
import java.io.File;
import java.util.List;
@ -149,6 +150,8 @@ public interface CodegenConfig {
Compiler processCompiler(Compiler compiler);
TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter templatingEngine);
String sanitizeTag(String tag);
String toApiFilename(String name);
@ -260,8 +263,11 @@ public interface CodegenConfig {
*/
void setOpenAPI(OpenAPI openAPI);
void setTemplatingEngine(TemplatingEngineAdapter s);
TemplatingEngineAdapter getTemplatingEngine();
public boolean isEnableMinimalUpdate();
public void setEnableMinimalUpdate(boolean isEnableMinimalUpdate);
}

View File

@ -184,6 +184,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: \"mustache\" (default) or \"handlebars\" (beta)";
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}

View File

@ -41,9 +41,11 @@ import org.apache.commons.lang3.StringEscapeUtils;
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,8 @@ 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();
// flag to indicate whether to only update files whose contents have changed
protected boolean enableMinimalUpdate = false;
@ -462,6 +466,12 @@ public class DefaultCodegen implements CodegenConfig {
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) {
@ -3828,6 +3838,16 @@ public class DefaultCodegen implements CodegenConfig {
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)
*

View File

@ -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;
@ -35,7 +33,10 @@ import org.apache.commons.io.IOUtils;
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;
@ -53,6 +54,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;
@ -77,6 +79,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
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) {
@ -95,6 +98,13 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
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.
@ -702,21 +712,9 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
}
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()) {
@ -891,6 +889,9 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
configureGeneratorProperties();
configureOpenAPIInfo();
// If the template adapter is mustache, we'll set the config-modified Compiler.
configPostProcessMustacheCompiler();
List<File> files = new ArrayList<File>();
// models
List<String> filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI);
@ -911,24 +912,16 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
return files;
}
@Override
public String getFullTemplateContents(String templateName) {
return readTemplate(getFullTemplateFile(config, templateName));
}
protected File processTemplateToFile(Map<String, Object> 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);
}

View File

@ -17,8 +17,6 @@
package org.openapitools.codegen.config;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -29,41 +27,25 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.core.models.AuthorizationValue;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.Validate;
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;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.Validate;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.ClientOpts;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenConfigLoader;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.SpecValidationException;
import org.openapitools.codegen.auth.AuthParser;
import org.openapitools.codegen.languages.CSharpNancyFXServerCodegen;
import org.openapitools.codegen.languages.CppQt5ClientCodegen;
import org.openapitools.codegen.languages.CppRestSdkClientCodegen;
import org.openapitools.codegen.languages.CppTizenClientCodegen;
import org.openapitools.codegen.languages.JavaJerseyServerCodegen;
import org.openapitools.codegen.languages.PhpLumenServerCodegen;
import org.openapitools.codegen.languages.PhpSlimServerCodegen;
import org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen;
import org.openapitools.codegen.languages.RubySinatraServerCodegen;
import org.openapitools.codegen.languages.ScalaAkkaClientCodegen;
import org.openapitools.codegen.languages.ScalaHttpClientCodegen;
import org.openapitools.codegen.languages.SwiftClientCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
/**
* A class that contains all codegen configuration properties a user would want to manipulate. An
@ -103,6 +85,7 @@ public class CodegenConfigurator implements Serializable {
private boolean enablePostProcessFile;
private boolean enableMinimalUpdate;
private String templateDir;
private String templatingEngineName;
private String auth;
private String apiPackage;
private String modelPackage;
@ -582,6 +565,7 @@ public class CodegenConfigurator implements Serializable {
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);
@ -595,6 +579,13 @@ public class CodegenConfigurator implements Serializable {
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().config(config);
@ -602,8 +593,7 @@ public class CodegenConfigurator implements Serializable {
final List<AuthorizationValue> authorizationValues = AuthParser.parse(auth);
ParseOptions options = new ParseOptions();
options.setResolve(true);
SwaggerParseResult result =
new OpenAPIParser().readLocation(inputSpec, authorizationValues, options);
SwaggerParseResult result = new OpenAPIParser().readLocation(inputSpec, authorizationValues, options);
Set<String> validationMessages = new HashSet<>(result.getMessages());
OpenAPI specification = result.getOpenAPI();
@ -732,4 +722,9 @@ public class CodegenConfigurator implements Serializable {
}
return null;
}
public CodegenConfigurator setTemplatingEngineName(String templatingEngineName) {
this.templatingEngineName = templatingEngineName;
return this;
}
}

View File

@ -25,7 +25,7 @@ import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.mustache.*;
import org.openapitools.codegen.templating.mustache.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -25,7 +25,7 @@ import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.mustache.IndentedLambda;
import org.openapitools.codegen.templating.mustache.IndentedLambda;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -21,7 +21,7 @@ import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.mustache.JoinWithCommaLambda;
import org.openapitools.codegen.templating.mustache.JoinWithCommaLambda;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -23,7 +23,7 @@ import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
import org.openapitools.codegen.languages.features.GzipFeatures;
import org.openapitools.codegen.languages.features.PerformBeanValidationFeatures;
import org.openapitools.codegen.mustache.CaseFormatLambda;
import org.openapitools.codegen.templating.mustache.CaseFormatLambda;
import org.openapitools.codegen.utils.ProcessUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -24,7 +24,7 @@ import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.mustache.*;
import org.openapitools.codegen.templating.mustache.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -21,7 +21,7 @@ import com.samskivert.mustache.Mustache;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.mustache.*;
import org.openapitools.codegen.templating.mustache.IndentedLambda;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -0,0 +1,75 @@
package org.openapitools.codegen.templating;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Jackson2Helper;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.context.FieldValueResolver;
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.github.jknack.handlebars.helper.ConditionalHelpers;
import com.github.jknack.handlebars.helper.StringHelpers;
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.AbstractTemplatingEngineAdapter;
import org.openapitools.codegen.api.TemplatingGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
static Logger LOGGER = LoggerFactory.getLogger(HandlebarsEngineAdapter.class);
private final String[] extensions = new String[]{"handlebars", "hbs"};
public String compileTemplate(TemplatingGenerator generator,
Map<String, Object> bundle, String templateFile) throws IOException {
TemplateLoader loader = new AbstractTemplateLoader() {
@Override
public TemplateSource sourceAt(String location) {
return findTemplate(generator, location);
}
};
Context context = Context
.newBuilder(bundle)
.resolver(
MapValueResolver.INSTANCE,
JavaBeanValueResolver.INSTANCE,
FieldValueResolver.INSTANCE)
.build();
Handlebars handlebars = new Handlebars(loader);
handlebars.registerHelperMissing((obj, options) -> {
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:\n%s", options.helperName, options.fn.text()));
return "";
});
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
StringHelpers.register(handlebars);
handlebars.registerHelpers(ConditionalHelpers.class);
handlebars.registerHelpers(org.openapitools.codegen.templating.handlebars.StringHelpers.class);
Template tmpl = handlebars.compile(templateFile);
return tmpl.apply(context);
}
public TemplateSource findTemplate(TemplatingGenerator generator, String templateFile) {
for (String file : getModifiedFileLocation(templateFile)) {
try {
return new StringTemplateSource(file, generator.getFullTemplateContents(file));
} catch (Exception ignored) {
}
}
throw new RuntimeException("couldnt find a subtemplate " + templateFile);
}
@Override
public String[] getFileExtensions() {
return extensions;
}
}

View File

@ -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<String, Object> 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;
}
}

View File

@ -0,0 +1,65 @@
package org.openapitools.codegen.templating.handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.TagType;
import java.io.IOException;
import java.util.Locale;
public enum StringHelpers implements Helper<Object> {
/**
* Indicates the string starts with the defined value.
* For example:
*
* <pre>
* {{startsWith a b ["insensitive"]}}
* </pre>
*
* <pre>
* {{startsWith a text='b' [insensitive=true]}}
* </pre>
*
* Render 'yes' or 'no':
* <pre>
* {{#startsWith a b}}
* yes
* {{else}}
* no
* {{/startsWith}}
* </pre>
*
* Render 'true' or 'false':
* <pre>
* {{startsWith a b}}
* </pre>
*
* Render 'y' or 'n':
* <pre>
* {{startsWith a b yes='y' no='n'}}
* </pre>
*
* If value is "handlebars.java", the output will be "Handlebars.java".
*/
startsWith {
@Override
public Object apply(Object value, Options options) throws IOException {
String match = options.param(0, options.hash("text", ""));
if (match.length() < 1) {
return false;
}
boolean caseInsensitive = options.hash("insensitive", false);
boolean result = caseInsensitive ? value.toString().toLowerCase(Locale.ROOT).startsWith(match.toLowerCase(Locale.ROOT)) : value.toString().startsWith(match);
if (options.tagType == TagType.SECTION) {
return result ? options.fn() : options.inverse();
}
return result
? options.hash("yes", true)
: options.hash("no", false);
}
}
}

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

View File

@ -1,4 +1,4 @@
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.google.common.base.CaseFormat;
import com.samskivert.mustache.Mustache;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.openapitools.codegen.mustache;
package org.openapitools.codegen.templating.mustache;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;

View File

View File

@ -10,6 +10,7 @@ import org.openapitools.codegen.MockDefaultGenerator;
import org.openapitools.codegen.MockDefaultGenerator.WrittenTemplateBasedFile;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.languages.JavaJerseyServerCodegen;
import org.openapitools.codegen.templating.MustacheEngineAdapter;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

View File

@ -0,0 +1,86 @@
package org.openapitools.codegen.templating.handlebars;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.context.FieldValueResolver;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.HashMap;
import static org.testng.Assert.assertEquals;
public class StringHelpersTest {
private Handlebars handlebars = null;
private void evaluate(HashMap<String, Object> data, String template, String expect) throws IOException {
Context context = Context
.newBuilder(data)
.resolver(
FieldValueResolver.INSTANCE)
.build();
Template tmpl = handlebars.compileInline(template);
String actual = tmpl.apply(context);
assertEquals(actual, expect);
}
@BeforeMethod
public void setup() {
handlebars = new Handlebars();
handlebars.registerHelpers(StringHelpers.class);
}
@Test(description = "Handlebars StringHelpers.startsWith, section")
public void startsWithSectionalTest() throws IOException {
HashMap<String, Object> data = new HashMap<String, Object>() {{
put("asdf", "asdf");
put("a", "a");
put("b", "b");
}};
String template = "{{~#startsWith asdf a ~}}yes{{~else~}}no{{~/startsWith~}}";
evaluate(data, template, "yes");
template = "{{~#startsWith asdf b ~}}yes{{~else~}}no{{~/startsWith~}}";
evaluate(data, template, "no");
}
@Test(description = "Handlebars StringHelpers.startsWith")
public void startsWithTest() throws IOException {
HashMap<String, Object> data = new HashMap<String, Object>() {{
put("asdf", "asdf");
put("ASDF", "ASDF");
put("a", "a");
put("b", "b");
}};
evaluate(data, "{{startsWith asdf a}}", "true");
evaluate(data, "{{startsWith asdf b}}", "false");
evaluate(data, "{{startsWith ASDF a insensitive=true }}", "true");
evaluate(data, "{{startsWith ASDF a insensitive=false }}", "false");
evaluate(data, "{{startsWith ASDF 'a' insensitive=true }}", "true");
evaluate(data, "{{startsWith ASDF b insensitive=true }}", "false");
evaluate(data, "{{startsWith ASDF 'b' insensitive=true }}", "false");
evaluate(data, "{{startsWith ASDF insensitive=true text='a'}}", "true");
evaluate(data, "{{startsWith ASDF insensitive=true text='a' yes='✓' no='✘'}}", "");
evaluate(data, "{{startsWith ASDF insensitive=false text='a' yes='✓' no='✘'}}", "");
}
@Test(description = "Handlebars StringHelpers.startsWith, yes/no override")
public void startsWithYesOverrideTest() throws IOException {
HashMap<String, Object> data = new HashMap<String, Object>() {{
put("asdf", "asdf");
put("a", "a");
put("b", "b");
}};
String template = "{{startsWith asdf a yes='y' no='n'}}";
evaluate(data, template, "y");
template = "{{startsWith asdf b yes='y' no='n'}}";
evaluate(data, template, "n");
}
}

View File

@ -1293,6 +1293,7 @@
<module>modules/openapi-generator-maven-plugin</module>
<module>modules/openapi-generator-gradle-plugin</module>
<module>modules/openapi-generator-online</module>
<module>modules/openapi-generator-core</module>
</modules>
<reporting>
<outputDirectory>target/site</outputDirectory>
@ -1372,6 +1373,7 @@
<slf4j-version>1.7.12</slf4j-version>
<scala-maven-plugin-version>3.2.1</scala-maven-plugin-version>
<jmustache-version>1.14</jmustache-version>
<handlebars.java-version>4.1.2</handlebars.java-version>
<testng-version>6.14.3</testng-version>
<surefire-version>2.22.1</surefire-version>
<jmockit-version>1.43</jmockit-version>