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
This commit is contained in:
David Biesack 2017-03-23 03:15:01 -04:00 committed by wing328
parent 55b7db3456
commit 5f27fcab18
9 changed files with 601 additions and 70 deletions

31
bin/html-markdown.sh Executable file
View File

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

View File

@ -273,6 +273,11 @@
<version>${diffutils-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
<repositories>

View File

@ -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,10 +70,16 @@ public class StaticHtmlGenerator extends DefaultCodegen implements CodegenConfig
importMapping = new HashMap<String, String>();
}
/**
* 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
@ -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
* <code>"Text with **bold**"</code> is converted from Markdown to HTML as
* <code>"Text with &lt;strong&gt;bold&lt;/strong&gt; text"</code> and then
* the default compiler with HTML escaping on turns this into
* <code>"Text with &amp;lt;strong&amp;gt;bold&amp;lt;/strong&amp;gt; text"</code>.
* 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<String, Model> 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);
}
}

View File

@ -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.
* <a href='https://github.com/atlassian/commonmark-java/issues/83'>This class is threadsafe.</a>
*/
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 <p>...</p>.
* @param markdown text with Markdown styles. If <code>null<code>, </code>""</code> 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
// <p> ... html ... </p>\n
// This method removes that markup wrapper if there are no other <p> elements,
// do that Markdown can be used in non-block contexts such as operation summary etc.
private static final String P_END = "</p>\n";
private static final String P_START = "<p>";
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;
}
}

View File

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

12
pom.xml
View File

@ -772,7 +772,6 @@
<module>samples/client/petstore/jaxrs-cxf-client</module>
<module>samples/client/petstore/javascript</module>
<module>samples/client/petstore/python</module>
<module>samples/client/petstore/spring-cloud</module>
<module>samples/client/petstore/scala</module>
<module>samples/client/petstore/typescript-fetch/builds/default</module>
<module>samples/client/petstore/typescript-fetch/builds/es6-target</module>
@ -785,19 +784,20 @@
<!--module>samples/client/petstore/swift/SwaggerClientTests</module-->
<!-- servers -->
<module>samples/server/petstore/java-inflector</module>
<module>samples/server/petstore/java-play-framework</module>
<module>samples/server/petstore/undertow</module>
<module>samples/server/petstore/jaxrs/jersey1</module>
<module>samples/server/petstore/jaxrs/jersey2</module>
<module>samples/server/petstore/jaxrs-resteasy/default</module>
<module>samples/server/petstore/jaxrs-resteasy/joda</module>
<module>samples/server/petstore/scalatra</module>
<module>samples/server/petstore/spring-mvc</module>
<module>samples/client/petstore/spring-cloud</module>
<module>samples/server/petstore/springboot</module>
<module>samples/server/petstore/jaxrs-cxf</module>
<module>samples/server/petstore/jaxrs-cxf-annotated-base-path</module>
<module>samples/server/petstore/jaxrs-cxf-cdi</module>
<module>samples/server/petstore/jaxrs-cxf-non-spring-app</module>
<module>samples/server/petstore/scalatra</module>
<module>samples/server/petstore/spring-mvc</module>
<module>samples/server/petstore/springboot</module>
<module>samples/server/petstore/undertow</module>
<!--<module>samples/server/petstore/java-msf4j</module> note: JDK8 only -->
</modules>
</profile>

View File

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

278
samples/html.md/index.html Normal file
View File

@ -0,0 +1,278 @@
<!doctype html>
<html>
<head>
<title>An <em>API</em> with more <strong>Markdown</strong> in summary, description, and other text</title>
<style type="text/css">
body {
font-family: Trebuchet MS, sans-serif;
font-size: 15px;
color: #444;
margin-right: 24px;
}
h1 {
font-size: 25px;
}
h2 {
font-size: 20px;
}
h3 {
font-size: 16px;
font-weight: bold;
}
hr {
height: 1px;
border: 0;
color: #ddd;
background-color: #ddd;
}
.app-desc {
clear: both;
margin-left: 20px;
}
.param-name {
width: 100%;
}
.license-info {
margin-left: 20px;
}
.license-url {
margin-left: 20px;
}
.model {
margin: 0 0 0px 20px;
}
.method {
margin-left: 20px;
}
.method-notes {
margin: 10px 0 20px 0;
font-size: 90%;
color: #555;
}
pre {
padding: 10px;
margin-bottom: 2px;
}
.http-method {
text-transform: uppercase;
}
pre.get {
background-color: #0f6ab4;
}
pre.post {
background-color: #10a54a;
}
pre.put {
background-color: #c5862b;
}
pre.delete {
background-color: #a41e22;
}
.huge {
color: #fff;
}
pre.example {
background-color: #f3f3f3;
padding: 10px;
border: 1px solid #ddd;
}
code {
white-space: pre;
}
.nickname {
font-weight: bold;
}
.method-path {
font-size: 1.5em;
background-color: #0f6ab4;
}
.up {
float:right;
}
.parameter {
width: 500px;
}
.param {
width: 500px;
padding: 10px 0 0 20px;
font-weight: bold;
}
.param-desc {
width: 700px;
padding: 0 0 0 20px;
color: #777;
}
.param-type {
font-style: italic;
}
.param-enum-header {
width: 700px;
padding: 0 0 0 60px;
color: #777;
font-weight: bold;
}
.param-enum {
width: 700px;
padding: 0 0 0 80px;
color: #777;
font-style: italic;
}
.field-label {
padding: 0;
margin: 0;
clear: both;
}
.field-items {
padding: 0 0 15px 0;
margin-bottom: 15px;
}
.return-type {
clear: both;
padding-bottom: 10px;
}
.param-header {
font-weight: bold;
}
.method-tags {
text-align: right;
}
.method-tag {
background: none repeat scroll 0% 0% #24A600;
border-radius: 3px;
padding: 2px 10px;
margin: 2px;
color: #FFF;
display: inline-block;
text-decoration: none;
}
</style>
</head>
<body>
<h1>An <em>API</em> with more <strong>Markdown</strong> in summary, description, and other text</h1>
<div class="app-desc"><p>Not really a <em>pseudo-randum</em> number generator API. This API uses <a href="http://daringfireball.net/projects/markdown/syntax">Markdown</a> in text:</p>
<ol>
<li>in this API description</li>
<li>in operation summaries</li>
<li>in operation descriptions</li>
<li>in schema (model) titles and descriptions</li>
<li>in schema (model) member descriptions</li>
</ol>
</div>
<div class="app-desc">More information: <a href="https://helloreverb.com">https://helloreverb.com</a></div>
<div class="app-desc">Contact Info: <a href="hello@helloreverb.com">hello@helloreverb.com</a></div>
<div class="app-desc">Version: 0.1.0</div>
<div class="app-desc">BasePath:/v1</div>
<div class="license-info">All rights reserved</div>
<div class="license-url">http://apache.org/licenses/LICENSE-2.0.html</div>
<h2>Access</h2>
<ol>
<li>APIKey KeyParamName:api_key KeyInQuery:false KeyInHeader:true</li>
</ol>
<h2><a name="__Methods">Methods</a></h2>
[ Jump to <a href="#__Models">Models</a> ]
<h3>Table of Contents </h3>
<div class="method-summary"></div>
<h4><a href="#Tag1">Tag1</a></h4>
<ul>
<li><a href="#getRandomNumber"><code><span class="http-method">get</span> /random</code></a></li>
</ul>
<h1><a name="Tag1">Tag1</a></h1>
<div class="method"><a name="getRandomNumber"/>
<div class="method-path">
<a class="up" href="#__Methods">Up</a>
<pre class="get"><code class="huge"><span class="http-method">get</span> /random</code></pre></div>
<div class="method-summary">A single <em>random</em> result (<span class="nickname">getRandomNumber</span>)</div>
<div class="method-notes">Return a single <em>random</em> result from a given seed</div>
<h3 class="field-label">Query parameters</h3>
<div class="field-items">
<div class="param">seed (required)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; A random number <em>seed</em>. </div>
</div> <!-- field-items -->
<h3 class="field-label">Return type</h3>
<div class="return-type">
<a href="#RandomNumber">RandomNumber</a>
</div>
<!--Todo: process Response Object and its headers, schema, examples -->
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"sequence" : 1,
"seed" : 6.027456183070403,
"value" : 0.8008281904610115
}</code></pre>
<h3 class="field-label">Responses</h3>
<h4 class="field-label">200</h4>
Operation <em>succeded</em>
<a href="#RandomNumber">RandomNumber</a>
<h4 class="field-label">404</h4>
Invalid or omitted <em>seed</em>. Seeds must be <strong>valid</strong> numbers.
<a href="#"></a>
</div> <!-- method -->
<hr/>
<h2><a name="__Models">Models</a></h2>
[ Jump to <a href="#__Methods">Methods</a> ]
<h3>Table of Contents</h3>
<ol>
<li><a href="#RandomNumber"><code>RandomNumber</code> - <em>Pseudo-random</em> number</a></li>
</ol>
<div class="model">
<h3><a name="RandomNumber"><code>RandomNumber</code> - <em>Pseudo-random</em> number</a> <a class="up" href="#__Models">Up</a></h3>
<div class='model-description'>A <em>pseudo-random</em> number generated from a seed.</div>
<div class="field-items">
<div class="param">value (optional)</div><div class="param-desc"><span class="param-type"><a href="#double">Double</a></span> The <em>pseudo-random</em> number format: double</div>
<div class="param">seed (optional)</div><div class="param-desc"><span class="param-type"><a href="#double">Double</a></span> The <code>seed</code> used to generate this number format: double</div>
<div class="param">sequence (optional)</div><div class="param-desc"><span class="param-type"><a href="#long">Long</a></span> The sequence number of this random number. format: int64</div>
</div> <!-- field-items -->
</div>
</body>
</html>

View File

@ -180,8 +180,8 @@ font-style: italic;
</head>
<body>
<h1>Swagger Petstore</h1>
<div class="app-desc">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.</div>
<div class="app-desc">This is a sample server Petstore server. You can find out more about Swagger at <a href="http://swagger.io">http://swagger.io</a> or on <a href="http://swagger.io/irc/">irc.freenode.net, #swagger</a>. For this sample, you can use the api key <code>special-key</code> to test the authorization filters.</div>
<div class="app-desc">More information: <a href=""></a></div>
<div class="app-desc">Contact Info: <a href="apiteam@swagger.io">apiteam@swagger.io</a></div>
<div class="app-desc">Version: 1.0.0</div>
<div class="app-desc">BasePath:/v2</div>
@ -356,18 +356,18 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>[ {
"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"
} ]</code></pre>
<h3 class="field-label">Produces</h3>
@ -429,18 +429,18 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>[ {
"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"
} ]</code></pre>
<h3 class="field-label">Produces</h3>
@ -502,18 +502,18 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"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"
}</code></pre>
<h3 class="field-label">Produces</h3>
@ -679,9 +679,9 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"message" : "aeiou",
"code" : 3,
"type" : "aeiou"
"code" : 0,
"type" : "aeiou",
"message" : "aeiou"
}</code></pre>
<h3 class="field-label">Produces</h3>
@ -762,7 +762,7 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"key" : 9
"key" : 0
}</code></pre>
<h3 class="field-label">Produces</h3>
@ -783,7 +783,7 @@ font-style: italic;
<a class="up" href="#__Methods">Up</a>
<pre class="get"><code class="huge"><span class="http-method">get</span> /store/order/{orderId}</code></pre></div>
<div class="method-summary">Find purchase order by ID (<span class="nickname">getOrderById</span>)</div>
<div class="method-notes">For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generated exceptions</div>
<div class="method-notes">For valid response try integer IDs with value &lt;= 5 or &gt; 10. Other values will generated exceptions</div>
<h3 class="field-label">Path parameters</h3>
<div class="field-items">
@ -818,12 +818,12 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"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"
}</code></pre>
<h3 class="field-label">Produces</h3>
@ -887,12 +887,12 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"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"
}</code></pre>
<h3 class="field-label">Produces</h3>
@ -1109,14 +1109,14 @@ font-style: italic;
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"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"
}</code></pre>
<h3 class="field-label">Produces</h3>