asciidoctor 1.5.8 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/CHANGELOG.adoc +628 -45
  4. data/LICENSE +2 -1
  5. data/README-de.adoc +28 -38
  6. data/README-fr.adoc +30 -43
  7. data/README-jp.adoc +255 -201
  8. data/README-zh_CN.adoc +40 -44
  9. data/README.adoc +170 -143
  10. data/asciidoctor.gemspec +22 -34
  11. data/bin/asciidoctor +5 -4
  12. data/data/locale/attributes-ar.adoc +4 -3
  13. data/data/locale/attributes-be.adoc +23 -0
  14. data/data/locale/attributes-bg.adoc +4 -3
  15. data/data/locale/attributes-ca.adoc +6 -5
  16. data/data/locale/attributes-cs.adoc +4 -3
  17. data/data/locale/attributes-da.adoc +6 -5
  18. data/data/locale/attributes-de.adoc +6 -5
  19. data/data/locale/attributes-en.adoc +4 -4
  20. data/data/locale/attributes-es.adoc +6 -5
  21. data/data/locale/attributes-fa.adoc +4 -3
  22. data/data/locale/attributes-fi.adoc +4 -3
  23. data/data/locale/attributes-fr.adoc +8 -7
  24. data/data/locale/attributes-hu.adoc +4 -3
  25. data/data/locale/attributes-id.adoc +4 -3
  26. data/data/locale/attributes-it.adoc +6 -5
  27. data/data/locale/attributes-ja.adoc +4 -3
  28. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  29. data/data/locale/attributes-nb.adoc +4 -3
  30. data/data/locale/attributes-nl.adoc +6 -5
  31. data/data/locale/attributes-nn.adoc +4 -3
  32. data/data/locale/attributes-pl.adoc +8 -7
  33. data/data/locale/attributes-pt.adoc +6 -5
  34. data/data/locale/attributes-pt_BR.adoc +6 -5
  35. data/data/locale/attributes-ro.adoc +4 -3
  36. data/data/locale/attributes-ru.adoc +6 -5
  37. data/data/locale/attributes-sr.adoc +4 -4
  38. data/data/locale/attributes-sr_Latn.adoc +4 -4
  39. data/data/locale/attributes-sv.adoc +4 -4
  40. data/data/locale/attributes-th.adoc +23 -0
  41. data/data/locale/attributes-tr.adoc +4 -3
  42. data/data/locale/attributes-uk.adoc +6 -5
  43. data/data/locale/attributes-vi.adoc +23 -0
  44. data/data/locale/attributes-zh_CN.adoc +4 -3
  45. data/data/locale/attributes-zh_TW.adoc +4 -3
  46. data/data/reference/syntax.adoc +296 -0
  47. data/data/stylesheets/asciidoctor-default.css +120 -114
  48. data/data/stylesheets/coderay-asciidoctor.css +15 -17
  49. data/lib/asciidoctor/abstract_block.rb +146 -140
  50. data/lib/asciidoctor/abstract_node.rb +152 -170
  51. data/lib/asciidoctor/attribute_list.rb +77 -89
  52. data/lib/asciidoctor/block.rb +29 -28
  53. data/lib/asciidoctor/callouts.rb +4 -2
  54. data/lib/asciidoctor/cli/invoker.rb +20 -24
  55. data/lib/asciidoctor/cli/options.rb +107 -96
  56. data/lib/asciidoctor/cli.rb +3 -2
  57. data/lib/asciidoctor/convert.rb +199 -0
  58. data/lib/asciidoctor/converter/composite.rb +40 -48
  59. data/lib/asciidoctor/converter/docbook5.rb +627 -644
  60. data/lib/asciidoctor/converter/html5.rb +1053 -951
  61. data/lib/asciidoctor/converter/manpage.rb +581 -532
  62. data/lib/asciidoctor/converter/template.rb +232 -271
  63. data/lib/asciidoctor/converter.rb +370 -185
  64. data/lib/asciidoctor/core_ext/float/truncate.rb +20 -0
  65. data/lib/asciidoctor/core_ext/hash/merge.rb +8 -0
  66. data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
  67. data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
  68. data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
  69. data/lib/asciidoctor/core_ext.rb +8 -17
  70. data/lib/asciidoctor/document.rb +503 -461
  71. data/lib/asciidoctor/extensions.rb +127 -174
  72. data/lib/asciidoctor/helpers.rb +184 -107
  73. data/lib/asciidoctor/inline.rb +9 -12
  74. data/lib/asciidoctor/list.rb +11 -29
  75. data/lib/asciidoctor/load.rb +119 -0
  76. data/lib/asciidoctor/logging.rb +22 -17
  77. data/lib/asciidoctor/parser.rb +673 -719
  78. data/lib/asciidoctor/path_resolver.rb +48 -33
  79. data/lib/asciidoctor/reader.rb +383 -338
  80. data/lib/asciidoctor/rouge_ext.rb +39 -0
  81. data/lib/asciidoctor/rx.rb +723 -0
  82. data/lib/asciidoctor/section.rb +17 -16
  83. data/lib/asciidoctor/stylesheets.rb +19 -37
  84. data/lib/asciidoctor/substitutors.rb +926 -1022
  85. data/lib/asciidoctor/syntax_highlighter/coderay.rb +88 -0
  86. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +34 -0
  87. data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
  88. data/lib/asciidoctor/syntax_highlighter/prettify.rb +30 -0
  89. data/lib/asciidoctor/syntax_highlighter/pygments.rb +157 -0
  90. data/lib/asciidoctor/syntax_highlighter/rouge.rb +143 -0
  91. data/lib/asciidoctor/syntax_highlighter.rb +253 -0
  92. data/lib/asciidoctor/table.rb +152 -114
  93. data/lib/asciidoctor/timings.rb +7 -5
  94. data/lib/asciidoctor/version.rb +2 -1
  95. data/lib/asciidoctor/writer.rb +30 -0
  96. data/lib/asciidoctor.rb +266 -1340
  97. data/man/asciidoctor.1 +49 -47
  98. data/man/asciidoctor.adoc +54 -45
  99. metadata +50 -245
  100. data/CONTRIBUTING.adoc +0 -185
  101. data/Gemfile +0 -60
  102. data/Rakefile +0 -129
  103. data/bin/asciidoctor-safe +0 -15
  104. data/features/open_block.feature +0 -92
  105. data/features/pass_block.feature +0 -66
  106. data/features/step_definitions.rb +0 -49
  107. data/features/text_formatting.feature +0 -57
  108. data/features/xref.feature +0 -1039
  109. data/lib/asciidoctor/converter/base.rb +0 -59
  110. data/lib/asciidoctor/converter/docbook45.rb +0 -93
  111. data/lib/asciidoctor/converter/factory.rb +0 -226
  112. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
  113. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
  114. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
  115. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
  116. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
  117. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
  118. data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
  119. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
  120. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
  121. data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
  122. data/test/api_test.rb +0 -1240
  123. data/test/attribute_list_test.rb +0 -242
  124. data/test/attributes_test.rb +0 -1623
  125. data/test/blocks_test.rb +0 -3870
  126. data/test/converter_test.rb +0 -470
  127. data/test/document_test.rb +0 -1853
  128. data/test/extensions_test.rb +0 -1560
  129. data/test/fixtures/asciidoc_index.txt +0 -521
  130. data/test/fixtures/basic-docinfo-footer.html +0 -6
  131. data/test/fixtures/basic-docinfo-footer.xml +0 -8
  132. data/test/fixtures/basic-docinfo.html +0 -1
  133. data/test/fixtures/basic-docinfo.xml +0 -4
  134. data/test/fixtures/basic.asciidoc +0 -5
  135. data/test/fixtures/chapter-a.adoc +0 -3
  136. data/test/fixtures/child-include.adoc +0 -5
  137. data/test/fixtures/circle.svg +0 -9
  138. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
  139. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
  140. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
  141. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
  142. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
  143. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
  144. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
  145. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
  146. data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
  147. data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
  148. data/test/fixtures/docinfo-footer.html +0 -1
  149. data/test/fixtures/docinfo-footer.xml +0 -9
  150. data/test/fixtures/docinfo.html +0 -1
  151. data/test/fixtures/docinfo.xml +0 -3
  152. data/test/fixtures/doctime-localtime.adoc +0 -2
  153. data/test/fixtures/dot.gif +0 -0
  154. data/test/fixtures/encoding.asciidoc +0 -13
  155. data/test/fixtures/file-with-missing-include.adoc +0 -1
  156. data/test/fixtures/grandchild-include.adoc +0 -3
  157. data/test/fixtures/hello-asciidoctor.pdf +0 -69
  158. data/test/fixtures/include-file.asciidoc +0 -24
  159. data/test/fixtures/include-file.jsx +0 -8
  160. data/test/fixtures/include-file.ml +0 -3
  161. data/test/fixtures/include-file.xml +0 -5
  162. data/test/fixtures/lists.adoc +0 -96
  163. data/test/fixtures/master.adoc +0 -5
  164. data/test/fixtures/mismatched-end-tag.adoc +0 -7
  165. data/test/fixtures/other-chapters.adoc +0 -11
  166. data/test/fixtures/outer-include.adoc +0 -5
  167. data/test/fixtures/parent-include-restricted.adoc +0 -5
  168. data/test/fixtures/parent-include.adoc +0 -5
  169. data/test/fixtures/sample.asciidoc +0 -30
  170. data/test/fixtures/section-a.adoc +0 -4
  171. data/test/fixtures/stylesheets/custom.css +0 -3
  172. data/test/fixtures/subdir/index.adoc +0 -3
  173. data/test/fixtures/subdir/inner-include.adoc +0 -3
  174. data/test/fixtures/subdir/middle-include.adoc +0 -5
  175. data/test/fixtures/subs-docinfo.html +0 -2
  176. data/test/fixtures/subs.adoc +0 -6
  177. data/test/fixtures/tagged-class-enclosed.rb +0 -25
  178. data/test/fixtures/tagged-class.rb +0 -23
  179. data/test/fixtures/tip.gif +0 -0
  180. data/test/fixtures/unclosed-tag.adoc +0 -3
  181. data/test/fixtures/unexpected-end-tag.adoc +0 -4
  182. data/test/invoker_test.rb +0 -745
  183. data/test/links_test.rb +0 -855
  184. data/test/lists_test.rb +0 -5151
  185. data/test/logger_test.rb +0 -211
  186. data/test/manpage_test.rb +0 -660
  187. data/test/options_test.rb +0 -262
  188. data/test/paragraphs_test.rb +0 -562
  189. data/test/parser_test.rb +0 -742
  190. data/test/paths_test.rb +0 -395
  191. data/test/preamble_test.rb +0 -173
  192. data/test/reader_test.rb +0 -2161
  193. data/test/sections_test.rb +0 -3575
  194. data/test/substitutions_test.rb +0 -2066
  195. data/test/tables_test.rb +0 -2036
  196. data/test/test_helper.rb +0 -447
  197. data/test/text_test.rb +0 -309
@@ -1,14 +1,17 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
+ # Internal: Except where noted, a module that contains internal helper functions.
3
4
  module Helpers
4
- # Internal: Require the specified library using Kernel#require.
5
+ module_function
6
+
7
+ # Public: Require the specified library using Kernel#require.
5
8
  #
6
9
  # Attempts to load the library specified in the first argument using the
7
10
  # Kernel#require. Rescues the LoadError if the library is not available and
8
11
  # passes a message to Kernel#raise if on_failure is :abort or Kernel#warn if
9
12
  # on_failure is :warn to communicate to the user that processing is being
10
13
  # aborted or functionality is disabled, respectively. If a gem_name is
11
- # specified, the message communicates that a required gem is not installed.
14
+ # specified, the message communicates that a required gem is not available.
12
15
  #
13
16
  # name - the String name of the library to require.
14
17
  # gem_name - a Boolean that indicates whether this library is provided by a RubyGem,
@@ -20,108 +23,96 @@ module Helpers
20
23
  # Otherwise, if on_failure is :abort, Kernel#raise is called with an appropriate message.
21
24
  # Otherwise, if on_failure is :warn, Kernel#warn is called with an appropriate message and nil returned.
22
25
  # Otherwise, nil is returned.
23
- def self.require_library name, gem_name = true, on_failure = :abort
26
+ def require_library name, gem_name = true, on_failure = :abort
24
27
  require name
25
- rescue ::LoadError => e
28
+ rescue ::LoadError
26
29
  include Logging unless include? Logging
27
30
  if gem_name
28
31
  gem_name = name if gem_name == true
29
32
  case on_failure
30
33
  when :abort
31
- raise ::LoadError, %(asciidoctor: FAILED: required gem '#{gem_name}' is not installed. Processing aborted.)
34
+ details = $!.path == gem_name ? '' : %[ (reason: #{$!.path ? %(cannot load '#{$!.path}') : $!.message})]
35
+ raise ::LoadError, %(asciidoctor: FAILED: required gem '#{gem_name}' is not available#{details}. Processing aborted.)
32
36
  when :warn
33
- logger.warn %(optional gem '#{gem_name}' is not installed. Functionality disabled.)
37
+ details = $!.path == gem_name ? '' : %[ (reason: #{$!.path ? %(cannot load '#{$!.path}') : $!.message})]
38
+ logger.warn %(optional gem '#{gem_name}' is not available#{details}. Functionality disabled.)
34
39
  end
35
40
  else
36
41
  case on_failure
37
42
  when :abort
38
- raise ::LoadError, %(asciidoctor: FAILED: #{e.message.chomp '.'}. Processing aborted.)
43
+ raise ::LoadError, %(asciidoctor: FAILED: #{$!.message.chomp '.'}. Processing aborted.)
39
44
  when :warn
40
- logger.warn %(#{e.message.chomp '.'}. Functionality disabled.)
45
+ logger.warn %(#{$!.message.chomp '.'}. Functionality disabled.)
41
46
  end
42
47
  end
43
48
  nil
44
49
  end
45
50
 
46
- # Public: Normalize the data to prepare for parsing
47
- #
48
- # Delegates to Helpers#normalize_lines_from_string if data is a String.
49
- # Delegates to Helpers#normalize_lines_array if data is a String Array.
50
- #
51
- # returns a String Array of normalized lines
52
- def self.normalize_lines data
53
- ::String === data ? (normalize_lines_from_string data) : (normalize_lines_array data)
54
- end
55
-
56
- # Public: Normalize the array of lines to prepare them for parsing
57
- #
58
- # Force encodes the data to UTF-8 and removes trailing whitespace from each line.
59
- #
60
- # If a BOM is present at the beginning of the data, a best attempt
61
- # is made to encode from the specified encoding to UTF-8.
62
- #
63
- # data - a String Array of lines to normalize
64
- #
65
- # returns a String Array of normalized lines
66
- def self.normalize_lines_array data
67
- return data if data.empty?
68
-
69
- leading_bytes = (first_line = data[0]).unpack 'C3'
70
- if COERCE_ENCODING
71
- utf8 = ::Encoding::UTF_8
72
- if (leading_2_bytes = leading_bytes.slice 0, 2) == BOM_BYTES_UTF_16LE
73
- # HACK Ruby messes up trailing whitespace on UTF-16LE, so reencode whole document first
74
- data = data.join
75
- return (((data.force_encoding ::Encoding::UTF_16LE).slice 1, data.length).encode utf8).each_line.map {|line| line.rstrip }
76
- elsif leading_2_bytes == BOM_BYTES_UTF_16BE
77
- data[0] = (first_line.force_encoding ::Encoding::UTF_16BE).slice 1, first_line.length
78
- return data.map {|line| ((line.force_encoding ::Encoding::UTF_16BE).encode utf8).rstrip }
79
- elsif leading_bytes == BOM_BYTES_UTF_8
80
- data[0] = (first_line.force_encoding utf8).slice 1, first_line.length
81
- end
82
-
83
- data.map {|line| line.encoding == utf8 ? line.rstrip : (line.force_encoding utf8).rstrip }
51
+ # Internal: Prepare the source data Array for parsing.
52
+ #
53
+ # Encodes the data to UTF-8, if necessary, and removes any trailing
54
+ # whitespace from every line.
55
+ #
56
+ # If a BOM is found at the beginning of the data, a best attempt is made to
57
+ # encode it to UTF-8 from the specified source encoding.
58
+ #
59
+ # data - the source data Array to prepare (no nil entries allowed)
60
+ # trim_end - whether to trim whitespace from the end of each line;
61
+ # (true cleans all whitespace; false only removes trailing newline) (default: true)
62
+ #
63
+ # returns a String Array of prepared lines
64
+ def prepare_source_array data, trim_end = true
65
+ return [] if data.empty?
66
+ if (leading_2_bytes = (leading_bytes = (first = data[0]).unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
67
+ data[0] = first.byteslice 2, first.bytesize
68
+ # NOTE you can't split a UTF-16LE string using .lines when encoding is UTF-8; doing so will cause this line to fail
69
+ return trim_end ? data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16LE).rstrip } : data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16LE).chomp }
70
+ elsif leading_2_bytes == BOM_BYTES_UTF_16BE
71
+ data[0] = first.byteslice 2, first.bytesize
72
+ return trim_end ? data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16BE).rstrip } : data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16BE).chomp }
73
+ elsif leading_bytes == BOM_BYTES_UTF_8
74
+ data[0] = first.byteslice 3, first.bytesize
75
+ end
76
+ if first.encoding == UTF_8
77
+ trim_end ? data.map {|line| line.rstrip } : data.map {|line| line.chomp }
84
78
  else
85
- # Ruby 1.8 has no built-in re-encoding, so no point in removing the UTF-16 BOMs
86
- data[0] = first_line.slice 3, first_line.length if leading_bytes == BOM_BYTES_UTF_8
87
- data.map {|line| line.rstrip }
79
+ trim_end ? data.map {|line| (line.encode UTF_8).rstrip } : data.map {|line| (line.encode UTF_8).chomp }
88
80
  end
89
81
  end
90
82
 
91
- # Public: Normalize the String and split into lines to prepare them for parsing
83
+ # Internal: Prepare the source data String for parsing.
92
84
  #
93
- # Force encodes the data to UTF-8 and removes trailing whitespace from each line.
94
- # Converts the data to a String Array.
85
+ # Encodes the data to UTF-8, if necessary, splits it into an array, and
86
+ # removes any trailing whitespace from every line.
95
87
  #
96
- # If a BOM is present at the beginning of the data, a best attempt
97
- # is made to encode from the specified encoding to UTF-8.
88
+ # If a BOM is found at the beginning of the data, a best attempt is made to
89
+ # encode it to UTF-8 from the specified source encoding.
98
90
  #
99
- # data - a String of lines to normalize
91
+ # data - the source data String to prepare
92
+ # trim_end - whether to trim whitespace from the end of each line;
93
+ # (true cleans all whitespace; false only removes trailing newline) (default: true)
100
94
  #
101
- # returns a String Array of normalized lines
102
- def self.normalize_lines_from_string data
95
+ # returns a String Array of prepared lines
96
+ def prepare_source_string data, trim_end = true
103
97
  return [] if data.nil_or_empty?
104
-
105
- leading_bytes = data.unpack 'C3'
106
- if COERCE_ENCODING
107
- utf8 = ::Encoding::UTF_8
108
- if (leading_2_bytes = leading_bytes.slice 0, 2) == BOM_BYTES_UTF_16LE
109
- data = ((data.force_encoding ::Encoding::UTF_16LE).slice 1, data.length).encode utf8
110
- elsif leading_2_bytes == BOM_BYTES_UTF_16BE
111
- data = ((data.force_encoding ::Encoding::UTF_16BE).slice 1, data.length).encode utf8
112
- elsif leading_bytes == BOM_BYTES_UTF_8
113
- data = data.encoding == utf8 ? (data.slice 1, data.length) : ((data.force_encoding utf8).slice 1, data.length)
114
- else
115
- data = data.force_encoding utf8 unless data.encoding == utf8
116
- end
98
+ if (leading_2_bytes = (leading_bytes = data.unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
99
+ data = (data.byteslice 2, data.bytesize).encode UTF_8, ::Encoding::UTF_16LE
100
+ elsif leading_2_bytes == BOM_BYTES_UTF_16BE
101
+ data = (data.byteslice 2, data.bytesize).encode UTF_8, ::Encoding::UTF_16BE
102
+ elsif leading_bytes == BOM_BYTES_UTF_8
103
+ data = data.byteslice 3, data.bytesize
104
+ data = data.encode UTF_8 unless data.encoding == UTF_8
105
+ elsif data.encoding != UTF_8
106
+ data = data.encode UTF_8
107
+ end
108
+ if trim_end
109
+ [].tap {|lines| data.each_line {|line| lines << line.rstrip } }
117
110
  else
118
- # Ruby 1.8 has no built-in re-encoding, so no point in removing the UTF-16 BOMs
119
- data = data.slice 3, data.length if leading_bytes == BOM_BYTES_UTF_8
111
+ [].tap {|lines| data.each_line {|line| lines << line.chomp } }
120
112
  end
121
- data.each_line.map {|line| line.rstrip }
122
113
  end
123
114
 
124
- # Public: Efficiently checks whether the specified String resembles a URI
115
+ # Internal: Efficiently checks whether the specified String resembles a URI
125
116
  #
126
117
  # Uses the Asciidoctor::UriSniffRx regex to check whether the String begins
127
118
  # with a URI prefix (e.g., http://). No validation of the URI is performed.
@@ -129,46 +120,57 @@ module Helpers
129
120
  # str - the String to check
130
121
  #
131
122
  # returns true if the String is a URI, false if it is not
132
- def self.uriish? str
123
+ def uriish? str
133
124
  (str.include? ':') && (UriSniffRx.match? str)
134
125
  end
135
126
 
136
- # Public: Efficiently retrieves the URI prefix of the specified String
137
- #
138
- # Uses the Asciidoctor::UriSniffRx regex to match the URI prefix in the
139
- # specified String (e.g., http://), if present.
140
- #
141
- # str - the String to check
142
- #
143
- # returns the string URI prefix if the string is a URI, otherwise nil
144
- def self.uri_prefix str
145
- (str.include? ':') && UriSniffRx =~ str ? $& : nil
127
+ # Internal: Encode a URI component String for safe inclusion in a URI.
128
+ #
129
+ # str - the URI component String to encode
130
+ #
131
+ # Returns the String with all reserved URI characters encoded (e.g., /, &, =, space, etc).
132
+ if RUBY_ENGINE == 'opal'
133
+ def encode_uri_component str
134
+ # patch necessary to adhere with RFC-3986 (and thus CGI.escape)
135
+ # see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description
136
+ %x(
137
+ return encodeURIComponent(str).replace(/%20|[!'()*]/g, function (m) {
138
+ return m === '%20' ? '+' : '%' + m.charCodeAt(0).toString(16)
139
+ })
140
+ )
141
+ end
142
+ else
143
+ CGI = ::CGI
144
+ def encode_uri_component str
145
+ CGI.escape str
146
+ end
146
147
  end
147
148
 
148
- # Matches the characters in a URI to encode
149
- REGEXP_ENCODE_URI_CHARS = /[^\w\-.!~*';:@=+$,()\[\]]/
150
-
151
- # Public: Encode a String for inclusion in a URI.
149
+ # Internal: Apply URI path encoding to spaces in the specified string (i.e., convert spaces to %20).
152
150
  #
153
- # str - the String to URI encode
151
+ # str - the String to encode
154
152
  #
155
- # Returns the String with all URI reserved characters encoded.
156
- def self.uri_encode str
157
- str.gsub(REGEXP_ENCODE_URI_CHARS) { $&.each_byte.map {|c| sprintf '%%%02X', c }.join }
153
+ # Returns the specified String with all spaces replaced with %20.
154
+ def encode_spaces_in_uri str
155
+ (str.include? ' ') ? (str.gsub ' ', '%20') : str
158
156
  end
159
157
 
160
158
  # Public: Removes the file extension from filename and returns the result
161
159
  #
162
- # filename - The String file name to process
160
+ # filename - The String file name to process; expected to be a posix path
163
161
  #
164
162
  # Examples
165
163
  #
166
- # Helpers.rootname('part1/chapter1.adoc')
164
+ # Helpers.rootname 'part1/chapter1.adoc'
167
165
  # # => "part1/chapter1"
168
166
  #
169
167
  # Returns the String filename with the file extension removed
170
- def self.rootname filename
171
- filename.slice 0, ((filename.rindex '.') || filename.length)
168
+ def rootname filename
169
+ if (last_dot_idx = filename.rindex '.')
170
+ (filename.index '/', last_dot_idx) ? filename : (filename.slice 0, last_dot_idx)
171
+ else
172
+ filename
173
+ end
172
174
  end
173
175
 
174
176
  # Public: Retrieves the basename of the filename, optionally removing the extension, if present
@@ -179,22 +181,59 @@ module Helpers
179
181
  #
180
182
  # Examples
181
183
  #
182
- # Helpers.basename('images/tiger.png', true)
184
+ # Helpers.basename 'images/tiger.png', true
183
185
  # # => "tiger"
184
186
  #
185
- # Helpers.basename('images/tiger.png', '.png')
187
+ # Helpers.basename 'images/tiger.png', '.png'
186
188
  # # => "tiger"
187
189
  #
188
190
  # Returns the String filename with leading directories removed and, if specified, the extension removed
189
- def self.basename(filename, drop_ext = nil)
191
+ def basename filename, drop_ext = nil
190
192
  if drop_ext
191
- ::File.basename filename, (drop_ext == true ? (::File.extname filename) : drop_ext)
193
+ ::File.basename filename, (drop_ext == true ? (extname filename) : drop_ext)
192
194
  else
193
195
  ::File.basename filename
194
196
  end
195
197
  end
196
198
 
197
- def self.mkdir_p dir
199
+ # Public: Returns whether this path has a file extension.
200
+ #
201
+ # path - The path String to check; expects a posix path
202
+ #
203
+ # Returns true if the path has a file extension, false otherwise
204
+ def extname? path
205
+ (last_dot_idx = path.rindex '.') && !(path.index '/', last_dot_idx)
206
+ end
207
+
208
+ # Public: Retrieves the file extension of the specified path. The file extension is the portion of the path in the
209
+ # last path segment starting from the last period.
210
+ #
211
+ # This method differs from File.extname in that it gives us control over the fallback value and is more efficient.
212
+ #
213
+ # path - The path String in which to look for a file extension
214
+ # fallback - The fallback String to return if no file extension is present (optional, default: '')
215
+ #
216
+ # Returns the String file extension (with the leading dot included) or the fallback value if the path has no file extension.
217
+ if ::File::ALT_SEPARATOR
218
+ def extname path, fallback = ''
219
+ if (last_dot_idx = path.rindex '.')
220
+ (path.index '/', last_dot_idx) || (path.index ::File::ALT_SEPARATOR, last_dot_idx) ? fallback : (path.slice last_dot_idx, path.length)
221
+ else
222
+ fallback
223
+ end
224
+ end
225
+ else
226
+ def extname path, fallback = ''
227
+ if (last_dot_idx = path.rindex '.')
228
+ (path.index '/', last_dot_idx) ? fallback : (path.slice last_dot_idx, path.length)
229
+ else
230
+ fallback
231
+ end
232
+ end
233
+ end
234
+
235
+ # Internal: Make a directory, ensuring all parent directories exist.
236
+ def mkdir_p dir
198
237
  unless ::File.directory? dir
199
238
  unless (parent_dir = ::File.dirname dir) == '.'
200
239
  mkdir_p parent_dir
@@ -211,17 +250,55 @@ module Helpers
211
250
  'M' => 1000, 'CM' => 900, 'D' => 500, 'CD' => 400, 'C' => 100, 'XC' => 90,
212
251
  'L' => 50, 'XL' => 40, 'X' => 10, 'IX' => 9, 'V' => 5, 'IV' => 4, 'I' => 1
213
252
  }
253
+ private_constant :ROMAN_NUMERALS
214
254
 
215
- # Converts an integer to a Roman numeral.
255
+ # Internal: Converts an integer to a Roman numeral.
216
256
  #
217
257
  # val - the [Integer] value to convert
218
258
  #
219
259
  # Returns the [String] roman numeral for this integer
220
- def self.int_to_roman val
221
- ROMAN_NUMERALS.map {|l, i|
260
+ def int_to_roman val
261
+ ROMAN_NUMERALS.map do |l, i|
222
262
  repeat, val = val.divmod i
223
263
  l * repeat
224
- }.join
264
+ end.join
265
+ end
266
+
267
+ # Internal: Get the next value in the sequence.
268
+ #
269
+ # Handles both integer and character sequences.
270
+ #
271
+ # current - the value to increment as a String or Integer
272
+ #
273
+ # returns the next value in the sequence according to the current value's type
274
+ def nextval current
275
+ if ::Integer === current
276
+ current + 1
277
+ elsif (intval = current.to_i).to_s == current.to_s
278
+ intval + 1
279
+ else
280
+ current.succ
281
+ end
282
+ end
283
+
284
+ # Internal: Resolve the specified object as a Class
285
+ #
286
+ # object - The Object to resolve as a Class
287
+ #
288
+ # Returns a Class if the specified object is a Class (but not a Module) or
289
+ # a String that resolves to a Class; otherwise, nil
290
+ def resolve_class object
291
+ ::Class === object ? object : (::String === object ? (class_for_name object) : nil)
292
+ end
293
+
294
+ # Internal: Resolves a Class object (not a Module) for the qualified name.
295
+ #
296
+ # Returns Class
297
+ def class_for_name qualified_name
298
+ raise unless ::Class === (resolved = ::Object.const_get qualified_name, false)
299
+ resolved
300
+ rescue
301
+ raise ::NameError, %(Could not resolve class for name: #{qualified_name})
225
302
  end
226
303
  end
227
304
  end
@@ -1,9 +1,9 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # Public: Methods for managing inline elements in AsciiDoc block
4
4
  class Inline < AbstractNode
5
5
  # Public: Get the text of this inline element
6
- attr_reader :text
6
+ attr_accessor :text
7
7
 
8
8
  # Public: Get the type (qualifier) of this inline element
9
9
  attr_reader :type
@@ -12,19 +12,12 @@ class Inline < AbstractNode
12
12
  attr_accessor :target
13
13
 
14
14
  def initialize(parent, context, text = nil, opts = {})
15
- super(parent, context)
15
+ super(parent, context, opts)
16
16
  @node_name = %(inline_#{context})
17
-
18
17
  @text = text
19
-
20
18
  @id = opts[:id]
21
19
  @type = opts[:type]
22
20
  @target = opts[:target]
23
-
24
- # value of attributes option for inline nodes may be nil
25
- if (attrs = opts[:attributes])
26
- @attributes = attrs.dup
27
- end
28
21
  end
29
22
 
30
23
  def block?
@@ -39,21 +32,25 @@ class Inline < AbstractNode
39
32
  converter.convert self
40
33
  end
41
34
 
42
- # Alias render to convert to maintain backwards compatibility
35
+ # Deprecated: Use {Inline#convert} instead.
43
36
  alias render convert
44
37
 
45
38
  # Public: Returns the converted alt text for this inline image.
46
39
  #
47
40
  # Returns the [String] value of the alt attribute.
48
41
  def alt
49
- attr 'alt'
42
+ (attr 'alt') || ''
50
43
  end
51
44
 
45
+ # For a reference node (:ref or :bibref), the text is the reftext (and the reftext attribute is not set).
46
+ #
52
47
  # (see AbstractNode#reftext?)
53
48
  def reftext?
54
49
  @text && (@type == :ref || @type == :bibref)
55
50
  end
56
51
 
52
+ # For a reference node (:ref or :bibref), the text is the reftext (and the reftext attribute is not set).
53
+ #
57
54
  # (see AbstractNode#reftext)
58
55
  def reftext
59
56
  (val = @text) ? (apply_reftext_subs val) : nil
@@ -1,4 +1,4 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # Public: Methods for managing AsciiDoc lists (ordered, unordered and description lists)
4
4
  class List < AbstractBlock
@@ -31,7 +31,7 @@ class List < AbstractBlock
31
31
  end
32
32
  end
33
33
 
34
- # Alias render to convert to maintain backwards compatibility
34
+ # Deprecated: Use {List#convert} instead.
35
35
  alias render convert
36
36
 
37
37
  def to_s
@@ -41,6 +41,9 @@ class List < AbstractBlock
41
41
  end
42
42
 
43
43
  # Public: Methods for managing items for AsciiDoc olists, ulist, and dlists.
44
+ #
45
+ # In a description list (dlist), each item is a tuple that consists of a 2-item Array of ListItem terms and a ListItem
46
+ # description (i.e., [[term, term, ...], desc]. If a description is not set, then the second entry in the tuple is nil.
44
47
  class ListItem < AbstractBlock
45
48
 
46
49
  # A contextual alias for the list parent node; counterpart to the items alias on List
@@ -63,7 +66,7 @@ class ListItem < AbstractBlock
63
66
  # Public: A convenience method that checks whether the text of this list item
64
67
  # is not blank (i.e., not nil or empty string).
65
68
  def text?
66
- !@text.nil_or_empty?
69
+ @text.nil_or_empty? ? false : true
67
70
  end
68
71
 
69
72
  # Public: Get the String text of this ListItem with substitutions applied.
@@ -77,12 +80,8 @@ class ListItem < AbstractBlock
77
80
  @text && (apply_subs @text, @subs)
78
81
  end
79
82
 
80
- # Public: Set the String text.
81
- #
82
- # Returns the new String text assigned to this ListItem
83
- def text= val
84
- @text = val
85
- end
83
+ # Public: Set the String text assigned to this ListItem
84
+ attr_writer :text
86
85
 
87
86
  # Check whether this list item has simple content (no nested blocks aside from a single outline list).
88
87
  # Primarily relevant for outline lists.
@@ -100,33 +99,16 @@ class ListItem < AbstractBlock
100
99
  !simple?
101
100
  end
102
101
 
103
- # Public: Fold the first paragraph block into the text
104
- #
105
- # Here are the rules for when a folding occurs:
106
- #
107
- # Given: this list item has at least one block
108
- # When: the first block is a paragraph that's not connected by a list continuation
109
- # Or: the first block is an indented paragraph that's adjacent (wrapped line)
110
- # Or: the first block is an indented paragraph that's not connected by a list continuation
111
- # Then: then drop the first block and fold it's content (buffer) into the list text
102
+ # Internal: Fold the adjacent paragraph block into the list item text
112
103
  #
113
104
  # Returns nothing
114
- def fold_first(continuation_connects_first_block = false, content_adjacent = false)
115
- if (first_block = @blocks[0]) && Block === first_block &&
116
- ((first_block.context == :paragraph && !continuation_connects_first_block) ||
117
- ((content_adjacent || !continuation_connects_first_block) && first_block.context == :literal &&
118
- first_block.option?('listparagraph')))
119
-
120
- block = blocks.shift
121
- block.lines.unshift @text unless @text.nil_or_empty?
122
- @text = block.source
123
- end
105
+ def fold_first
106
+ @text = @text.nil_or_empty? ? @blocks.shift.source : %(#{@text}#{LF}#{@blocks.shift.source})
124
107
  nil
125
108
  end
126
109
 
127
110
  def to_s
128
111
  %(#<#{self.class}@#{object_id} {list_context: #{parent.context.inspect}, text: #{@text.inspect}, blocks: #{(@blocks || []).size}}>)
129
112
  end
130
-
131
113
  end
132
114
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+ module Asciidoctor
3
+ class << self
4
+ # Public: Parse the AsciiDoc source input into a {Document}
5
+ #
6
+ # Accepts input as an IO (or StringIO), String or String Array object. If the
7
+ # input is a File, the object is expected to be opened for reading and is not
8
+ # closed afterwards by this method. Information about the file (filename,
9
+ # directory name, etc) gets assigned to attributes on the Document object.
10
+ #
11
+ # input - the AsciiDoc source as a IO, String or Array.
12
+ # options - a String, Array or Hash of options to control processing (default: {})
13
+ # String and Array values are converted into a Hash.
14
+ # See {Document#initialize} for details about these options.
15
+ #
16
+ # Returns the Document
17
+ def load input, options = {}
18
+ options = options.merge
19
+
20
+ if (timings = options[:timings])
21
+ timings.start :read
22
+ end
23
+
24
+ if (options.key? :logger) && (logger = options[:logger]) != LoggerManager.logger
25
+ LoggerManager.logger = logger || NullLogger.new
26
+ end
27
+
28
+ if !(attrs = options[:attributes])
29
+ attrs = {}
30
+ elsif ::Hash === attrs
31
+ attrs = attrs.merge
32
+ elsif (defined? ::Java::JavaUtil::Map) && ::Java::JavaUtil::Map === attrs
33
+ attrs = attrs.dup
34
+ elsif ::Array === attrs
35
+ attrs = {}.tap do |accum|
36
+ attrs.each do |entry|
37
+ k, _, v = entry.partition '='
38
+ accum[k] = v
39
+ end
40
+ end
41
+ elsif ::String === attrs
42
+ # condense and convert non-escaped spaces to null, unescape escaped spaces, then split on null
43
+ attrs = {}.tap do |accum|
44
+ attrs.gsub(SpaceDelimiterRx, '\1' + NULL).gsub(EscapedSpaceRx, '\1').split(NULL).each do |entry|
45
+ k, _, v = entry.partition '='
46
+ accum[k] = v
47
+ end
48
+ end
49
+ elsif (attrs.respond_to? :keys) && (attrs.respond_to? :[])
50
+ # coerce attrs to a real Hash
51
+ attrs = {}.tap {|accum| attrs.keys.each {|k| accum[k] = attrs[k] } }
52
+ else
53
+ raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors.join ' < '})
54
+ end
55
+
56
+ if ::File === input
57
+ # File#mtime on JRuby 9.1 for Windows doesn't honor TZ environment variable; see https://github.com/jruby/jruby/issues/6659
58
+ options[:input_mtime] = RUBY_ENGINE == 'jruby' ? (::Time.at input.mtime.to_i) : input.mtime
59
+ # NOTE defer setting infile and indir until we get a better sense of their purpose
60
+ # TODO cli checks if input path can be read and is file, but might want to add check to API too
61
+ attrs['docfile'] = input_path = ::File.absolute_path input.path
62
+ attrs['docdir'] = ::File.dirname input_path
63
+ attrs['docname'] = Helpers.basename input_path, (attrs['docfilesuffix'] = Helpers.extname input_path)
64
+ source = input.read
65
+ elsif input.respond_to? :read
66
+ # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
67
+ # just fail the rewind operation silently to handle all cases
68
+ input.rewind rescue nil
69
+ source = input.read
70
+ elsif ::String === input
71
+ source = input
72
+ elsif ::Array === input
73
+ source = input.drop 0
74
+ elsif input
75
+ raise ::ArgumentError, %(unsupported input type: #{input.class})
76
+ end
77
+
78
+ if timings
79
+ timings.record :read
80
+ timings.start :parse
81
+ end
82
+
83
+ options[:attributes] = attrs
84
+ doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse
85
+
86
+ timings.record :parse if timings
87
+ doc
88
+ rescue => e
89
+ begin
90
+ context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
91
+ if e.respond_to? :exception
92
+ # The original message must be explicitly preserved when wrapping a Ruby exception
93
+ wrapped_e = e.exception %(#{context} - #{e.message})
94
+ # JRuby automatically sets backtrace; MRI did not until 2.6
95
+ wrapped_e.set_backtrace e.backtrace
96
+ else
97
+ # Likely a Java exception class
98
+ wrapped_e = e.class.new context, e
99
+ wrapped_e.stack_trace = e.stack_trace
100
+ end
101
+ rescue
102
+ wrapped_e = e
103
+ end
104
+ raise wrapped_e
105
+ end
106
+
107
+ # Public: Parse the contents of the AsciiDoc source file into an Asciidoctor::Document
108
+ #
109
+ # input - the String AsciiDoc source filename
110
+ # options - a String, Array or Hash of options to control processing (default: {})
111
+ # String and Array values are converted into a Hash.
112
+ # See Asciidoctor::Document#initialize for details about options.
113
+ #
114
+ # Returns the Asciidoctor::Document
115
+ def load_file filename, options = {}
116
+ ::File.open(filename, FILE_READ_MODE) {|file| load file, options }
117
+ end
118
+ end
119
+ end