Add support for generating snippets in Markdown
This commit introduces support for generating snippets formatted using Markdown. Asciidoctor remains the default. A new SnippetFormat abstraction has been introduced with Asciidoctor and Markdown implementations provided out of the box. Markdown-formatted templates are also provided for all of the default snippets. Please refer to the updated reference documentation for further details. Closes gh-150 Closes gh-19
This commit is contained in:
@@ -93,13 +93,20 @@ public abstract class RestDocumentationConfigurer<S, T> {
|
||||
|
||||
private static final class TemplateEngineConfigurer extends AbstractConfigurer {
|
||||
|
||||
private TemplateEngine templateEngine = new MustacheTemplateEngine(
|
||||
new StandardTemplateResourceResolver());
|
||||
private TemplateEngine templateEngine;
|
||||
|
||||
@Override
|
||||
public void apply(Map<String, Object> configuration,
|
||||
RestDocumentationContext context) {
|
||||
configuration.put(TemplateEngine.class.getName(), this.templateEngine);
|
||||
TemplateEngine engineToUse = this.templateEngine;
|
||||
if (engineToUse == null) {
|
||||
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
|
||||
.get(SnippetConfiguration.class.getName());
|
||||
engineToUse = new MustacheTemplateEngine(
|
||||
new StandardTemplateResourceResolver(
|
||||
snippetConfiguration.getFormat()));
|
||||
}
|
||||
configuration.put(TemplateEngine.class.getName(), engineToUse);
|
||||
}
|
||||
|
||||
private void setTemplateEngine(TemplateEngine templateEngine) {
|
||||
@@ -117,8 +124,12 @@ public abstract class RestDocumentationConfigurer<S, T> {
|
||||
RestDocumentationContext context) {
|
||||
WriterResolver resolverToUse = this.writerResolver;
|
||||
if (resolverToUse == null) {
|
||||
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
|
||||
.get(SnippetConfiguration.class.getName());
|
||||
resolverToUse = new StandardWriterResolver(
|
||||
new RestDocumentationContextPlaceholderResolver(context));
|
||||
new RestDocumentationContextPlaceholderResolver(context),
|
||||
snippetConfiguration.getEncoding(),
|
||||
snippetConfiguration.getFormat());
|
||||
}
|
||||
configuration.put(WriterResolver.class.getName(), resolverToUse);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.config;
|
||||
|
||||
import org.springframework.restdocs.snippet.SnippetFormat;
|
||||
|
||||
/**
|
||||
* An encapsulation of the configuration for documentation snippets.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class SnippetConfiguration {
|
||||
|
||||
private final String encoding;
|
||||
|
||||
private final SnippetFormat format;
|
||||
|
||||
SnippetConfiguration(String encoding, SnippetFormat format) {
|
||||
this.encoding = encoding;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
String getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
SnippetFormat getFormat() {
|
||||
return this.format;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,8 @@ import org.springframework.restdocs.RestDocumentationContext;
|
||||
import org.springframework.restdocs.curl.CurlDocumentation;
|
||||
import org.springframework.restdocs.http.HttpDocumentation;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.WriterResolver;
|
||||
import org.springframework.restdocs.snippet.SnippetFormat;
|
||||
import org.springframework.restdocs.snippet.SnippetFormats;
|
||||
|
||||
/**
|
||||
* A configurer that can be used to configure the generated documentation snippets.
|
||||
@@ -51,8 +52,18 @@ public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P
|
||||
*/
|
||||
public static final String DEFAULT_SNIPPET_ENCODING = "UTF-8";
|
||||
|
||||
/**
|
||||
* The default format for documentation snippets.
|
||||
*
|
||||
* @see #withFormat(SnippetFormat)
|
||||
*/
|
||||
public static final SnippetFormat DEFAULT_SNIPPET_FORMAT = SnippetFormats
|
||||
.asciidoctor();
|
||||
|
||||
private String snippetEncoding = DEFAULT_SNIPPET_ENCODING;
|
||||
|
||||
private SnippetFormat snippetFormat = DEFAULT_SNIPPET_FORMAT;
|
||||
|
||||
/**
|
||||
* Creates a new {@code SnippetConfigurer} with the given {@code parent}.
|
||||
*
|
||||
@@ -64,8 +75,8 @@ public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P
|
||||
|
||||
@Override
|
||||
public void apply(Map<String, Object> configuration, RestDocumentationContext context) {
|
||||
((WriterResolver) configuration.get(WriterResolver.class.getName()))
|
||||
.setEncoding(this.snippetEncoding);
|
||||
configuration.put(SnippetConfiguration.class.getName(), new SnippetConfiguration(
|
||||
this.snippetEncoding, this.snippetFormat));
|
||||
configuration.put(ATTRIBUTE_DEFAULT_SNIPPETS, this.defaultSnippets);
|
||||
}
|
||||
|
||||
@@ -93,4 +104,17 @@ public abstract class SnippetConfigurer<P, T> extends AbstractNestedConfigurer<P
|
||||
this.defaultSnippets = Arrays.asList(defaultSnippets);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the format of the documentation snippets.
|
||||
*
|
||||
* @param format the snippet format
|
||||
* @return {@code this}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withFormat(SnippetFormat format) {
|
||||
this.snippetFormat = format;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.snippet;
|
||||
|
||||
/**
|
||||
* A {@link SnippetFormat} provides information about a particular snippet format, such as
|
||||
* Asciidoctor or Markdown.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public interface SnippetFormat {
|
||||
|
||||
/**
|
||||
* Returns the snippet format's file extension.
|
||||
*
|
||||
* @return the file extension
|
||||
*/
|
||||
String getFileExtension();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.snippet;
|
||||
|
||||
/**
|
||||
* An enumeration of the built-in snippet formats.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class SnippetFormats {
|
||||
|
||||
private static final SnippetFormat ASCIIDOCTOR = new AsciidoctorSnippetFormat();
|
||||
|
||||
private static final SnippetFormat MARKDOWN = new MarkdownSnippetFormat();
|
||||
|
||||
private SnippetFormats() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Asciidoctor snippet format.
|
||||
*
|
||||
* @return the snippet format
|
||||
*/
|
||||
public static SnippetFormat asciidoctor() {
|
||||
return ASCIIDOCTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Markdown snippet format.
|
||||
*
|
||||
* @return the snippet format
|
||||
*/
|
||||
public static SnippetFormat markdown() {
|
||||
return MARKDOWN;
|
||||
}
|
||||
|
||||
private static final class AsciidoctorSnippetFormat implements SnippetFormat {
|
||||
|
||||
private static final String FILE_EXTENSION = "adoc";
|
||||
|
||||
@Override
|
||||
public String getFileExtension() {
|
||||
return FILE_EXTENSION;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class MarkdownSnippetFormat implements SnippetFormat {
|
||||
|
||||
private static final String FILE_EXTENSION = "md";
|
||||
|
||||
@Override
|
||||
public String getFileExtension() {
|
||||
return FILE_EXTENSION;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,29 +33,54 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
|
||||
*/
|
||||
public final class StandardWriterResolver implements WriterResolver {
|
||||
|
||||
private String encoding = "UTF-8";
|
||||
|
||||
private final PlaceholderResolver placeholderResolver;
|
||||
|
||||
private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper(
|
||||
"{", "}");
|
||||
|
||||
private String encoding = "UTF-8";
|
||||
|
||||
private SnippetFormat snippetFormat;
|
||||
|
||||
/**
|
||||
* Creates a new {@code StandardWriterResolver} that will use the given
|
||||
* {@code placeholderResolver} to resolve any placeholders in the
|
||||
* {@code operationName}.
|
||||
* {@code operationName}. Writers will use {@code UTF-8} encoding and, when writing to
|
||||
* a file, will use a filename appropriate for Asciidoctor content.
|
||||
*
|
||||
* @param placeholderResolver the placeholder resolver
|
||||
* @deprecated since 1.1.0 in favor of
|
||||
* {@link #StandardWriterResolver(PropertyPlaceholderHelper.PlaceholderResolver, String, SnippetFormat)}
|
||||
*/
|
||||
@Deprecated
|
||||
public StandardWriterResolver(PlaceholderResolver placeholderResolver) {
|
||||
this(placeholderResolver, "UTF-8", SnippetFormats.asciidoctor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code StandardWriterResolver} that will use the given
|
||||
* {@code placeholderResolver} to resolve any placeholders in the
|
||||
* {@code operationName}. Writers will use the given {@code encoding} and, when
|
||||
* writing to a file, will use a filename appropriate for content in the given
|
||||
* {@code snippetFormat}.
|
||||
*
|
||||
* @param placeholderResolver the placeholder resolver
|
||||
* @param encoding the encoding
|
||||
* @param snippetFormat the snippet format
|
||||
*/
|
||||
public StandardWriterResolver(PlaceholderResolver placeholderResolver,
|
||||
String encoding, SnippetFormat snippetFormat) {
|
||||
this.placeholderResolver = placeholderResolver;
|
||||
this.encoding = encoding;
|
||||
this.snippetFormat = snippetFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer resolve(String operationName, String snippetName,
|
||||
RestDocumentationContext context) throws IOException {
|
||||
File outputFile = resolveFile(this.propertyPlaceholderHelper.replacePlaceholders(
|
||||
operationName, this.placeholderResolver), snippetName + ".adoc", context);
|
||||
operationName, this.placeholderResolver), snippetName + "."
|
||||
+ this.snippetFormat.getFileExtension(), context);
|
||||
|
||||
if (outputFile != null) {
|
||||
createDirectoriesIfNecessary(outputFile);
|
||||
@@ -95,4 +120,5 @@ public final class StandardWriterResolver implements WriterResolver {
|
||||
throw new IllegalStateException("Failed to create directory '" + parent + "'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,7 +47,10 @@ public interface WriterResolver {
|
||||
* resolver.
|
||||
*
|
||||
* @param encoding the encoding
|
||||
* @deprecated since 1.1.0 in favour of configuring the encoding when to resolver is
|
||||
* created
|
||||
*/
|
||||
@Deprecated
|
||||
void setEncoding(String encoding);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,27 +18,54 @@ package org.springframework.restdocs.templates;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.restdocs.snippet.SnippetFormat;
|
||||
import org.springframework.restdocs.snippet.SnippetFormats;
|
||||
|
||||
/**
|
||||
* Standard implementation of {@link TemplateResourceResolver}.
|
||||
* <p>
|
||||
* Templates are resolved by first looking for a resource on the classpath named
|
||||
* Templates are resolved by looking for a resource on the classpath named
|
||||
* {@code org/springframework/restdocs/templates/{name}.snippet}. If no such
|
||||
* resource exists {@code default-} is prepended to the name and the classpath is checked
|
||||
* again. The built-in snippet templates are all named {@code default- name}, thereby
|
||||
* allowing them to be overridden.
|
||||
* resource exists an attempt is made to return a default resource that is appropriate for
|
||||
* the configured snippet format.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class StandardTemplateResourceResolver implements TemplateResourceResolver {
|
||||
|
||||
private final SnippetFormat snippetFormat;
|
||||
|
||||
/**
|
||||
* Creates a new {@code StandardTemplateResourceResolver} that will produce default
|
||||
* template resources formatted with Asciidoctor.
|
||||
*
|
||||
* @deprecated since 1.1.0 in favour of
|
||||
* {@link #StandardTemplateResourceResolver(SnippetFormat)}
|
||||
*/
|
||||
@Deprecated
|
||||
public StandardTemplateResourceResolver() {
|
||||
this(SnippetFormats.asciidoctor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code StandardTemplateResourceResolver} that will produce default
|
||||
* template resources formatted with the given {@code snippetFormat}.
|
||||
*
|
||||
* @param snippetFormat the format for the default snippet templates
|
||||
*/
|
||||
public StandardTemplateResourceResolver(SnippetFormat snippetFormat) {
|
||||
this.snippetFormat = snippetFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolveTemplateResource(String name) {
|
||||
ClassPathResource classPathResource = new ClassPathResource(
|
||||
"org/springframework/restdocs/templates/" + name + ".snippet");
|
||||
if (!classPathResource.exists()) {
|
||||
classPathResource = new ClassPathResource(
|
||||
"org/springframework/restdocs/templates/default-" + name + ".snippet");
|
||||
"org/springframework/restdocs/templates/"
|
||||
+ this.snippetFormat.getFileExtension() + "/" + name
|
||||
+ ".snippet");
|
||||
if (!classPathResource.exists()) {
|
||||
throw new IllegalStateException("Template named '" + name
|
||||
+ "' could not be resolved");
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
```bash
|
||||
$ curl {{url}} {{options}}
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
```http
|
||||
{{method}} {{path}} HTTP/1.1
|
||||
{{#headers}}
|
||||
{{name}}: {{value}}
|
||||
{{/headers}}
|
||||
{{requestBody}}
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
```http
|
||||
HTTP/1.1 {{statusCode}} {{statusReason}}
|
||||
{{#headers}}
|
||||
{{name}}: {{value}}
|
||||
{{/headers}}
|
||||
{{responseBody}}
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
Relation | Description
|
||||
-------- | -----------
|
||||
{{#links}}
|
||||
{{rel}} | {{description}}
|
||||
{{/links}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{path}}
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
{{#parameters}}
|
||||
{{name}} | {{description}}
|
||||
{{/parameters}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Path | Type | Description
|
||||
---- | ---- | -----------
|
||||
{{#fields}}
|
||||
{{path}} | {{type}} | {{description}}
|
||||
{{/fields}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Name | Description
|
||||
---- | -----------
|
||||
{{#headers}}
|
||||
{{name}} | {{description}}
|
||||
{{/headers}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Parameter | Description
|
||||
--------- | -----------
|
||||
{{#parameters}}
|
||||
{{name}} | {{description}}
|
||||
{{/parameters}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Path | Type | Description
|
||||
---- | ---- | -----------
|
||||
{{#fields}}
|
||||
{{path}} | {{type}} | {{description}}
|
||||
{{/fields}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Name | Description
|
||||
---- | -----------
|
||||
{{#headers}}
|
||||
{{name}} | {{description}}
|
||||
{{/headers}}
|
||||
Reference in New Issue
Block a user