diff --git a/spring-restdocs-asciidoctor/build.gradle b/spring-restdocs-asciidoctor/build.gradle index 6897242e..ab311ad5 100644 --- a/spring-restdocs-asciidoctor/build.gradle +++ b/spring-restdocs-asciidoctor/build.gradle @@ -5,3 +5,12 @@ dependencies { testCompile 'junit:junit' testCompile 'org.asciidoctor:asciidoctorj' } + +task copyTestSnippets(type: Copy) { + from 'src/test/resources/some-operation' + into 'build/generated-snippets/some-operation' +} + +test { + dependsOn copyTestSnippets +} diff --git a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java index 15bca154..6d2be185 100644 --- a/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java +++ b/spring-restdocs-asciidoctor/src/main/java/org/springframework/restdocs/asciidoctor/RestDocsExtensionRegistry.java @@ -17,6 +17,8 @@ package org.springframework.restdocs.asciidoctor; import org.asciidoctor.Asciidoctor; +import org.asciidoctor.extension.JavaExtensionRegistry; +import org.asciidoctor.extension.RubyExtensionRegistry; import org.asciidoctor.extension.spi.ExtensionRegistry; /** @@ -28,8 +30,15 @@ public final class RestDocsExtensionRegistry implements ExtensionRegistry { @Override public void register(Asciidoctor asciidoctor) { - asciidoctor.javaExtensionRegistry() - .preprocessor(new DefaultAttributesPreprocessor()); + JavaExtensionRegistry registry = asciidoctor.javaExtensionRegistry(); + registry.preprocessor(new DefaultAttributesPreprocessor()); + + RubyExtensionRegistry rubyExtensionRegistry = asciidoctor.rubyExtensionRegistry(); + rubyExtensionRegistry + .loadClass(RestDocsExtensionRegistry.class + .getResourceAsStream("/extensions/operation_block_macro.rb")) + .blockMacro("operation", "OperationBlockMacro"); + } } diff --git a/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb b/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb new file mode 100644 index 00000000..a38fcbc2 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb @@ -0,0 +1,115 @@ +require 'asciidoctor/extensions' +require 'stringio' + +# Spring REST Docs block macro to import multiple snippet of an operation at once +# +# Usage +# +# operation::operation-name[snippets='snippet-name1,snippet-name2', level=] +# +class OperationBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor + use_dsl + named :operation + + def initialize name, config + super + # pre-defined section titles for commonly used snippets + @titles = {:'http-request' => 'HTTP request', + :'curl-request' => 'curl request', + :'httpie-request' => 'HTTPie request', + :'request-body' => 'Request body', + :'request-fields' => 'Request fields', + :'http-response' => 'HTTP response', + :'response-body' => 'Response body', + :'response-fields' => 'Response fields', + :'links' => 'Links' + } + end + + def process(parent, reader, attrs) + doc = parent.document + snippet_dir = doc.attributes['snippets'] + snippets = snippets_to_include(attrs, snippet_dir, reader) + section_level = parent.level + 1 + + params = {:snippet_dir => snippet_dir, + :section_level => section_level, + :operation => reader} + + content = StringIO.new + snippets.each do |snippet| + append_snippet_block(content, snippet, params) + end + + add_snippets_block(content, doc, parent) unless content.length == 0 + nil + end + + def add_snippets_block(content, doc, parent) + fragment = Asciidoctor.load content, + safe: doc.options[:safe], + attributes: {'fragment' => '', 'projectdir' => doc.attr(:projectdir)} + fragment.blocks.each do |b| + b.parent = parent + parent << b + end + end + + def snippets_to_include(attrs, snippet_dir, operation) + if not attrs['snippets'].to_s.empty? + snippets_from_attribute attrs + else + all_snippets snippet_dir, operation + end + end + + def snippets_from_attribute(attrs) + attrs.fetch('snippets').split(',') + end + + def all_snippets(snippet_dir, operation) + all_snippet_file_names = [] + Dir.entries(File.join(snippet_dir.to_s, operation)).sort.select { |file| + if file.end_with? '.adoc' + file.slice!('.adoc') + all_snippet_file_names << file + end + } + all_snippet_file_names + end + + def append_snippet_block(content, snippet, params) + write_title content, snippet, params[:section_level] + write_content content, snippet, params + end + + def write_content(content, snippet, params) + snippet_path = File.join(params[:snippet_dir].to_s, params[:operation], "#{snippet}.adoc") + content.puts File.readlines(snippet_path).join + + rescue Errno::ENOENT + content.puts "WARNING: snippet not found: #{snippet_path}" + add_new_line content + end + + def write_title(content, snippet, level) + # an asciidoctor level is always an equal + # sign more than the level number + section_level = '=' * (level + 1) + content.puts "#{section_level} #{title(snippet)}" + add_new_line content + end + + def title(snippet) + (@titles[snippet.to_sym] || title_from_file_name(snippet)) + end + + def add_new_line(content) + content.puts '' + end + + def title_from_file_name(snippet) + snippet.sub('-', ' ').capitalize + end + +end diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationIncludeBlockMacroTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationIncludeBlockMacroTests.java new file mode 100644 index 00000000..0d952dba --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationIncludeBlockMacroTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014-2017 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.asciidoctor; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.Attributes; +import org.asciidoctor.Options; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; + +/** + * Tests for ruby based rest docs block macro. + * Because there is no java implementation (yet) + * we can only test the behaviour when rendering. + * + * @author Gerrit Meier + */ +public class OperationIncludeBlockMacroTests { + + private final Options options = new Options(); + private final Asciidoctor asciidoctor = Asciidoctor.Factory.create(); + + @Before + public void setUp() { + this.options.setAttributes(getAttributes()); + } + + private Attributes getAttributes() { + Attributes attributes = new Attributes(); + attributes.setAttribute("projectdir", new File(".").getAbsolutePath()); + return attributes; + } + + @Test + public void simpleSnippetInclude() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[snippets='curl-request']", this.options); + + assertThat(result, equalTo(getExpectedContentFromFile("snippet_simple"))); + } + + @Test + public void includeSnippetInSection() throws Exception { + String result = this.asciidoctor.convert( + "== Section\n" + + "operation::some-operation[snippets='curl-request']", this.options); + + assertThat(result, equalTo(getExpectedContentFromFile("snippet_in_section"))); + } + + @Test + public void includeMultipleSnippets() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[snippets='curl-request,http-request']", this.options); + + assertThat(result, equalTo(getExpectedContentFromFile("multiple_snippets"))); + } + + @Test + public void useMacroWithoutSnippetAttributeAddsAllSnippets() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[]", this.options); + + assertThat(result, equalTo(getExpectedContentFromFile("all_snippets"))); + } + + @Test + public void useMacroWithEmptySnippetAttributeAddsAllSnippets() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[snippets=]", this.options); + + assertThat(result, equalTo(getExpectedContentFromFile("all_snippets"))); + } + + @Test + public void includingUnknownSnippetAddsWarning() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[snippets='unknown-snippet']", this.options); + + assertThat(result, startsWith(getExpectedContentFromFile("snippet_warning"))); + } + + @Test + public void includingCustomSnippetCreatesCustomTitle() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[snippets='custom-snippet']", this.options); + + assertThat(result, containsString(getExpectedContentFromFile("snippet_custom_title"))); + } + + private String getExpectedContentFromFile(String fileName) throws URISyntaxException, IOException { + Path filePath = Paths.get(this.getClass().getResource("/operations/" + fileName + ".html").toURI()); + return new String(Files.readAllBytes(filePath)); + } +} diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/all_snippets.html b/spring-restdocs-asciidoctor/src/test/resources/operations/all_snippets.html new file mode 100644 index 00000000..7ecff6a0 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/all_snippets.html @@ -0,0 +1,31 @@ +
+

curl request

+
+
+
+
$ curl 'http://localhost:8080/' -i
+
+
+
+
+
+

Custom snippet

+
+
+
+
mycustomsnippet
+
+
+
+
+
+

HTTP request

+
+
+
+
GET / HTTP/1.1
+Host: localhost:8080
+
+
+
+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/multiple_snippets.html b/spring-restdocs-asciidoctor/src/test/resources/operations/multiple_snippets.html new file mode 100644 index 00000000..b8c018da --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/multiple_snippets.html @@ -0,0 +1,21 @@ +
+

curl request

+
+
+
+
$ curl 'http://localhost:8080/' -i
+
+
+
+
+
+

HTTP request

+
+
+
+
GET / HTTP/1.1
+Host: localhost:8080
+
+
+
+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_custom_title.html b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_custom_title.html new file mode 100644 index 00000000..132a95ce --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_custom_title.html @@ -0,0 +1 @@ +

Custom snippet

diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_in_section.html b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_in_section.html new file mode 100644 index 00000000..fe3ce739 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_in_section.html @@ -0,0 +1,13 @@ +
+

Section

+
+
+

curl request

+
+
+
$ curl 'http://localhost:8080/' -i
+
+
+
+
+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_simple.html b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_simple.html new file mode 100644 index 00000000..222c30a2 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_simple.html @@ -0,0 +1,10 @@ +
+

curl request

+
+
+
+
$ curl 'http://localhost:8080/' -i
+
+
+
+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_warning.html b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_warning.html new file mode 100644 index 00000000..1058ad72 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_warning.html @@ -0,0 +1,11 @@ +
+

Unknown snippet

+
+
+ + + +
+
Warning
+
+snippet not found: \ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_with_level.html b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_with_level.html new file mode 100644 index 00000000..4a393cda --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet_with_level.html @@ -0,0 +1,8 @@ +
+

curl request

+
+
+
$ curl 'http://localhost:8080/' -i
+
+
+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/some-operation/curl-request.adoc b/spring-restdocs-asciidoctor/src/test/resources/some-operation/curl-request.adoc new file mode 100644 index 00000000..0183405f --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/some-operation/curl-request.adoc @@ -0,0 +1,4 @@ +[source,bash] +---- +$ curl 'http://localhost:8080/' -i +---- \ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/some-operation/custom-snippet.adoc b/spring-restdocs-asciidoctor/src/test/resources/some-operation/custom-snippet.adoc new file mode 100644 index 00000000..4a27b9ba --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/some-operation/custom-snippet.adoc @@ -0,0 +1,4 @@ +[source,http,options="nowrap"] +---- +mycustomsnippet +---- \ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/some-operation/http-request.adoc b/spring-restdocs-asciidoctor/src/test/resources/some-operation/http-request.adoc new file mode 100644 index 00000000..2034fb60 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/some-operation/http-request.adoc @@ -0,0 +1,6 @@ +[source,http,options="nowrap"] +---- +GET / HTTP/1.1 +Host: localhost:8080 + +---- \ No newline at end of file