asciidoctor 1.5.8 → 2.0.17

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.
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