From ffecaa1b0bff867d29c4a6e0b2a6b16d3afbb744 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 3 Apr 2018 14:36:08 +0800 Subject: [PATCH] add html, html2 generators --- .../languages/StaticHtml2Generator.java | 273 ++++++++++++++++++ .../languages/StaticHtmlGenerator.java | 203 +++++++++++++ .../org.openapitools.codegen.CodegenConfig | 2 + 3 files changed, 478 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtml2Generator.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtmlGenerator.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtml2Generator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtml2Generator.java new file mode 100644 index 00000000000..41697ea5c1d --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtml2Generator.java @@ -0,0 +1,273 @@ +package org.openapitools.codegen.languages; + +import com.samskivert.mustache.Mustache; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenResponse; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.utils.Markdown; + +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; + +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +public class StaticHtml2Generator extends DefaultCodegen implements CodegenConfig { + protected String invokerPackage = "io.swagger.client"; // default for Java and Android + protected String phpInvokerPackage = "Swagger\\Client"; // default for PHP + protected String packageName = "IO.Swagger"; // default for C# + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-client"; + protected String artifactVersion = "1.0.0"; + protected String jsProjectName; + protected String jsModuleName; + protected String perlModuleName = "WWW::SwaggerClient"; + protected String pythonPackageName = "swagger_client"; + + public StaticHtml2Generator() { + super(); + outputFolder = "docs"; + embeddedTemplateDir = templateDir = "htmlDocs2"; + + defaultIncludes = new HashSet(); + + cliOptions.add(new CliOption("appName", "short name of the application")); + cliOptions.add(new CliOption("appDescription", "description of the application")); + cliOptions.add(new CliOption("infoUrl", "a URL where users can get more information about the application")); + cliOptions.add(new CliOption("infoEmail", "an email address to contact for inquiries about the application")); + cliOptions.add(new CliOption("licenseInfo", "a short description of the license")); + cliOptions.add(new CliOption("licenseUrl", "a URL pointing to the full license")); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.PHP_INVOKER_PACKAGE, CodegenConstants.PHP_INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.PERL_MODULE_NAME, CodegenConstants.PERL_MODULE_NAME_DESC)); + cliOptions.add(new CliOption(CodegenConstants.PYTHON_PACKAGE_NAME, CodegenConstants.PYTHON_PACKAGE_NAME_DESC)); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "C# package name")); + cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, CodegenConstants.GROUP_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, CodegenConstants.ARTIFACT_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, CodegenConstants.ARTIFACT_VERSION_DESC)); + + additionalProperties.put("appName", "Swagger Sample"); + additionalProperties.put("appDescription", "A sample swagger server"); + additionalProperties.put("infoUrl", "https://helloreverb.com"); + additionalProperties.put("infoEmail", "hello@helloreverb.com"); + additionalProperties.put("licenseInfo", "All rights reserved"); + additionalProperties.put("licenseUrl", "http://apache.org/licenses/LICENSE-2.0.html"); + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.PHP_INVOKER_PACKAGE, phpInvokerPackage); + additionalProperties.put(CodegenConstants.PERL_MODULE_NAME, perlModuleName); + additionalProperties.put(CodegenConstants.PYTHON_PACKAGE_NAME, pythonPackageName); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + + supportingFiles.add(new SupportingFile("index.mustache", "", "index.html")); + reservedWords = new HashSet(); + + languageSpecificPrimitives = new HashSet(); + importMapping = new HashMap(); + } + + @Override + public CodegenType getTag() { + return CodegenType.DOCUMENTATION; + } + + @Override + public String getName() { + return "html2"; + } + + @Override + public String escapeText(String input) { + // newline escaping disabled for HTML documentation for markdown to work correctly + return input; + } + + @Override + public String getHelp() { + return "Generates a static HTML file."; + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]"; + } else if (isMapSchema(p)) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + + return getSchemaType(p) + "[String, " + getTypeDeclaration(inner) + "]"; + } + return super.getTypeDeclaration(p); + } + + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + op.httpMethod = op.httpMethod.toLowerCase(); + for (CodegenResponse response : op.responses){ + if ("0".equals(response.code)){ + response.code = "default"; + } + } + op.formParams = postProcessParameterEnum(op.formParams); + } + return objs; + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + + if (openAPI.getInfo() != null) { + Info info = openAPI.getInfo(); + if (StringUtils.isBlank(jsProjectName) && info.getTitle() != null) { + // when jsProjectName is not specified, generate it from info.title + jsProjectName = sanitizeName(dashize(info.getTitle())); + } + } + + // default values + if (StringUtils.isBlank(jsProjectName)) { + jsProjectName = "swagger-js-client"; + } + if (StringUtils.isBlank(jsModuleName)) { + jsModuleName = camelize(underscore(jsProjectName)); + } + + additionalProperties.put("jsProjectName", jsProjectName); + additionalProperties.put("jsModuleName", jsModuleName); + + preparHtmlForGlobalDescription(openAPI); + } + + @Override + public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map definitions, OpenAPI openAPI) { + CodegenOperation op = super.fromOperation(path, httpMethod, operation, definitions, openAPI); + if (op.returnType != null) { + op.returnType = normalizeType(op.returnType); + } + + //path is an unescaped variable in the mustache template api.mustache line 82 '<&path>' + op.path = sanitizePath(op.path); + + // Set vendor-extension to be used in template: + // x-codegen-hasMoreRequired + // x-codegen-hasMoreOptional + // x-codegen-hasRequiredParams + CodegenParameter lastRequired = null; + CodegenParameter lastOptional = null; + for (CodegenParameter p : op.allParams) { + if (p.required) { + lastRequired = p; + } else { + lastOptional = p; + } + } + for (CodegenParameter p : op.allParams) { + if (p == lastRequired) { + p.vendorExtensions.put("x-codegen-hasMoreRequired", false); + } else if (p == lastOptional) { + p.vendorExtensions.put("x-codegen-hasMoreOptional", false); + } else { + p.vendorExtensions.put("x-codegen-hasMoreRequired", true); + p.vendorExtensions.put("x-codegen-hasMoreOptional", true); + } + } + op.vendorExtensions.put("x-codegen-hasRequiredParams", lastRequired != null); + + op.vendorExtensions.put("x-codegen-httpMethodUpperCase", httpMethod.toUpperCase()); + + return op; + } + + /** + * Parse Markdown to HTML for the main "Description" attribute + * + * @param swagger The base object containing the global description through "Info" class + * @return Void + */ + private void preparHtmlForGlobalDescription(OpenAPI openAPI) { + if (openAPI.getInfo() == null) { + return; + } + + String currentDescription = openAPI.getInfo().getDescription(); + if (currentDescription != null && !currentDescription.isEmpty()) { + Markdown markInstance = new Markdown(); + openAPI.getInfo().setDescription( markInstance.toHtml(currentDescription) ); + } else { + LOGGER.error("OpenAPI object description is empty [" + openAPI.getInfo().getTitle() + "]"); + } + } + + /** + * Format to HTML the enums contained in every operations + * + * @param parameterList The whole parameters contained in one operation + * @return String | Html formated enum + */ + public List postProcessParameterEnum(List parameterList) { + String enumFormatted = ""; + for(CodegenParameter parameter : parameterList) { + if (parameter.isEnum) { + for (int i = 0; i < parameter._enum.size(); i++) { + String spacer = (i == (parameter._enum.size() - 1)) ? " " : ", "; + + if (parameter._enum.get(i) != null) + enumFormatted += "`" + parameter._enum.get(i) + "`" + spacer; + } + Markdown markInstance = new Markdown(); + if (!enumFormatted.isEmpty()) + parameter.vendorExtensions.put("x-eumFormatted", markInstance.toHtml(enumFormatted)); + } + } + return parameterList; + } + + private String sanitizePath(String p) { + //prefer replace a ', instead of a fuLL URL encode for readability + return p.replaceAll("'", "%27"); + } + + /** + * Normalize type by wrapping primitive types with single quotes. + * + * @param type Primitive type + * @return Normalized type + */ + public String normalizeType(String type) { + return type.replaceAll("\\b(Boolean|Integer|Number|String|Date)\\b", "'$1'"); + } + + @Override + public String escapeQuotationMark(String input) { + // just return the original string + return input; + } + + @Override + public String escapeUnsafeCharacters(String input) { + // just return the original string + return input; + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtmlGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtmlGenerator.java new file mode 100644 index 00000000000..2c55066edce --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/StaticHtmlGenerator.java @@ -0,0 +1,203 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenResponse; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.utils.Markdown; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.info.Info; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import com.samskivert.mustache.Escapers; +import com.samskivert.mustache.Mustache.Compiler; + +public class StaticHtmlGenerator extends DefaultCodegen implements CodegenConfig { + protected String invokerPackage = "io.swagger.client"; + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-client"; + protected String artifactVersion = "1.0.0"; + + public StaticHtmlGenerator() { + super(); + outputFolder = "docs"; + embeddedTemplateDir = templateDir = "htmlDocs"; + + defaultIncludes = new HashSet(); + + cliOptions.add(new CliOption("appName", "short name of the application")); + cliOptions.add(new CliOption("appDescription", "description of the application")); + cliOptions.add(new CliOption("infoUrl", "a URL where users can get more information about the application")); + cliOptions.add(new CliOption("infoEmail", "an email address to contact for inquiries about the application")); + cliOptions.add(new CliOption("licenseInfo", "a short description of the license")); + cliOptions.add(new CliOption("licenseUrl", "a URL pointing to the full license")); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, CodegenConstants.GROUP_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, CodegenConstants.ARTIFACT_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, CodegenConstants.ARTIFACT_VERSION_DESC)); + + additionalProperties.put("appName", "Swagger Sample"); + additionalProperties.put("appDescription", "A sample swagger server"); + additionalProperties.put("infoUrl", "https://helloreverb.com"); + additionalProperties.put("infoEmail", "hello@helloreverb.com"); + additionalProperties.put("licenseInfo", "All rights reserved"); + additionalProperties.put("licenseUrl", "http://apache.org/licenses/LICENSE-2.0.html"); + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + + supportingFiles.add(new SupportingFile("index.mustache", "", "index.html")); + reservedWords = new HashSet(); + + languageSpecificPrimitives = new HashSet(); + importMapping = new HashMap(); + } + + /** + * Convert Markdown (CommonMark) to HTML. This class also disables normal HTML + * escaping in the Mustache engine (see {@link DefaultCodegen#processCompiler(Compiler)} above.) + */ + @Override + public String escapeText(String input) { + // newline escaping disabled for HTML documentation for markdown to work correctly + return toHtml(input); + } + + @Override + public CodegenType getTag() { + return CodegenType.DOCUMENTATION; + } + + @Override + public String getName() { + return "html"; + } + + @Override + public String getHelp() { + return "Generates a static HTML file."; + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]"; + } else if (isMapSchema(p)) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + + return getSchemaType(p) + "[String, " + getTypeDeclaration(inner) + "]"; + } + return super.getTypeDeclaration(p); + } + +@Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + op.httpMethod = op.httpMethod.toLowerCase(); + for (CodegenResponse response : op.responses) { + if ("0".equals(response.code)) { + response.code = "default"; + } + } + } + return objs; + } + + + @Override + public String escapeQuotationMark(String input) { + // just return the original string + return input; + } + + @Override + public String escapeUnsafeCharacters(String input) { + // just return the original string + return input; + } + + /** + * Markdown conversion emits HTML and by default, the Mustache + * {@link Compiler} will escape HTML. For example a summary + * "Text with **bold**" is converted from Markdown to HTML as + * "Text with <strong>bold</strong> text" and then + * the default compiler with HTML escaping on turns this into + * "Text with &lt;strong&gt;bold&lt;/strong&gt; text". + * Here, we disable escaping by setting the compiler to {@link Escapers#NONE + * Escapers.NONE} + */ + @Override + public Compiler processCompiler(Compiler compiler) { + return compiler.withEscaper(Escapers.NONE); + } + + private Markdown markdownConverter = new Markdown(); + + private static final boolean CONVERT_TO_MARKDOWN_VIA_ESCAPE_TEXT = false; + + /** + * Convert Markdown text to HTML + * @param input text in Markdown; may be null. + * @return the text, converted to Markdown. For null input, "" is returned. + */ + public String toHtml(String input) { + if (input == null) + return ""; + return markdownConverter.toHtml(input); + } + + // DefaultCodegen converts model names to UpperCamelCase + // but for static HTML, we want the names to be preserved as coded in the OpenApi + // so HTML links work + @Override + public String toModelName(final String name) { + return name; + } + + public void preprocessOpenAPI(OpenAPI openAPI) { + Info info = openAPI.getInfo(); + info.setDescription(toHtml(info.getDescription())); + info.setTitle(toHtml(info.getTitle())); + Map models = openAPI.getComponents().getSchemas(); + for (Schema model : models.values()) { + model.setDescription(toHtml(model.getDescription())); + model.setTitle(toHtml(model.getTitle())); + } + } + + // override to post-process any parameters + public void postProcessParameter(CodegenParameter parameter) { + parameter.description = toHtml(parameter.description); + parameter.unescapedDescription = toHtml( + parameter.unescapedDescription); + } + + // override to post-process any model properties + public void postProcessModelProperty(CodegenModel model, + CodegenProperty property) { + property.description = toHtml(property.description); + property.unescapedDescription = toHtml( + property.unescapedDescription); + } + +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 879f60e2f96..8e88190041d 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -57,6 +57,8 @@ org.openapitools.codegen.languages.ScalaGatlingCodegen org.openapitools.codegen.languages.ScalaLagomServerCodegen org.openapitools.codegen.languages.ScalazClientCodegen org.openapitools.codegen.languages.StaticDocCodegen +org.openapitools.codegen.languages.StaticHtmlGenerator +org.openapitools.codegen.languages.StaticHtml2Generator org.openapitools.codegen.languages.SwiftClientCodegen org.openapitools.codegen.languages.Swift3Codegen org.openapitools.codegen.languages.Swift4Codegen