diff --git a/README.asciidoc b/README.asciidoc
index cfe420091b13b..05d39b54ec29f 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -1014,12 +1014,26 @@ footnote to a particular line of code:
=== View in Console
Code blocks can be followed by a "View in Console" link which, when clicked,
-will open the code snippet in Console. The snippet can either be taken directly
-from the code block (`CONSOLE`), or be a link to a custom snippet.
+will open the code snippet in Console. There are two ways to do this, the
+"AsciiDoc" way and the "Asciidoctor" way. The "AsciiDoc" way is preferred in
+the Elaticsearch repository because it can recognize it to make tests. The
+"Asciidoctor" way is preferred in other books, but only if they are built with
+"Asciidoctor". Try it first and if it works then use it. Otherwise, use the
+"AsciiDoc" way.
-.Code block with CONSOLE link
+////
+
+The tricks that we pull to make ascidoctor support // CONSOLE are windy
+and force us to add subs=+macros when we render an asciidoc snippet.
+We *don't* require that for normal snippets, just those that contain
+asciidoc.
+
+////
+
+.Code block with CONSOLE link (AsciiDoc way)
==================================
-[source,asciidoc]
+ifdef::asciidoctor[[source,asciidoc,subs=+macros]]
+ifndef::asciidoctor[[source,asciidoc]]
--
[source,js]
----------------------------------
@@ -1035,6 +1049,24 @@ GET /_search
==================================
<1> The `// CONSOLE` line must follow immediately after the code block, before any callouts.
+.Code block with CONSOLE link (Asciidoctor way)
+==================================
+[source,asciidoc]
+--
+[source,console]
+----------------------------------
+GET /_search
+{
+ "query": "foo bar" \<1>
+}
+----------------------------------
+
+\<1> Here's the explanation
+--
+==================================
+
+Both render as:
+
[source,js]
----------------------------------
GET /_search
@@ -1060,38 +1092,6 @@ The local web browser can be stopped with `Ctrl-C`.
================================
-==== Custom Console snippets
-
-Sometimes you will want to show a small amount of code in the code block, but
-to provide a full recreation in the Console snippet. In this case, you need to:
-
-* Save the snippet file in the `./snippets/` directory in the root docs directory.
-* Under the code block, specify the name of the snippet file with
-+
- // CONSOLE: path/to/snippet.json
-
-For instance, to add a custom snippet to the file `./one/two/three.asciidoc`, save the snippet
-to `./snippets/one/two/three/example_1.json`, then add the `CONSOLE` link below the code block:
-
-.Code block with custom CONSOLE link
-==================================
-[source,asciidoc]
---
-[source,js]
-----------------------------------
-GET /_search
-{
- "query": "foo bar" \<1>
-}
-----------------------------------
-// CONSOLE:one/two/three/example_1.json <1>
-
-\<1> Here's the explanation
---
-<1> The path should not contain the initial `snippets` directory
-==================================
-
-
[[admon-blocks]]
=== Admonition blocks
diff --git a/integtest/Makefile b/integtest/Makefile
index 508dd0c34446e..5b2b56684209c 100644
--- a/integtest/Makefile
+++ b/integtest/Makefile
@@ -63,14 +63,15 @@ experimental_expected_files: /tmp/experimental_asciidoc
%_same_files: /tmp/%_asciidoc /tmp/%_asciidoctor
diff \
<(cd /tmp/$*_asciidoc && find * -type f | sort \
- | grep -v snippets/blocks \
+ | grep -v 'snippets/' \
) \
- <(cd /tmp/$*_asciidoctor && find * -type f | sort)
- # The grep -v below are for known issues with asciidoctor
- for file in $$(cd /tmp/$*_asciidoc && find * -type f -name '*.html' \
- | grep -v 'blocks'); do \
+ <(cd /tmp/$*_asciidoctor && find * -type f | sort \
+ | grep -v 'snippets/' \
+ )
+ for file in $$(cd /tmp/$*_asciidoc && find * -type f -name '*.html'); do \
./html_diff /tmp/$*_asciidoc/$$file /tmp/$*_asciidoctor/$$file; \
done
+ # TODO validate the snippets have the same contents even if the files aren't the same
# Build the docs into the target
define BD=
diff --git a/integtest/html_diff b/integtest/html_diff
index bbf8f24285454..d1b0f939076b8 100755
--- a/integtest/html_diff
+++ b/integtest/html_diff
@@ -35,10 +35,13 @@ def normalize_html(html):
# is between words. They are actively nice to have but asciidoc doesn't
# make them.
html = html.replace('\u2014\u200b', '\u2014')
- # Temporary workaround for known issues
- html = re.sub(
- r'(?m)^\s+
'
- r'\s+
\n', '', html)
+ # We intentionally changed lang-js to lang-console because in Asciidoctor
+ # because that is more accurate
+ html = html.replace('"programlisting prettyprint lang-js"',
+ '"programlisting prettyprint lang-console"')
+ # The URL for the console snippets has changed
+ html = re.sub(r'data-snippet="[^"]+"', 'data-snippet="snippet"', html)
+ # Temporary work around for known issue
html = html.replace('\\<1>', '<1>')
return html
diff --git a/lib/ES/Template.pm b/lib/ES/Template.pm
index 98e40b363d89a..4caf4bd838b25 100644
--- a/lib/ES/Template.pm
+++ b/lib/ES/Template.pm
@@ -47,6 +47,7 @@ sub apply {
my $self = shift;
my $dir = shift;
my $lang = shift || die "No lang specified";
+ my $asciidoctor = shift;
my $map = $self->_map;
@@ -61,7 +62,7 @@ sub apply {
$contents =~ s/\s*$/\n/;
# Extract AUTOSENSE snippets
- $contents = $self->_autosense_snippets( $file, $contents );
+ $contents = $self->_autosense_snippets( $file, $contents ) unless $asciidoctor;
# Fill in template
my @parts = @{ $self->_parts };
diff --git a/lib/ES/Util.pm b/lib/ES/Util.pm
index 68d39c6e0f4a4..be1962b7a0c3a 100644
--- a/lib/ES/Util.pm
+++ b/lib/ES/Util.pm
@@ -154,7 +154,7 @@ sub build_chunked {
my ($chunk_dir) = grep { -d and /\.chunked$/ } $dest->children
or die "Couldn't find chunk dir in <$dest>";
- finish_build( $index->parent, $chunk_dir, $lang );
+ finish_build( $index->parent, $chunk_dir, $lang, $asciidoctor );
extract_toc_from_index($chunk_dir);
for ( $chunk_dir->children ) {
run( 'mv', $_, $dest );
@@ -287,7 +287,7 @@ sub build_single {
or die "Couldn't rename <$src> to : $!";
}
- finish_build( $index->parent, $dest, $lang );
+ finish_build( $index->parent, $dest, $lang, $asciidoctor );
}
#===================================
@@ -369,10 +369,10 @@ sub build_pdf {
#===================================
sub finish_build {
#===================================
- my ( $source, $dest, $lang ) = @_;
+ my ( $source, $dest, $lang, $asciidoctor ) = @_;
# Apply template to HTML files
- $Opts->{template}->apply( $dest, $lang );
+ $Opts->{template}->apply( $dest, $lang, $asciidoctor );
my $snippets_dest = $dest->subdir('snippets');
my $snippets_src;
diff --git a/resources/asciidoctor/lib/elastic_compat_preprocessor/extension.rb b/resources/asciidoctor/lib/elastic_compat_preprocessor/extension.rb
index c859a694fe9f8..e101e5831062e 100644
--- a/resources/asciidoctor/lib/elastic_compat_preprocessor/extension.rb
+++ b/resources/asciidoctor/lib/elastic_compat_preprocessor/extension.rb
@@ -89,12 +89,31 @@
# Because Asciidoc permits these mismatches but asciidoctor does not. We'll
# emit a warning because, permitted or not, they are bad style.
#
+# With the help of ElasticCompatTreeProcessor turns
+# [source,js]
+# ----
+# foo
+# ----
+# // CONSOLE
+#
+# Into
+# [source,console]
+# ----
+# foo
+# ----
+# Because Elastic has thousands of these constructs but Asciidoctor feels
+# strongly that comments should not convey meaning. This is a totally
+# reasonable stance and we should migrate away from these comments in new
+# docs when it is possible. But for now we have to support the comments as
+# well.
+#
class ElasticCompatPreprocessor < Asciidoctor::Extensions::Preprocessor
include Asciidoctor::Logging
INCLUDE_TAGGED_DIRECTIVE_RX = /^include-tagged::([^\[][^\[]*)\[(#{Asciidoctor::CC_ANY}+)?\]$/.freeze
SOURCE_WITH_SUBS_RX = /^\["source", ?"[^"]+", ?subs="(#{Asciidoctor::CC_ANY}+)"\]$/.freeze
CODE_BLOCK_RX = /^-----*$/.freeze
+ SNIPPET_RX = %r{//\s*(?:AUTOSENSE|KIBANA|CONSOLE|SENSE:[^\n<]+)}.freeze
def process(_document, reader)
reader.instance_variable_set :@in_attribute_only_block, false
@@ -142,6 +161,7 @@ def reader.process_line(line)
@code_block_start = line
end
end
+
supported = 'added|beta|coming|deprecated|experimental'
# First convert the "block" version of these macros. We convert them
# to block macros because they are at the start of the line....
@@ -149,6 +169,14 @@ def reader.process_line(line)
# Then convert the "inline" version of these macros. We convert them
# to inline macros because they are *not* at the start of the line....
line&.gsub!(/(#{supported})\[([^\]]*)\]/, '\1:[\2]')
+
+ # Transform Elastic's traditional comment based marking for
+ # AUTOSENSE/KIBANA/CONSOLE snippets into a marker that we can pick
+ # up during tree processing to turn the snippet into a marked up
+ # CONSOLE snippet. Asciidoctor really doesn't recommend this sort of
+ # thing but we have thousands of them and it'll take us some time to
+ # stop doing it.
+ line&.gsub!(SNIPPET_RX, 'pass:[\0]')
end
end
reader
diff --git a/resources/asciidoctor/lib/elastic_compat_tree_processor/extension.rb b/resources/asciidoctor/lib/elastic_compat_tree_processor/extension.rb
index 5cc4785426836..70c3017b53880 100644
--- a/resources/asciidoctor/lib/elastic_compat_tree_processor/extension.rb
+++ b/resources/asciidoctor/lib/elastic_compat_tree_processor/extension.rb
@@ -23,14 +23,62 @@
# <1> The count of categories that were matched
# <2> The categories retrieved
#
+# Turns
+# [source,js]
+# --------------------------------------------------
+# GET / <1>
+# --------------------------------------------------
+# pass:[// CONSOLE]
+# <1> The count of categories that were matched
+# <2> The categories retrieved
+#
+# Into
+# [source,console]
+# --------------------------------------------------
+# GET / <1>
+# --------------------------------------------------
+# <1> The count of categories that were matched
+# <2> The categories retrieved
+#
class ElasticCompatTreeProcessor < TreeProcessorScaffold
+ include Asciidoctor::Logging
+
def process_block(block)
- if block.context == :listing && block.style == "source" &&
- block.subs.include?(:specialcharacters) == false
- # callouts have to come *after* special characters
- had_callouts = block.subs.delete(:callouts)
- block.subs << :specialcharacters
- block.subs << :callouts if had_callouts
- end
+ return unless block.context == :listing && block.style == 'source'
+
+ process_subs block
+ process_lang_override block
+ end
+
+ def process_subs(block)
+ return if block.subs.include? :specialcharacters
+
+ # callouts have to come *after* special characters
+ had_callouts = block.subs.delete(:callouts)
+ block.subs << :specialcharacters
+ block.subs << :callouts if had_callouts
+ end
+
+ LANG_MAPPING = {
+ 'AUTOSENSE' => 'sense',
+ 'CONSOLE' => 'console',
+ 'KIBANA' => 'kibana',
+ 'SENSE' => 'sense',
+ }.freeze
+
+ def process_lang_override(block)
+ next_block = block.next_adjacent_block
+ return unless next_block && next_block.context == :paragraph
+ return unless next_block.source =~ %r{pass:\[//\s*([^:\]]+)(?::\s*([^\]]+))?\]}
+
+ lang = LANG_MAPPING[$1]
+ snippet = $2
+ return unless lang # Not a language we handle
+
+ block.set_attr 'language', lang
+ block.set_attr 'snippet', snippet
+
+ block.parent.blocks.delete next_block
+ block.parent.reindex_sections
end
end
diff --git a/resources/asciidoctor/lib/extensions.rb b/resources/asciidoctor/lib/extensions.rb
index d175aa3feb83b..2636161689d39 100644
--- a/resources/asciidoctor/lib/extensions.rb
+++ b/resources/asciidoctor/lib/extensions.rb
@@ -8,6 +8,7 @@
require_relative 'elastic_compat_tree_processor/extension'
require_relative 'elastic_compat_preprocessor/extension'
require_relative 'elastic_include_tagged/extension'
+require_relative 'open_in_widget/extension'
Asciidoctor::Extensions.register CareAdmonition
Asciidoctor::Extensions.register ChangeAdmonition
@@ -20,5 +21,6 @@
treeprocessor CopyImages::CopyImages
treeprocessor EditMe
treeprocessor ElasticCompatTreeProcessor
+ treeprocessor OpenInWidget
include_processor ElasticIncludeTagged
end
diff --git a/resources/asciidoctor/lib/open_in_widget/extension.rb b/resources/asciidoctor/lib/open_in_widget/extension.rb
new file mode 100644
index 0000000000000..cd5e676e0253f
--- /dev/null
+++ b/resources/asciidoctor/lib/open_in_widget/extension.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'fileutils'
+
+require_relative '../scaffold.rb'
+
+##
+# Extensions for enriching certain source blocks with "OPEN IN CONSOLE",
+# "OPEN IN SENSE", "OPEN IN KIBANA", AND/OR "COPY_AS_CURL".
+#
+# Usage
+#
+# [source,console]
+# ---------
+# GET /
+# ---------
+#
+# or
+#
+# [source,sense]
+# ---------
+# GET /
+# ---------
+#
+# or
+#
+# [source,kibana]
+# ---------
+# GET /
+# ---------
+#
+# or
+#
+# [source,sense,snippet=path/to/snippet.console]
+# ---------
+# GET /
+# ---------
+#
+class OpenInWidget < TreeProcessorScaffold
+ include Asciidoctor::Logging
+
+ CALLOUT_SCAN_RX = / ?#{Asciidoctor::CalloutScanRx}/.freeze
+
+ def process_block(block)
+ return unless block.context == :listing && block.style == 'source'
+
+ lang = block.attr 'language'
+ return unless %w[console sense kibana].include? lang
+
+ snippet = block.attr 'snippet'
+ if snippet
+ # If you specify the snippet path then we should copy it into the
+ # destination directory so it is available for Kibana.
+ snippet_path = "snippets/#{snippet}"
+ normalized = block.normalize_system_path(snippet_path, block.document.base_dir)
+ if File.readable? normalized
+ copy_snippet block, normalized, snippet_path
+ logger.warn message_with_context "reading snippets from a path makes the book harder to read", :source_location => block.source_location
+ else
+ logger.error message_with_context "can't read snippet from #{normalized}", :source_location => block.source_location
+ end
+ else
+ # If you don't specify the snippet then we assign it a number and read
+ # the contents of the source listing, copying it to the destination
+ # directory so it is available for Kibana.
+ snippet_number = block.document.attr 'snippet_number', 1
+ snippet = "#{snippet_number}.#{lang}"
+ block.document.set_attr 'snippet_number', snippet_number + 1
+
+ snippet_path = "snippets/#{snippet}"
+ source = block.source.gsub(CALLOUT_SCAN_RX, '') + "\n"
+ write_snippet block, source, snippet_path
+ end
+ block.set_attr 'snippet_link', ""
+ block.document.register :links, snippet_path
+
+ def block.content
+ "#{@attributes['snippet_link']}#{super}"
+ end
+ end
+
+ def copy_snippet(block, source, uri)
+ logger.info message_with_context "copying snippet #{source}", :source_location => block.source_location
+ copy_proc = block.document.attr 'copy_snippet'
+ if copy_proc
+ # Delegate to a proc for copying if one is defined. Used for testing.
+ copy_proc.call(uri, source)
+ else
+ destination = ::File.join block.document.options[:to_dir], uri
+ destination_dir = ::File.dirname destination
+ FileUtils.mkdir_p destination_dir
+ FileUtils.cp source, destination
+ end
+ end
+
+ def write_snippet(block, snippet, uri)
+ logger.info message_with_context "writing snippet #{uri}", :source_location => block.source_location
+ write_proc = block.document.attr 'write_snippet'
+ if write_proc
+ # Delegate to a proc for copying if one is defined. Used for testing.
+ write_proc.call(uri, snippet)
+ else
+ destination = ::File.join block.document.options[:to_dir], uri
+ destination_dir = ::File.dirname destination
+ FileUtils.mkdir_p destination_dir
+ File.open(destination, 'w') { |file| file.write(snippet) }
+ end
+ end
+end
diff --git a/resources/asciidoctor/spec/elastic_compat_preprocessor_spec.rb b/resources/asciidoctor/spec/elastic_compat_preprocessor_spec.rb
index 8fc2f3c5a5c72..a752a1577bf57 100644
--- a/resources/asciidoctor/spec/elastic_compat_preprocessor_spec.rb
+++ b/resources/asciidoctor/spec/elastic_compat_preprocessor_spec.rb
@@ -3,7 +3,9 @@
require 'care_admonition/extension'
require 'change_admonition/extension'
require 'elastic_compat_preprocessor/extension'
+require 'elastic_compat_tree_processor/extension'
require 'elastic_include_tagged/extension'
+require 'open_in_widget/extension'
require 'shared_examples/does_not_break_line_numbers'
RSpec.describe ElasticCompatPreprocessor do
@@ -13,6 +15,8 @@
Asciidoctor::Extensions.register do
preprocessor ElasticCompatPreprocessor
include_processor ElasticIncludeTagged
+ treeprocessor ElasticCompatTreeProcessor
+ treeprocessor OpenInWidget
end
end
@@ -20,6 +24,8 @@
Asciidoctor::Extensions.unregister_all
end
+ spec_dir = File.dirname(__FILE__)
+
include_examples "doesn't break line numbers"
[
@@ -262,9 +268,9 @@
Example
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.zip
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{version}.zip.sha512
- shasum -a 512 -c elasticsearch-{version}.zip.sha512 <1>
+ shasum -a 512 -c elasticsearch-{version}.zip.sha512
unzip elasticsearch-{version}.zip
- cd elasticsearch-{version}/ <2>
+ cd elasticsearch-{version}/
Compares the SHA of the downloaded .zip archive and the published checksum, which should output
@@ -364,4 +370,61 @@
DOCBOOK
expect(actual).to eq(expected.strip)
end
+
+ def stub_file_opts
+ return {
+ 'copy_snippet' => proc { |uri, source| },
+ 'write_snippet' => proc { |uri, source| },
+ }
+ end
+
+ [
+ %w[CONSOLE console],
+ %w[AUTOSENSE sense],
+ %w[KIBANA kibana],
+ ].each do |name, lang|
+ it "transforms #{name} comments into a listing with the #{lang} language" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,js]
+ ----
+ foo
+ ----
+ // #{name}
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ foo
+
+ DOCBOOK
+ actual = convert input, stub_file_opts, eq(
+ "INFO: : line 3: writing snippet snippets/1.#{lang}"
+ )
+ expect(actual).to eq(expected.strip)
+ end
+ end
+
+ it "transforms SENSE comments into a listing with the SENSE language and a path" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,js]
+ ----
+ foo
+ ----
+ // SENSE: snippet.sense
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ foo
+
+ DOCBOOK
+ warnings = <<~WARNINGS
+ INFO: : line 3: copying snippet #{spec_dir}/snippets/snippet.sense
+ WARN: : line 3: reading snippets from a path makes the book harder to read
+ WARNINGS
+ actual = convert input, stub_file_opts, eq(warnings.strip)
+ expect(actual).to eq(expected.strip)
+end
end
diff --git a/resources/asciidoctor/spec/elastic_compat_tree_processor_spec.rb b/resources/asciidoctor/spec/elastic_compat_tree_processor_spec.rb
index adf30c136cb24..04ba3a3d23b6c 100644
--- a/resources/asciidoctor/spec/elastic_compat_tree_processor_spec.rb
+++ b/resources/asciidoctor/spec/elastic_compat_tree_processor_spec.rb
@@ -71,4 +71,29 @@
DOCBOOK
expect(actual).to eq(expected.strip)
end
+
+ [
+ %w[CONSOLE console],
+ %w[AUTOSENSE sense],
+ %w[KIBANA kibana],
+ %w[SENSE:path/to/snippet.sense sense],
+ ].each do |command, lang|
+ it "transforms legacy // #{command} commands into the #{lang} language" do
+ actual = convert <<~ASCIIDOC
+ == Example
+ [source,js]
+ ----
+ GET /
+ ----
+ pass:[// #{command}]
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ GET /
+
+ DOCBOOK
+ expect(actual).to eq(expected.strip)
+ end
+ end
end
diff --git a/resources/asciidoctor/spec/open_in_widget_spec.rb b/resources/asciidoctor/spec/open_in_widget_spec.rb
new file mode 100644
index 0000000000000..35765652677b3
--- /dev/null
+++ b/resources/asciidoctor/spec/open_in_widget_spec.rb
@@ -0,0 +1,183 @@
+# frozen_string_literal: true
+
+require 'open_in_widget/extension'
+
+RSpec.describe OpenInWidget do
+ before(:each) do
+ Asciidoctor::Extensions.register do
+ treeprocessor OpenInWidget
+ end
+ end
+
+ after(:each) do
+ Asciidoctor::Extensions.unregister_all
+ end
+
+ spec_dir = File.dirname(__FILE__)
+
+ def stub_file_opts(result)
+ return {
+ 'copy_snippet' => proc { |uri, source| result << [uri, source] },
+ 'write_snippet' => proc { |uri, snippet| result << [uri, snippet] },
+ }
+ end
+
+ %w[console sense kibana].each do |lang|
+ it "supports automatic snippet extraction with #{lang} language" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,#{lang}]
+ ----
+ GET /
+ ----
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ GET /
+
+ DOCBOOK
+ file_opts = []
+ actual = convert input, stub_file_opts(file_opts), eq(
+ "INFO: : line 3: writing snippet snippets/1.#{lang}"
+ )
+ expect(actual).to eq(expected.strip)
+ expect(file_opts).to eq([
+ ["snippets/1.#{lang}", "GET /\n"],
+ ])
+ end
+
+ it "supports automatic snippet extraction for many snippets with #{lang} language" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,#{lang}]
+ ----
+ GET /
+ ----
+
+ [source,#{lang}]
+ ----
+ GET /
+ ----
+
+ [source,#{lang}]
+ ----
+ GET /
+ ----
+
+ [source,#{lang}]
+ ----
+ GET /
+ ----
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ GET /
+ GET /
+ GET /
+ GET /
+
+ DOCBOOK
+ file_opts = []
+ warnings = <<~WARNINGS
+ INFO: : line 3: writing snippet snippets/1.#{lang}
+ INFO: : line 8: writing snippet snippets/2.#{lang}
+ INFO: : line 13: writing snippet snippets/3.#{lang}
+ INFO: : line 18: writing snippet snippets/4.#{lang}
+ WARNINGS
+ actual = convert input, stub_file_opts(file_opts), eq(warnings.strip)
+ expect(actual).to eq(expected.strip)
+ expect(file_opts).to eq([
+ ["snippets/1.#{lang}", "GET /\n"],
+ ["snippets/2.#{lang}", "GET /\n"],
+ ["snippets/3.#{lang}", "GET /\n"],
+ ["snippets/4.#{lang}", "GET /\n"],
+ ])
+ end
+
+ it "supports override snippet path with #{lang} language" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,#{lang},snippet=snippet.#{lang}]
+ ----
+ GET /
+ ----
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ GET /
+
+ DOCBOOK
+ warnings = <<~WARNINGS
+ INFO: : line 3: copying snippet #{spec_dir}/snippets/snippet.#{lang}
+ WARN: : line 3: reading snippets from a path makes the book harder to read
+ WARNINGS
+ file_opts = []
+ actual = convert input, stub_file_opts(file_opts), eq(warnings.strip)
+ expect(actual).to eq(expected.strip)
+ expect(file_opts).to eq([
+ ["snippets/snippet.#{lang}", "#{spec_dir}/snippets/snippet.#{lang}"],
+ ])
+ end
+
+ it "logs an error if override snippet is missing with #{lang} language" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,#{lang},snippet=missing.#{lang}]
+ ----
+ GET /
+ ----
+ ASCIIDOC
+ warnings = <<~WARNINGS
+ ERROR: : line 3: can't read snippet from #{spec_dir}/snippets/missing.#{lang}
+ WARNINGS
+ file_opts = []
+ convert input, stub_file_opts(file_opts), eq(warnings.strip)
+ expect(file_opts).to eq([])
+ end
+ end
+
+ it "strips callouts from written snippets" do
+ input = <<~ASCIIDOC
+ == Example
+ [source,console]
+ ----
+ GET / <1>
+
+ POST /foo/_doc/1
+ {
+ "f1": "v1" <2>
+ }
+ ----
+ ASCIIDOC
+ expected = <<~DOCBOOK
+
+ Example
+ GET /
+
+ POST /foo/_doc/1
+ {
+ "f1": "v1"
+ }
+
+ DOCBOOK
+ file_opts = []
+ actual = convert input, stub_file_opts(file_opts), eq(
+ "INFO: : line 3: writing snippet snippets/1.console"
+ )
+ expect(actual).to eq(expected.strip)
+ expected_snippet_body = <<~SNIPPET
+ GET /
+
+ POST /foo/_doc/1
+ {
+ "f1": "v1"
+ }
+ SNIPPET
+ expect(file_opts).to eq([
+ ["snippets/1.console", expected_snippet_body],
+ ])
+ end
+end
diff --git a/resources/asciidoctor/spec/snippets/snippet.console b/resources/asciidoctor/spec/snippets/snippet.console
new file mode 100644
index 0000000000000..c6ee45096e44b
--- /dev/null
+++ b/resources/asciidoctor/spec/snippets/snippet.console
@@ -0,0 +1 @@
+GET /
diff --git a/resources/asciidoctor/spec/snippets/snippet.kibana b/resources/asciidoctor/spec/snippets/snippet.kibana
new file mode 100644
index 0000000000000..06faf0330fc0d
--- /dev/null
+++ b/resources/asciidoctor/spec/snippets/snippet.kibana
@@ -0,0 +1 @@
+GET /api/security/role/my_kibana_role
diff --git a/resources/asciidoctor/spec/snippets/snippet.sense b/resources/asciidoctor/spec/snippets/snippet.sense
new file mode 100644
index 0000000000000..c6ee45096e44b
--- /dev/null
+++ b/resources/asciidoctor/spec/snippets/snippet.sense
@@ -0,0 +1 @@
+GET /
diff --git a/resources/website_common.xsl b/resources/website_common.xsl
index 1be0bbc7dad46..f549e2adc3993 100644
--- a/resources/website_common.xsl
+++ b/resources/website_common.xsl
@@ -134,8 +134,19 @@
-
+
+
+
+
+
+
+
+
+
+
@@ -331,7 +342,7 @@
-
+
@@ -353,6 +364,7 @@
+