From 5f27fcab18083526ca2b8033f70dc21f2ac846a7 Mon Sep 17 00:00:00 2001 From: David Biesack Date: Thu, 23 Mar 2017 03:15:01 -0400 Subject: [PATCH] Add support for Markdown in -l html (#5144) * Sync with upstream/master * Support Markdown in -l html Add https://github.com/atlassian/commonmark-java to modules/swagger-codegen to convert Markdown to HTML, update StaticHtmlGenerator to use this (see the toHeml() method and its uses) Add a new test case bin/html-markdown.sh and modules/swagger-codegen/src/test/resources/2_0/markdown.yaml * Support Markdown in -l html Add https://github.com/atlassian/commonmark-java to modules/swagger-codegen to convert Markdown to HTML, update StaticHtmlGenerator to use this (see the toHeml() method and its uses) Add a new test case bin/html-markdown.sh and modules/swagger-codegen/src/test/resources/2_0/markdown.yaml --- bin/html-markdown.sh | 31 ++ modules/swagger-codegen/pom.xml | 5 + .../languages/StaticHtmlGenerator.java | 84 +++++- .../io/swagger/codegen/utils/Markdown.java | 47 +++ .../src/test/resources/2_0/markdown.yaml | 75 +++++ pom.xml | 12 +- samples/html.md/.swagger-codegen-ignore | 23 ++ samples/html.md/index.html | 278 ++++++++++++++++++ samples/html/index.html | 116 ++++---- 9 files changed, 601 insertions(+), 70 deletions(-) create mode 100755 bin/html-markdown.sh create mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/utils/Markdown.java create mode 100644 modules/swagger-codegen/src/test/resources/2_0/markdown.yaml create mode 100644 samples/html.md/.swagger-codegen-ignore create mode 100644 samples/html.md/index.html diff --git a/bin/html-markdown.sh b/bin/html-markdown.sh new file mode 100755 index 00000000000..089abfc5e8f --- /dev/null +++ b/bin/html-markdown.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +SCRIPT="$0" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar" + +if [ ! -f "$executable" ] +then + mvn clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="$@ generate -i modules/swagger-codegen/src/test/resources/2_0/markdown.yaml -l html -o samples/html.md" + +java $JAVA_OPTS -jar $executable $ags diff --git a/modules/swagger-codegen/pom.xml b/modules/swagger-codegen/pom.xml index 2802b90a1e8..05206eb2f9b 100644 --- a/modules/swagger-codegen/pom.xml +++ b/modules/swagger-codegen/pom.xml @@ -273,6 +273,11 @@ ${diffutils-version} test + + com.atlassian.commonmark + commonmark + 0.9.0 + diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/StaticHtmlGenerator.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/StaticHtmlGenerator.java index 1a69811723e..a22010e51fb 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/StaticHtmlGenerator.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/StaticHtmlGenerator.java @@ -3,22 +3,31 @@ package io.swagger.codegen.languages; import io.swagger.codegen.CliOption; import io.swagger.codegen.CodegenConfig; import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenModel; import io.swagger.codegen.CodegenOperation; +import io.swagger.codegen.CodegenParameter; +import io.swagger.codegen.CodegenProperty; import io.swagger.codegen.CodegenResponse; import io.swagger.codegen.CodegenType; import io.swagger.codegen.DefaultCodegen; import io.swagger.codegen.SupportingFile; -import io.swagger.models.Operation; +import io.swagger.models.Info; +import io.swagger.models.Model; +import io.swagger.models.Swagger; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.MapProperty; import io.swagger.models.properties.Property; - -import java.util.ArrayList; +import io.swagger.codegen.utils.Markdown; 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; + +import io.swagger.codegen.utils.Markdown; + public class StaticHtmlGenerator extends DefaultCodegen implements CodegenConfig { protected String invokerPackage = "io.swagger.client"; protected String groupId = "io.swagger"; @@ -61,13 +70,19 @@ public class StaticHtmlGenerator extends DefaultCodegen implements CodegenConfig importMapping = new HashMap(); } - @Override + + + /** + * Convert Markdown (CommonMark) to HTML. This class also disables normal HTML + * escaping in the Mustache engine (see {@link #processCompiler(Compiler)} above.) + */ + @Override public String escapeText(String input) { // newline escaping disabled for HTML documentation for markdown to work correctly - return input; + return toHtml(input); } - @Override + @Override public CodegenType getTag() { return CodegenType.DOCUMENTATION; } @@ -124,4 +139,61 @@ public class StaticHtmlGenerator extends DefaultCodegen implements CodegenConfig // 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); + } + + public void preprocessSwagger(Swagger swagger) { + Info info = swagger.getInfo(); + info.setDescription(toHtml(info.getDescription())); + info.setTitle(toHtml(info.getTitle())); + Map models = swagger.getDefinitions(); + for (Model 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/swagger-codegen/src/main/java/io/swagger/codegen/utils/Markdown.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/utils/Markdown.java new file mode 100644 index 00000000000..f57f59e9842 --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/utils/Markdown.java @@ -0,0 +1,47 @@ +package io.swagger.codegen.utils; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; + + +/** + * Utility class to convert Markdown (CommonMark) to HTML. + * This class is threadsafe. + */ +public class Markdown { + + // see https://github.com/atlassian/commonmark-java + private final Parser parser = Parser.builder().build(); + private final HtmlRenderer renderer = HtmlRenderer.builder().build(); + + /** + * Convert input markdown text to HTML. + * Simple text is not wrapped in

...

. + * @param markdown text with Markdown styles. If null, "" is returned. + * @return HTML rendering from the Markdown + */ + public String toHtml(String markdown) { + if (markdown == null) + return ""; + Node document = parser.parse(markdown); + String html = renderer.render(document); + html = unwrapped(html); + return html; + } + + // The CommonMark library wraps the HTML with + //

... html ...

\n + // This method removes that markup wrapper if there are no other

elements, + // do that Markdown can be used in non-block contexts such as operation summary etc. + private static final String P_END = "

\n"; + private static final String P_START = "

"; + private String unwrapped(String html) { + if (html.startsWith(P_START) && html.endsWith(P_END) + && html.lastIndexOf(P_START) == 0) + return html.substring(P_START.length(), + html.length() - P_END.length()); + else + return html; + } +} diff --git a/modules/swagger-codegen/src/test/resources/2_0/markdown.yaml b/modules/swagger-codegen/src/test/resources/2_0/markdown.yaml new file mode 100644 index 00000000000..efb852f4036 --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/2_0/markdown.yaml @@ -0,0 +1,75 @@ +swagger: '2.0' + +info: + version: '0.1.0' + title: An *API* with more **Markdown** in summary, description, and other text + description: > + Not really a *pseudo-randum* number generator API. + This API uses [Markdown](http://daringfireball.net/projects/markdown/syntax) + in text: + + 1. in this API description + + 1. in operation summaries + + 1. in operation descriptions + + 1. in schema (model) titles and descriptions + + 1. in schema (model) member descriptions + +schemes: + - http +host: api.example.com +basePath: /v1 +tags: + - name: tag1 + description: A simple API **tag** +securityDefinitions: + apiKey: + type: apiKey + in: header + name: api_key +security: + - apiKey: [] + +paths: + + /random: + get: + tags: + - tag1 + summary: A single *random* result + description: Return a single *random* result from a given seed + operationId: getRandomNumber + parameters: + - name: seed + in: query + description: A random number *seed*. + required: true + type: string + responses: + '200': + description: Operation *succeded* + schema: + $ref: '#/definitions/RandomNumber' + '404': + description: Invalid or omitted *seed*. Seeds must be **valid** numbers. + +definitions: + RandomNumber: + title: '*Pseudo-random* number' + description: A *pseudo-random* number generated from a seed. + properties: + value: + description: The *pseudo-random* number + type: number + format: double + seed: + description: The `seed` used to generate this number + type: number + format: double + sequence: + description: The sequence number of this random number. + type: integer + format: int64 diff --git a/pom.xml b/pom.xml index c1270d71aeb..2aff5fef3cd 100644 --- a/pom.xml +++ b/pom.xml @@ -772,7 +772,6 @@ samples/client/petstore/jaxrs-cxf-client samples/client/petstore/javascript samples/client/petstore/python - samples/client/petstore/spring-cloud samples/client/petstore/scala samples/client/petstore/typescript-fetch/builds/default samples/client/petstore/typescript-fetch/builds/es6-target @@ -785,19 +784,20 @@ samples/server/petstore/java-inflector - samples/server/petstore/java-play-framework + samples/server/petstore/undertow samples/server/petstore/jaxrs/jersey1 samples/server/petstore/jaxrs/jersey2 samples/server/petstore/jaxrs-resteasy/default samples/server/petstore/jaxrs-resteasy/joda + samples/server/petstore/scalatra + samples/server/petstore/spring-mvc + samples/client/petstore/spring-cloud + samples/server/petstore/springboot samples/server/petstore/jaxrs-cxf samples/server/petstore/jaxrs-cxf-annotated-base-path samples/server/petstore/jaxrs-cxf-cdi samples/server/petstore/jaxrs-cxf-non-spring-app - samples/server/petstore/scalatra - samples/server/petstore/spring-mvc - samples/server/petstore/springboot - samples/server/petstore/undertow + diff --git a/samples/html.md/.swagger-codegen-ignore b/samples/html.md/.swagger-codegen-ignore new file mode 100644 index 00000000000..c5fa491b4c5 --- /dev/null +++ b/samples/html.md/.swagger-codegen-ignore @@ -0,0 +1,23 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/html.md/index.html b/samples/html.md/index.html new file mode 100644 index 00000000000..28cbed90cc3 --- /dev/null +++ b/samples/html.md/index.html @@ -0,0 +1,278 @@ + + + + An <em>API</em> with more <strong>Markdown</strong> in summary, description, and other text + + + +

An API with more Markdown in summary, description, and other text

+

Not really a pseudo-randum number generator API. This API uses Markdown in text:

+
    +
  1. in this API description
  2. +
  3. in operation summaries
  4. +
  5. in operation descriptions
  6. +
  7. in schema (model) titles and descriptions
  8. +
  9. in schema (model) member descriptions
  10. +
+
+
More information: https://helloreverb.com
+
Contact Info: hello@helloreverb.com
+
Version: 0.1.0
+
BasePath:/v1
+
All rights reserved
+
http://apache.org/licenses/LICENSE-2.0.html
+

Access

+
    +
  1. APIKey KeyParamName:api_key KeyInQuery:false KeyInHeader:true
  2. +
+ +

Methods

+ [ Jump to Models ] + +

Table of Contents

+
+

Tag1

+ + +

Tag1

+
+
+ Up +
get /random
+
A single random result (getRandomNumber)
+
Return a single random result from a given seed
+ + + + + +

Query parameters

+
+
seed (required)
+ +
Query Parameter — A random number seed.
+
+ + +

Return type

+
+ RandomNumber + +
+ + + +

Example data

+
Content-Type: application/json
+
{
+  "sequence" : 1,
+  "seed" : 6.027456183070403,
+  "value" : 0.8008281904610115
+}
+ + +

Responses

+

200

+ Operation succeded + RandomNumber +

404

+ Invalid or omitted seed. Seeds must be valid numbers. + +
+
+ +

Models

+ [ Jump to Methods ] + +

Table of Contents

+
    +
  1. RandomNumber - Pseudo-random number
  2. +
+ +
+

RandomNumber - Pseudo-random number Up

+
A pseudo-random number generated from a seed.
+
+
value (optional)
Double The pseudo-random number format: double
+
seed (optional)
Double The seed used to generate this number format: double
+
sequence (optional)
Long The sequence number of this random number. format: int64
+
+
+ + diff --git a/samples/html/index.html b/samples/html/index.html index 57726e2ecf8..8638d803c18 100644 --- a/samples/html/index.html +++ b/samples/html/index.html @@ -180,8 +180,8 @@ font-style: italic;

Swagger Petstore

-
This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
- +
This is a sample server Petstore server. You can find out more about Swagger at http://swagger.io or on irc.freenode.net, #swagger. For this sample, you can use the api key special-key to test the authorization filters.
+
More information:
Contact Info: apiteam@swagger.io
Version: 1.0.0
BasePath:/v2
@@ -356,18 +356,18 @@ font-style: italic;

Example data

Content-Type: application/json
[ {
-  "tags" : [ {
-    "id" : 7,
-    "name" : "aeiou"
-  } ],
-  "id" : 2,
-  "category" : {
-    "id" : 2,
-    "name" : "aeiou"
-  },
-  "status" : "available",
+  "photoUrls" : [ "aeiou" ],
   "name" : "doggie",
-  "photoUrls" : [ "aeiou" ]
+  "id" : 0,
+  "category" : {
+    "name" : "aeiou",
+    "id" : 6
+  },
+  "tags" : [ {
+    "name" : "aeiou",
+    "id" : 1
+  } ],
+  "status" : "available"
 } ]

Produces

@@ -429,18 +429,18 @@ font-style: italic;

Example data

Content-Type: application/json
[ {
-  "tags" : [ {
-    "id" : 1,
-    "name" : "aeiou"
-  } ],
-  "id" : 9,
-  "category" : {
-    "id" : 4,
-    "name" : "aeiou"
-  },
-  "status" : "available",
+  "photoUrls" : [ "aeiou" ],
   "name" : "doggie",
-  "photoUrls" : [ "aeiou" ]
+  "id" : 0,
+  "category" : {
+    "name" : "aeiou",
+    "id" : 6
+  },
+  "tags" : [ {
+    "name" : "aeiou",
+    "id" : 1
+  } ],
+  "status" : "available"
 } ]

Produces

@@ -502,18 +502,18 @@ font-style: italic;

Example data

Content-Type: application/json
{
-  "tags" : [ {
-    "id" : 5,
-    "name" : "aeiou"
-  } ],
-  "id" : 8,
-  "category" : {
-    "id" : 3,
-    "name" : "aeiou"
-  },
-  "status" : "available",
+  "photoUrls" : [ "aeiou" ],
   "name" : "doggie",
-  "photoUrls" : [ "aeiou" ]
+  "id" : 0,
+  "category" : {
+    "name" : "aeiou",
+    "id" : 6
+  },
+  "tags" : [ {
+    "name" : "aeiou",
+    "id" : 1
+  } ],
+  "status" : "available"
 }

Produces

@@ -679,9 +679,9 @@ font-style: italic;

Example data

Content-Type: application/json
{
-  "message" : "aeiou",
-  "code" : 3,
-  "type" : "aeiou"
+  "code" : 0,
+  "type" : "aeiou",
+  "message" : "aeiou"
 }

Produces

@@ -762,7 +762,7 @@ font-style: italic;

Example data

Content-Type: application/json
{
-  "key" : 9
+  "key" : 0
 }

Produces

@@ -783,7 +783,7 @@ font-style: italic; Up
get /store/order/{orderId}
Find purchase order by ID (getOrderById)
-
For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
+
For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions

Path parameters

@@ -818,12 +818,12 @@ font-style: italic;

Example data

Content-Type: application/json
{
-  "id" : 5,
-  "petId" : 8,
+  "petId" : 6,
+  "quantity" : 1,
+  "id" : 0,
+  "shipDate" : "2000-01-23T04:56:07.000+00:00",
   "complete" : false,
-  "status" : "placed",
-  "quantity" : 4,
-  "shipDate" : "2000-01-23T04:56:07.000+00:00"
+  "status" : "placed"
 }

Produces

@@ -887,12 +887,12 @@ font-style: italic;

Example data

Content-Type: application/json
{
-  "id" : 6,
-  "petId" : 9,
+  "petId" : 6,
+  "quantity" : 1,
+  "id" : 0,
+  "shipDate" : "2000-01-23T04:56:07.000+00:00",
   "complete" : false,
-  "status" : "placed",
-  "quantity" : 2,
-  "shipDate" : "2000-01-23T04:56:07.000+00:00"
+  "status" : "placed"
 }

Produces

@@ -1078,7 +1078,7 @@ font-style: italic;
username (required)
-
Path Parameter — The name that needs to be fetched. Use user1 for testing.
+
Path Parameter — The name that needs to be fetched. Use user1 for testing.
@@ -1109,14 +1109,14 @@ font-style: italic;

Example data

Content-Type: application/json
{
-  "id" : 4,
-  "lastName" : "aeiou",
-  "phone" : "aeiou",
-  "username" : "aeiou",
-  "email" : "aeiou",
-  "userStatus" : 1,
   "firstName" : "aeiou",
-  "password" : "aeiou"
+  "lastName" : "aeiou",
+  "password" : "aeiou",
+  "userStatus" : 6,
+  "phone" : "aeiou",
+  "id" : 0,
+  "email" : "aeiou",
+  "username" : "aeiou"
 }

Produces