asciidoctor-doctest 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.adoc +0 -0
- data/LICENSE +21 -0
- data/README.adoc +327 -0
- data/Rakefile +12 -0
- data/data/examples/asciidoc/block_admonition.adoc +27 -0
- data/data/examples/asciidoc/block_audio.adoc +13 -0
- data/data/examples/asciidoc/block_colist.adoc +46 -0
- data/data/examples/asciidoc/block_dlist.adoc +99 -0
- data/data/examples/asciidoc/block_example.adoc +21 -0
- data/data/examples/asciidoc/block_floating_title.adoc +27 -0
- data/data/examples/asciidoc/block_image.adoc +28 -0
- data/data/examples/asciidoc/block_listing.adoc +68 -0
- data/data/examples/asciidoc/block_literal.adoc +30 -0
- data/data/examples/asciidoc/block_olist.adoc +55 -0
- data/data/examples/asciidoc/block_open.adoc +40 -0
- data/data/examples/asciidoc/block_outline.adoc +60 -0
- data/data/examples/asciidoc/block_page_break.adoc +6 -0
- data/data/examples/asciidoc/block_paragraph.adoc +17 -0
- data/data/examples/asciidoc/block_pass.adoc +5 -0
- data/data/examples/asciidoc/block_preamble.adoc +19 -0
- data/data/examples/asciidoc/block_quote.adoc +30 -0
- data/data/examples/asciidoc/block_sidebar.adoc +22 -0
- data/data/examples/asciidoc/block_stem.adoc +28 -0
- data/data/examples/asciidoc/block_table.adoc +168 -0
- data/data/examples/asciidoc/block_thematic_break.adoc +2 -0
- data/data/examples/asciidoc/block_toc.adoc +50 -0
- data/data/examples/asciidoc/block_ulist.adoc +43 -0
- data/data/examples/asciidoc/block_verse.adoc +37 -0
- data/data/examples/asciidoc/block_video.adoc +24 -0
- data/data/examples/asciidoc/document.adoc +51 -0
- data/data/examples/asciidoc/embedded.adoc +10 -0
- data/data/examples/asciidoc/inline_anchor.adoc +27 -0
- data/data/examples/asciidoc/inline_break.adoc +8 -0
- data/data/examples/asciidoc/inline_button.adoc +3 -0
- data/data/examples/asciidoc/inline_callout.adoc +5 -0
- data/data/examples/asciidoc/inline_footnote.adoc +9 -0
- data/data/examples/asciidoc/inline_image.adoc +44 -0
- data/data/examples/asciidoc/inline_kbd.adoc +7 -0
- data/data/examples/asciidoc/inline_menu.adoc +11 -0
- data/data/examples/asciidoc/inline_quoted.adoc +59 -0
- data/data/examples/asciidoc/section.adoc +74 -0
- data/doc/img/doctest-diag.odf +0 -0
- data/doc/img/doctest-diag.svg +56 -0
- data/doc/img/failing-test-term.gif +0 -0
- data/lib/asciidoctor-doctest.rb +1 -0
- data/lib/asciidoctor/doctest.rb +30 -0
- data/lib/asciidoctor/doctest/asciidoc/examples_suite.rb +44 -0
- data/lib/asciidoctor/doctest/asciidoc_renderer.rb +103 -0
- data/lib/asciidoctor/doctest/base_example.rb +161 -0
- data/lib/asciidoctor/doctest/base_examples_suite.rb +188 -0
- data/lib/asciidoctor/doctest/core_ext.rb +49 -0
- data/lib/asciidoctor/doctest/generator.rb +63 -0
- data/lib/asciidoctor/doctest/generator_task.rb +111 -0
- data/lib/asciidoctor/doctest/html/example.rb +21 -0
- data/lib/asciidoctor/doctest/html/examples_suite.rb +111 -0
- data/lib/asciidoctor/doctest/html/html_beautifier.rb +17 -0
- data/lib/asciidoctor/doctest/html/normalizer.rb +118 -0
- data/lib/asciidoctor/doctest/minitest_diffy.rb +74 -0
- data/lib/asciidoctor/doctest/test.rb +120 -0
- data/lib/asciidoctor/doctest/version.rb +5 -0
- data/spec/asciidoc/examples_suite_spec.rb +99 -0
- data/spec/base_example_spec.rb +176 -0
- data/spec/core_ext_spec.rb +67 -0
- data/spec/html/examples_suite_spec.rb +249 -0
- data/spec/html/normalizer_spec.rb +70 -0
- data/spec/shared_examples/base_examples_suite.rb +262 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/matchers.rb +7 -0
- data/spec/test_spec.rb +164 -0
- metadata +360 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Enumerable
|
2
|
+
|
3
|
+
##
|
4
|
+
# Sends a message to each element and collects the result.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# [1, 2, 3].map_send(:+, 3) #=> [4, 5, 6]
|
8
|
+
#
|
9
|
+
# @param method_name [Symbol] name of the public method to call.
|
10
|
+
# @param args arguments to pass to the method.
|
11
|
+
# @param block [Proc] block to pass to the method.
|
12
|
+
# @return [Enumerable]
|
13
|
+
#
|
14
|
+
def map_send(method_name, *args, &block)
|
15
|
+
map { |e| e.public_send(method_name, *args, &block) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Module
|
20
|
+
|
21
|
+
##
|
22
|
+
# Makes +new_name+ a new copy of the class method +old_name+.
|
23
|
+
#
|
24
|
+
# @param new_name [Symbol] name of the new class method to create.
|
25
|
+
# @param old_name [Symbol] name of the existing class method to alias.
|
26
|
+
#
|
27
|
+
def alias_class_method(new_name, old_name)
|
28
|
+
singleton_class.send(:alias_method, new_name, old_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class String
|
33
|
+
|
34
|
+
##
|
35
|
+
# Appends (concatenates) the given object to +str+.
|
36
|
+
#
|
37
|
+
# @param obj [String, Integer] the string, or codepoint to append.
|
38
|
+
# @param separator [String, nil] the separator to append when this +str+ is
|
39
|
+
# not empty.
|
40
|
+
# @return [String] self
|
41
|
+
#
|
42
|
+
def concat(obj, separator = nil)
|
43
|
+
if separator && !self.empty?
|
44
|
+
self << separator << obj
|
45
|
+
else
|
46
|
+
self << obj
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'asciidoctor'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
module Asciidoctor
|
5
|
+
module DocTest
|
6
|
+
module Generator
|
7
|
+
|
8
|
+
##
|
9
|
+
# Generates missing, or rewrite existing output examples from the
|
10
|
+
# input examples converted using the +renderer+.
|
11
|
+
#
|
12
|
+
# @param output_suite [BaseExamplesSuite] an instance of
|
13
|
+
# {BaseExamplesSuite} subclass to read and generate the output
|
14
|
+
# examples.
|
15
|
+
#
|
16
|
+
# @param input_suite [BaseExamplesSuite] an instance of
|
17
|
+
# {BaseExamplesSuite} subclass to read the reference input
|
18
|
+
# examples.
|
19
|
+
#
|
20
|
+
# @param renderer [#render]
|
21
|
+
#
|
22
|
+
# @param pattern [String] glob-like pattern to select examples to
|
23
|
+
# (re)generate (see {BaseExample#name_match?}).
|
24
|
+
#
|
25
|
+
# @param rewrite [Boolean] whether to rewrite an already existing
|
26
|
+
# example.
|
27
|
+
#
|
28
|
+
# @param log_os [#<<] output stream where to write log messages.
|
29
|
+
#
|
30
|
+
def self.generate!(output_suite, input_suite, renderer, pattern: '*:*',
|
31
|
+
rewrite: false, log_os: $stdout)
|
32
|
+
updated = []
|
33
|
+
|
34
|
+
input_suite.pair_with(output_suite).each do |input, output|
|
35
|
+
next unless input.name_match? pattern
|
36
|
+
|
37
|
+
log = ->(msg, color = :default) do
|
38
|
+
log_os << " --> #{(msg % input.name).colorize(color)}\n" if log_os
|
39
|
+
end
|
40
|
+
|
41
|
+
if input.empty?
|
42
|
+
log["Unknown %s, doesn't exist in input examples!"]
|
43
|
+
else
|
44
|
+
rendered = output_suite.convert_example(input, output.opts, renderer)
|
45
|
+
if output.empty?
|
46
|
+
log['Generating %s', :magenta]
|
47
|
+
updated << rendered
|
48
|
+
elsif rendered == output
|
49
|
+
log['Unchanged %s', :green]
|
50
|
+
elsif rewrite
|
51
|
+
log['Rewriting %s', :red]
|
52
|
+
updated << rendered
|
53
|
+
else
|
54
|
+
log['Skipping %s', :yellow]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
output_suite.update_examples updated unless updated.empty?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'active_support/core_ext/string/strip'
|
2
|
+
require 'asciidoctor/doctest/generator'
|
3
|
+
require 'asciidoctor/doctest/core_ext'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
|
6
|
+
module Asciidoctor
|
7
|
+
module DocTest
|
8
|
+
##
|
9
|
+
# Rake task for generating output examples.
|
10
|
+
# @see Generator
|
11
|
+
class GeneratorTask < Rake::TaskLib
|
12
|
+
|
13
|
+
# List of values representing +true+.
|
14
|
+
TRUE_VALUES = %w[yes y true]
|
15
|
+
|
16
|
+
# This attribute is used only for the default {#input_suite}.
|
17
|
+
# @return (see DocTest.examples_path)
|
18
|
+
attr_accessor :examples_path
|
19
|
+
|
20
|
+
# @return [Boolean] whether to rewrite an already existing testing
|
21
|
+
# example. May be overriden with +FORCE+ variable on the command line
|
22
|
+
# (default: false).
|
23
|
+
attr_accessor :force
|
24
|
+
|
25
|
+
# @return [BaseExamplesSuite] an instance of {BaseExamplesSuite} subclass
|
26
|
+
# to read the reference input examples
|
27
|
+
# (default: +Asciidoc::ExamplesSuite.new(examples_path: examples_path)+).
|
28
|
+
attr_accessor :input_suite
|
29
|
+
|
30
|
+
# @return [BaseExamplesSuite] an instance of {BaseExamplesSuite} subclass
|
31
|
+
# to read and generate the output examples.
|
32
|
+
attr_accessor :output_suite
|
33
|
+
|
34
|
+
# @return [#to_sym] name of the task.
|
35
|
+
attr_accessor :name
|
36
|
+
|
37
|
+
# @return [String] glob pattern to select examples to (re)generate.
|
38
|
+
# May be overriden with +PATTERN+ variable on the command line
|
39
|
+
# (default: *:*).
|
40
|
+
attr_accessor :pattern
|
41
|
+
|
42
|
+
# @return [Hash] options for Asciidoctor renderer.
|
43
|
+
# @see AsciidocRenderer#initialize
|
44
|
+
attr_accessor :renderer_opts
|
45
|
+
|
46
|
+
# @return [String] title of the task's description.
|
47
|
+
attr_accessor :title
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
# @param name [#to_sym] name of the task.
|
52
|
+
# @yield The block to configure this task.
|
53
|
+
def initialize(name)
|
54
|
+
@name = name
|
55
|
+
@examples_path = DocTest.examples_path
|
56
|
+
@force = false
|
57
|
+
@input_suite = nil
|
58
|
+
@output_suite = nil
|
59
|
+
@renderer_opts = {}
|
60
|
+
@pattern = '*:*'
|
61
|
+
@title = "Generate testing examples #{pattern}#{" for #{name}" if name != :generate}."
|
62
|
+
|
63
|
+
yield self
|
64
|
+
|
65
|
+
fail 'The output_suite is not provided!' unless @output_suite
|
66
|
+
if @output_suite.examples_path.first == DocTest::BUILTIN_EXAMPLES_PATH
|
67
|
+
fail "The examples_path in output suite is invalid: #{@output_suite.examples_path}"
|
68
|
+
end
|
69
|
+
|
70
|
+
@input_suite ||= Asciidoc::ExamplesSuite.new(examples_path: @examples_path)
|
71
|
+
@renderer ||= AsciidocRenderer.new(renderer_opts)
|
72
|
+
|
73
|
+
define
|
74
|
+
end
|
75
|
+
|
76
|
+
def pattern
|
77
|
+
ENV['PATTERN'] || @pattern
|
78
|
+
end
|
79
|
+
|
80
|
+
def force?
|
81
|
+
return TRUE_VALUES.include?(ENV['FORCE'].downcase) if ENV.key? 'FORCE'
|
82
|
+
!!force
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def define
|
88
|
+
desc description
|
89
|
+
|
90
|
+
task name.to_sym do
|
91
|
+
puts title
|
92
|
+
Generator.generate! output_suite, input_suite, @renderer,
|
93
|
+
pattern: pattern, rewrite: force?
|
94
|
+
end
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def description
|
99
|
+
<<-EOS.strip_heredoc
|
100
|
+
#{title}
|
101
|
+
|
102
|
+
Options (environment variables):
|
103
|
+
PATTERN glob pattern to select examples to (re)generate. [default: #{@pattern}]
|
104
|
+
E.g. *:*, block_toc:basic, block*:*, *list:with*, ...
|
105
|
+
FORCE overwrite existing examples (yes/no)? [default: #{@force ? 'yes' : 'no'}]
|
106
|
+
|
107
|
+
EOS
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'asciidoctor/doctest/base_example'
|
2
|
+
require 'asciidoctor/doctest/html/html_beautifier'
|
3
|
+
require 'asciidoctor/doctest/html/normalizer'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module Asciidoctor::DocTest
|
7
|
+
module HTML
|
8
|
+
##
|
9
|
+
# Subclass of {BaseExample} for HTML-based backends.
|
10
|
+
class Example < BaseExample
|
11
|
+
|
12
|
+
def content_normalized
|
13
|
+
Nokogiri::HTML.fragment(content).normalize!.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
HtmlBeautifier.beautify content_normalized
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'asciidoctor/doctest/base_examples_suite'
|
4
|
+
require 'asciidoctor/doctest/core_ext'
|
5
|
+
require 'asciidoctor/doctest/html/example'
|
6
|
+
require 'asciidoctor/doctest/html/normalizer'
|
7
|
+
require 'nokogiri'
|
8
|
+
|
9
|
+
module Asciidoctor::DocTest
|
10
|
+
module HTML
|
11
|
+
##
|
12
|
+
# Subclass of {BaseExamplesSuite} for HTML-based backends.
|
13
|
+
#
|
14
|
+
# @example Format of the example's header
|
15
|
+
# <!-- .example-name
|
16
|
+
# Any text that is not the example's name or an option and doesn't
|
17
|
+
# start with // is considered as a description.
|
18
|
+
# :option-1: value 1
|
19
|
+
# :option-2: value 1
|
20
|
+
# :option-2: value 2
|
21
|
+
# :boolean-option:
|
22
|
+
# -->
|
23
|
+
# <p>The example's content in <strong>HTML</strong>.</p>
|
24
|
+
#
|
25
|
+
# <div class="note">The trailing new line (below this) will be removed.</div>
|
26
|
+
#
|
27
|
+
class ExamplesSuite < BaseExamplesSuite
|
28
|
+
|
29
|
+
def initialize(file_ext: '.html', paragraph_xpath: './p/node()', **kwargs)
|
30
|
+
super file_ext: file_ext, **kwargs
|
31
|
+
@paragraph_xpath = paragraph_xpath
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse(input, group_name)
|
35
|
+
examples = []
|
36
|
+
current = create_example(nil)
|
37
|
+
in_comment = false
|
38
|
+
|
39
|
+
input.each_line do |line|
|
40
|
+
line.chomp!
|
41
|
+
if line =~ /^<!--\s*\.([^ \n]+)/
|
42
|
+
name = $1
|
43
|
+
current.content.chomp!
|
44
|
+
examples << (current = create_example([group_name, name]))
|
45
|
+
in_comment = true
|
46
|
+
elsif in_comment
|
47
|
+
if line =~ /^\s*:([^:]+):(.*)/
|
48
|
+
current[$1.to_sym] = $2.blank? ? true : $2.strip
|
49
|
+
elsif !line.start_with?('//')
|
50
|
+
desc = line.rstrip.chomp('-->').strip
|
51
|
+
(current.desc ||= '').concat(desc, "\n") unless desc.empty?
|
52
|
+
end
|
53
|
+
else
|
54
|
+
current.content.concat(line, "\n")
|
55
|
+
end
|
56
|
+
in_comment &= !line.end_with?('-->')
|
57
|
+
end
|
58
|
+
|
59
|
+
examples
|
60
|
+
end
|
61
|
+
|
62
|
+
def serialize(examples)
|
63
|
+
Array.wrap(examples).map { |exmpl|
|
64
|
+
header = [ ".#{exmpl.local_name}", exmpl.desc.presence ].compact
|
65
|
+
|
66
|
+
exmpl.opts.each do |name, vals|
|
67
|
+
Array.wrap(vals).each do |val|
|
68
|
+
header << (val == true ? ":#{name}:" : ":#{name}: #{val}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
header_str = header.one? ? (header.first + ' ') : (header.join("\n") + "\n")
|
72
|
+
|
73
|
+
[ "<!-- #{header_str}-->", exmpl.content.presence ].compact.join("\n") + "\n"
|
74
|
+
}.join("\n")
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_example(*args)
|
78
|
+
Example.new(*args)
|
79
|
+
end
|
80
|
+
|
81
|
+
def convert_example(example, opts, renderer)
|
82
|
+
header_footer = !!opts[:header_footer] || example.name.start_with?('document')
|
83
|
+
|
84
|
+
html = renderer.render(example.to_s, header_footer: header_footer)
|
85
|
+
html = parse_html(html, !header_footer)
|
86
|
+
|
87
|
+
# When asserting inline examples, ignore paragraph "wrapper".
|
88
|
+
includes = opts[:include] || (@paragraph_xpath if example.name.start_with? 'inline_')
|
89
|
+
|
90
|
+
Array.wrap(includes).each do |xpath|
|
91
|
+
# XPath returns NodeSet, but we need DocumentFragment, so convert it again.
|
92
|
+
html = parse_html(html.xpath(xpath).to_html)
|
93
|
+
end
|
94
|
+
|
95
|
+
Array.wrap(opts[:exclude]).each do |xpath|
|
96
|
+
html.xpath(xpath).remove
|
97
|
+
end
|
98
|
+
|
99
|
+
html.normalize!
|
100
|
+
|
101
|
+
create_example example.name, content: html.to_s, opts: opts
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def parse_html(str, fragment = true)
|
107
|
+
fragment ? ::Nokogiri::HTML.fragment(str) : ::Nokogiri::HTML.parse(str)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'htmlbeautifier'
|
2
|
+
|
3
|
+
module HtmlBeautifier
|
4
|
+
|
5
|
+
##
|
6
|
+
# Beautifies the +input+ HTML.
|
7
|
+
#
|
8
|
+
# @param input [String, #to_html]
|
9
|
+
# @return [String] a beautified copy of the +input+.
|
10
|
+
#
|
11
|
+
def self.beautify(input)
|
12
|
+
input = input.to_html unless input.is_a? String
|
13
|
+
output = []
|
14
|
+
Beautifier.new(output).scan(input)
|
15
|
+
output.join
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'active_support/core_ext/object/try'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Asciidoctor::DocTest
|
5
|
+
module HTML
|
6
|
+
##
|
7
|
+
# Module to be included into +Nokogiri::HTML::Document+
|
8
|
+
# or +DocumentFragment+ to add {#normalize!} feature.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# Nokogiri::HTML.parse(str).normalize!
|
12
|
+
# Nokogiri::HTML.fragment(str).normalize!
|
13
|
+
module Normalizer
|
14
|
+
|
15
|
+
##
|
16
|
+
# Normalizes the HTML document or fragment so it can be easily compared
|
17
|
+
# with another HTML.
|
18
|
+
#
|
19
|
+
# What does it actually do?
|
20
|
+
#
|
21
|
+
# * sorts element attributes by name
|
22
|
+
# * sorts inline CSS declarations inside a +style+ attribute by name
|
23
|
+
# * removes all blank text nodes (i.e. node that contain just whitespaces)
|
24
|
+
# * strips nonsignificant leading and trailing whitespaces around text
|
25
|
+
# * strips nonsignificant repeated whitespaces
|
26
|
+
#
|
27
|
+
# @return [Object] self
|
28
|
+
#
|
29
|
+
def normalize!
|
30
|
+
traverse do |node|
|
31
|
+
case node.type
|
32
|
+
|
33
|
+
when Nokogiri::XML::Node::ELEMENT_NODE
|
34
|
+
sort_element_attrs! node
|
35
|
+
sort_element_style_attr! node
|
36
|
+
|
37
|
+
when Nokogiri::XML::Node::TEXT_NODE
|
38
|
+
# Remove text node that contains whitespaces only.
|
39
|
+
if node.blank?
|
40
|
+
node.remove
|
41
|
+
|
42
|
+
elsif !preformatted_block? node
|
43
|
+
strip_redundant_spaces! node
|
44
|
+
strip_spaces_around_text! node
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Sorts attributes of the element +node+ by name.
|
54
|
+
def sort_element_attrs!(node)
|
55
|
+
node.attributes.sort_by(&:first).each do |name, value|
|
56
|
+
node.delete(name)
|
57
|
+
node[name] = value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sorts CSS declarations in style attribute of the element +node+ by name.
|
62
|
+
def sort_element_style_attr!(node)
|
63
|
+
return unless node.has_attribute? 'style'
|
64
|
+
decls = node['style'].scan(/([\w-]+):\s*([^;]+);?/).sort_by(&:first)
|
65
|
+
node['style'] = decls.map { |name, val| "#{name}: #{val};" }.join(' ')
|
66
|
+
end
|
67
|
+
|
68
|
+
# Note: muttable methods like +gsub!+ doesn't work on node content.
|
69
|
+
|
70
|
+
# Strips repeated whitespaces in the text +node+.
|
71
|
+
def strip_redundant_spaces!(node)
|
72
|
+
node.content = node.content.gsub("\n", ' ').gsub(/(\s)+/, '\1')
|
73
|
+
end
|
74
|
+
|
75
|
+
# Strips nonsignificant leading and trailing whitespaces in the text +node+.
|
76
|
+
def strip_spaces_around_text!(node)
|
77
|
+
node.content = node.content.lstrip if text_block_boundary? node, :left
|
78
|
+
node.content = node.content.rstrip if text_block_boundary? node, :right
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Returns +true+ if the text +node+ is the first (+:left+), or the last
|
83
|
+
# (+:right+) inline element of the nearest block element ancestor or
|
84
|
+
# direct sibling of +<br>+ element.
|
85
|
+
#
|
86
|
+
# @return [Boolean]
|
87
|
+
#
|
88
|
+
def text_block_boundary?(node, side)
|
89
|
+
method = { left: :previous_sibling, right: :next_sibling }[side]
|
90
|
+
|
91
|
+
return true if node.send(method).try(:name) == 'br'
|
92
|
+
loop do
|
93
|
+
if (sibling = node.send(method))
|
94
|
+
return false if sibling.text? || inline_element?(sibling)
|
95
|
+
end
|
96
|
+
node = node.parent
|
97
|
+
return true unless inline_element? node
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
HTML_INLINE_ELEMENTS = Nokogiri::HTML::ElementDescription::HTML_INLINE.flatten
|
102
|
+
|
103
|
+
# @return [Boolean] true if the +node+ represents an inline HTML element.
|
104
|
+
def inline_element?(node)
|
105
|
+
node.element? && HTML_INLINE_ELEMENTS.include?(node.name)
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [Boolean] true if the +node+ is descendant of +<pre>+ node.
|
109
|
+
def preformatted_block?(node)
|
110
|
+
node.path =~ %r{/pre/}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
[Nokogiri::HTML::Document, Nokogiri::HTML::DocumentFragment].each do |klass|
|
117
|
+
klass.send :include, Asciidoctor::DocTest::HTML::Normalizer
|
118
|
+
end
|