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:
Andy Wilkinson
2016-01-29 16:10:00 +00:00
parent b34d2dd443
commit 4d37dea1d6
99 changed files with 2100 additions and 1037 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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/&#123;name&#125;.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");

View File

@@ -0,0 +1,3 @@
```bash
$ curl {{url}} {{options}}
```

View File

@@ -0,0 +1,7 @@
```http
{{method}} {{path}} HTTP/1.1
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{requestBody}}
```

View File

@@ -0,0 +1,7 @@
```http
HTTP/1.1 {{statusCode}} {{statusReason}}
{{#headers}}
{{name}}: {{value}}
{{/headers}}
{{responseBody}}
```

View File

@@ -0,0 +1,5 @@
Relation | Description
-------- | -----------
{{#links}}
{{rel}} | {{description}}
{{/links}}

View File

@@ -0,0 +1,6 @@
{{path}}
Parameter | Description
--------- | -----------
{{#parameters}}
{{name}} | {{description}}
{{/parameters}}

View File

@@ -0,0 +1,5 @@
Path | Type | Description
---- | ---- | -----------
{{#fields}}
{{path}} | {{type}} | {{description}}
{{/fields}}

View File

@@ -0,0 +1,5 @@
Name | Description
---- | -----------
{{#headers}}
{{name}} | {{description}}
{{/headers}}

View File

@@ -0,0 +1,5 @@
Parameter | Description
--------- | -----------
{{#parameters}}
{{name}} | {{description}}
{{/parameters}}

View File

@@ -0,0 +1,5 @@
Path | Type | Description
---- | ---- | -----------
{{#fields}}
{{path}} | {{type}} | {{description}}
{{/fields}}

View File

@@ -0,0 +1,5 @@
Name | Description
---- | -----------
{{#headers}}
{{name}} | {{description}}
{{/headers}}