asciidoctor 2.0.7 → 2.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +169 -7
  3. data/LICENSE +2 -1
  4. data/README-de.adoc +5 -15
  5. data/README-fr.adoc +4 -14
  6. data/README-jp.adoc +234 -186
  7. data/README-zh_CN.adoc +7 -17
  8. data/README.adoc +18 -18
  9. data/asciidoctor.gemspec +4 -4
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-bg.adoc +4 -3
  12. data/data/locale/attributes-ca.adoc +6 -5
  13. data/data/locale/attributes-cs.adoc +4 -3
  14. data/data/locale/attributes-da.adoc +6 -5
  15. data/data/locale/attributes-de.adoc +4 -4
  16. data/data/locale/attributes-en.adoc +4 -4
  17. data/data/locale/attributes-es.adoc +6 -5
  18. data/data/locale/attributes-fa.adoc +4 -3
  19. data/data/locale/attributes-fi.adoc +4 -3
  20. data/data/locale/attributes-fr.adoc +6 -5
  21. data/data/locale/attributes-hu.adoc +4 -3
  22. data/data/locale/attributes-id.adoc +4 -3
  23. data/data/locale/attributes-it.adoc +4 -3
  24. data/data/locale/attributes-ja.adoc +4 -3
  25. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  26. data/data/locale/attributes-nb.adoc +4 -3
  27. data/data/locale/attributes-nl.adoc +4 -3
  28. data/data/locale/attributes-nn.adoc +4 -3
  29. data/data/locale/attributes-pl.adoc +8 -7
  30. data/data/locale/attributes-pt.adoc +6 -5
  31. data/data/locale/attributes-pt_BR.adoc +6 -5
  32. data/data/locale/attributes-ro.adoc +4 -3
  33. data/data/locale/attributes-ru.adoc +6 -5
  34. data/data/locale/attributes-sr.adoc +4 -4
  35. data/data/locale/attributes-sr_Latn.adoc +4 -4
  36. data/data/locale/attributes-sv.adoc +4 -4
  37. data/data/locale/attributes-tr.adoc +4 -3
  38. data/data/locale/attributes-uk.adoc +6 -5
  39. data/data/locale/attributes-zh_CN.adoc +4 -3
  40. data/data/locale/attributes-zh_TW.adoc +4 -3
  41. data/data/stylesheets/asciidoctor-default.css +33 -30
  42. data/lib/asciidoctor.rb +89 -791
  43. data/lib/asciidoctor/abstract_block.rb +19 -11
  44. data/lib/asciidoctor/abstract_node.rb +21 -15
  45. data/lib/asciidoctor/attribute_list.rb +59 -67
  46. data/lib/asciidoctor/cli/invoker.rb +2 -0
  47. data/lib/asciidoctor/cli/options.rb +3 -3
  48. data/lib/asciidoctor/convert.rb +167 -162
  49. data/lib/asciidoctor/converter.rb +14 -13
  50. data/lib/asciidoctor/converter/docbook5.rb +10 -26
  51. data/lib/asciidoctor/converter/html5.rb +62 -43
  52. data/lib/asciidoctor/converter/manpage.rb +13 -12
  53. data/lib/asciidoctor/converter/template.rb +6 -3
  54. data/lib/asciidoctor/document.rb +25 -41
  55. data/lib/asciidoctor/extensions.rb +3 -3
  56. data/lib/asciidoctor/helpers.rb +38 -39
  57. data/lib/asciidoctor/inline.rb +1 -1
  58. data/lib/asciidoctor/load.rb +101 -101
  59. data/lib/asciidoctor/parser.rb +30 -25
  60. data/lib/asciidoctor/path_resolver.rb +35 -25
  61. data/lib/asciidoctor/reader.rb +14 -7
  62. data/lib/asciidoctor/rx.rb +722 -0
  63. data/lib/asciidoctor/substitutors.rb +61 -39
  64. data/lib/asciidoctor/syntax_highlighter.rb +22 -8
  65. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  66. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  67. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  68. data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -3
  69. data/lib/asciidoctor/syntax_highlighter/rouge.rb +15 -7
  70. data/lib/asciidoctor/table.rb +52 -23
  71. data/lib/asciidoctor/version.rb +1 -1
  72. data/man/asciidoctor.1 +6 -6
  73. data/man/asciidoctor.adoc +4 -3
  74. metadata +10 -9
@@ -34,8 +34,8 @@ class Converter::TemplateConverter < Converter::Base
34
34
  }
35
35
 
36
36
  begin
37
- require 'concurrent/hash' unless defined? ::Concurrent::Hash
38
- @caches = { scans: ::Concurrent::Hash.new, templates: ::Concurrent::Hash.new }
37
+ require 'concurrent/map' unless defined? ::Concurrent::Map
38
+ @caches = { scans: ::Concurrent::Map.new, templates: ::Concurrent::Map.new }
39
39
  rescue ::LoadError
40
40
  @caches = { scans: {}, templates: {} }
41
41
  end
@@ -71,7 +71,7 @@ class Converter::TemplateConverter < Converter::Base
71
71
  end
72
72
  case opts[:template_cache]
73
73
  when true
74
- logger.warn 'optional gem \'concurrent-ruby\' is not available. This gem is recommended when using the default template cache.' unless defined? ::Concurrent::Hash
74
+ logger.warn 'optional gem \'concurrent-ruby\' is not available. This gem is recommended when using the default template cache.' unless defined? ::Concurrent::Map
75
75
  @caches = self.class.caches
76
76
  when ::Hash
77
77
  @caches = opts[:template_cache]
@@ -257,6 +257,9 @@ class Converter::TemplateConverter < Converter::Base
257
257
  if !name || name == 'erb'
258
258
  require 'erb' unless defined? ::ERB.version
259
259
  [::Tilt::ERBTemplate, {}]
260
+ elsif name == 'erubi'
261
+ Helpers.require_library 'erubi' unless defined? ::Erubis::Engine
262
+ [::Tilt::ErubiTemplate, {}]
260
263
  elsif name == 'erubis'
261
264
  Helpers.require_library 'erubis' unless defined? ::Erubis::FastEruby
262
265
  [::Tilt::ErubisTemplate, { engine_class: ::Erubis::FastEruby }]
@@ -326,7 +326,7 @@ class Document < AbstractBlock
326
326
  @sourcemap = options[:sourcemap]
327
327
  @timings = options.delete :timings
328
328
  @path_resolver = PathResolver.new
329
- initialize_extensions = (defined? ::Asciidoctor::Extensions) ? true : nil
329
+ initialize_extensions = (defined? ::Asciidoctor::Extensions) || (options.key? :extensions) ? ::Asciidoctor::Extensions : nil
330
330
  @extensions = nil # initialize furthur down if initialize_extensions is true
331
331
  options[:standalone] = options[:header_footer] if (options.key? :header_footer) && !(options.key? :standalone)
332
332
  end
@@ -339,51 +339,36 @@ class Document < AbstractBlock
339
339
  (@options = options).freeze
340
340
 
341
341
  attrs = @attributes
342
- #attrs['encoding'] = 'UTF-8'
343
- attrs['sectids'] = ''
344
- attrs['toc-placement'] = 'auto'
342
+ attrs['attribute-undefined'] = Compliance.attribute_undefined
343
+ attrs['attribute-missing'] = Compliance.attribute_missing
344
+ attrs.update DEFAULT_ATTRIBUTES
345
+ # TODO if lang attribute is set, @safe mode < SafeMode::SERVER, and !parent_doc,
346
+ # load attributes from data/locale/attributes-<lang>.adoc
347
+
345
348
  if standalone
346
- attrs['copycss'] = ''
347
349
  # sync embedded attribute with :standalone option value
348
350
  attr_overrides['embedded'] = nil
351
+ attrs['copycss'] = ''
352
+ attrs['iconfont-remote'] = ''
353
+ attrs['stylesheet'] = ''
354
+ attrs['webfonts'] = ''
349
355
  else
350
- attrs['notitle'] = ''
351
356
  # sync embedded attribute with :standalone option value
352
357
  attr_overrides['embedded'] = ''
358
+ if (attr_overrides.key? 'showtitle') && (attr_overrides.keys & %w(notitle showtitle))[-1] == 'showtitle'
359
+ attr_overrides['notitle'] = { nil => '', false => '@', '@' => false}[attr_overrides['showtitle']]
360
+ elsif attr_overrides.key? 'notitle'
361
+ attr_overrides['showtitle'] = { nil => '', false => '@', '@' => false}[attr_overrides['notitle']]
362
+ else
363
+ attrs['notitle'] = ''
364
+ end
353
365
  end
354
- attrs['stylesheet'] = ''
355
- attrs['webfonts'] = ''
356
- attrs['prewrap'] = ''
357
- attrs['attribute-undefined'] = Compliance.attribute_undefined
358
- attrs['attribute-missing'] = Compliance.attribute_missing
359
- attrs['iconfont-remote'] = ''
360
-
361
- # language strings
362
- # TODO load these based on language settings
363
- attrs['caution-caption'] = 'Caution'
364
- attrs['important-caption'] = 'Important'
365
- attrs['note-caption'] = 'Note'
366
- attrs['tip-caption'] = 'Tip'
367
- attrs['warning-caption'] = 'Warning'
368
- attrs['example-caption'] = 'Example'
369
- attrs['figure-caption'] = 'Figure'
370
- #attrs['listing-caption'] = 'Listing'
371
- attrs['table-caption'] = 'Table'
372
- attrs['toc-title'] = 'Table of Contents'
373
- #attrs['preface-title'] = 'Preface'
374
- attrs['section-refsig'] = 'Section'
375
- attrs['part-refsig'] = 'Part'
376
- attrs['chapter-refsig'] = 'Chapter'
377
- attrs['appendix-caption'] = attrs['appendix-refsig'] = 'Appendix'
378
- attrs['untitled-label'] = 'Untitled'
379
- attrs['version-label'] = 'Version'
380
- attrs['last-update-label'] = 'Last updated'
381
366
 
382
367
  attr_overrides['asciidoctor'] = ''
383
368
  attr_overrides['asciidoctor-version'] = ::Asciidoctor::VERSION
384
369
 
385
370
  attr_overrides['safe-mode-name'] = (safe_mode_name = SafeMode.name_for_value @safe)
386
- attr_overrides["safe-mode-#{safe_mode_name}"] = ''
371
+ attr_overrides[%(safe-mode-#{safe_mode_name})] = ''
387
372
  attr_overrides['safe-mode-level'] = @safe
388
373
 
389
374
  # the only way to set the max-include-depth attribute is via the API; default to 64 like AsciiDoc Python
@@ -505,10 +490,10 @@ class Document < AbstractBlock
505
490
  ::AsciidoctorJ::Extensions::ExtensionRegistry === ext_registry)
506
491
  @extensions = ext_registry.activate self
507
492
  end
508
- elsif ::Proc === (ext_block = options[:extensions])
493
+ elsif (ext_block = options[:extensions]).nil?
494
+ @extensions = Extensions::Registry.new.activate self unless Extensions.groups.empty?
495
+ elsif ::Proc === ext_block
509
496
  @extensions = Extensions.create(&ext_block).activate self
510
- elsif !Extensions.groups.empty?
511
- @extensions = Extensions::Registry.new.activate self
512
497
  end
513
498
  end
514
499
 
@@ -606,11 +591,10 @@ class Document < AbstractBlock
606
591
  when :refs
607
592
  @catalog[:refs][value[0]] ||= (ref = value[1])
608
593
  ref
609
- #when :footnotes, :indexterms
610
594
  when :footnotes
611
595
  @catalog[type] << value
612
596
  else
613
- @catalog[type] << (type == :images ? (ImageReference.new value[0], value[1]) : value) if @options[:catalog_assets]
597
+ @catalog[type] << (type == :images ? (ImageReference.new value, @attributes['imagesdir']) : value) if @options[:catalog_assets]
614
598
  end
615
599
  end
616
600
 
@@ -773,7 +757,7 @@ class Document < AbstractBlock
773
757
  end
774
758
 
775
759
  def notitle
776
- !@attributes.key?('showtitle') && @attributes.key?('notitle')
760
+ @attributes.key? 'notitle'
777
761
  end
778
762
 
779
763
  def noheader
@@ -1218,7 +1202,7 @@ class Document < AbstractBlock
1218
1202
  when '', 'font'
1219
1203
  else
1220
1204
  attrs['icons'] = ''
1221
- attrs['icontype'] = icons_val
1205
+ attrs['icontype'] = icons_val unless icons_val == 'image'
1222
1206
  end
1223
1207
  end
1224
1208
 
@@ -425,7 +425,7 @@ module Extensions
425
425
  # TIP: Postprocessors can also be used to relocate assets needed by the published
426
426
  # document.
427
427
  #
428
- # Postprocessor implementations must Postprocessor.
428
+ # Postprocessor implementations must extend Postprocessor.
429
429
  class Postprocessor < Processor
430
430
  def process document, output
431
431
  raise ::NotImplementedError, %(#{Postprocessor} subclass #{self.class} must implement the ##{__method__} method)
@@ -610,7 +610,7 @@ module Extensions
610
610
  #--
611
611
  # TODO break this out into different pattern types
612
612
  # for example, FullInlineMacro, ShortInlineMacro (no target) and other patterns
613
- # FIXME for inline passthrough, we need to have some way to specify the text as a passthrough
613
+ # FIXME for inline macro, we need to have some way to specify the text as a passthrough
614
614
  class InlineMacroProcessor < MacroProcessor
615
615
  @@rx_cache = {}
616
616
 
@@ -622,7 +622,7 @@ module Extensions
622
622
 
623
623
  def resolve_regexp name, format
624
624
  raise ::ArgumentError, %(invalid name for inline macro: #{name}) unless MacroNameRx.match? name
625
- @@rx_cache[[name, format]] ||= /\\?#{name}:#{format == :short ? '(){0}' : '(\S+?)'}\[(|.*?[^\\])\]/
625
+ @@rx_cache[[name, format]] ||= /\\?#{name}:#{format == :short ? '(){0}' : '(\S+?)'}\[(|#{CC_ANY}*?[^\\])\]/
626
626
  end
627
627
  end
628
628
 
@@ -2,7 +2,9 @@
2
2
  module Asciidoctor
3
3
  # Internal: Except where noted, a module that contains internal helper functions.
4
4
  module Helpers
5
- # Internal: Require the specified library using Kernel#require.
5
+ module_function
6
+
7
+ # Public: Require the specified library using Kernel#require.
6
8
  #
7
9
  # Attempts to load the library specified in the first argument using the
8
10
  # Kernel#require. Rescues the LoadError if the library is not available and
@@ -21,7 +23,7 @@ module Helpers
21
23
  # Otherwise, if on_failure is :abort, Kernel#raise is called with an appropriate message.
22
24
  # Otherwise, if on_failure is :warn, Kernel#warn is called with an appropriate message and nil returned.
23
25
  # Otherwise, nil is returned.
24
- def self.require_library name, gem_name = true, on_failure = :abort
26
+ def require_library name, gem_name = true, on_failure = :abort
25
27
  require name
26
28
  rescue ::LoadError
27
29
  include Logging unless include? Logging
@@ -54,25 +56,27 @@ module Helpers
54
56
  # If a BOM is found at the beginning of the data, a best attempt is made to
55
57
  # encode it to UTF-8 from the specified source encoding.
56
58
  #
57
- # data - the source data Array to prepare (no nil entries allowed)
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)
58
62
  #
59
63
  # returns a String Array of prepared lines
60
- def self.prepare_source_array data
64
+ def prepare_source_array data, trim_end = true
61
65
  return [] if data.empty?
62
66
  if (leading_2_bytes = (leading_bytes = (first = data[0]).unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
63
67
  data[0] = first.byteslice 2, first.bytesize
64
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
65
- return data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16LE).rstrip }
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 }
66
70
  elsif leading_2_bytes == BOM_BYTES_UTF_16BE
67
71
  data[0] = first.byteslice 2, first.bytesize
68
- return data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16BE).rstrip }
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 }
69
73
  elsif leading_bytes == BOM_BYTES_UTF_8
70
74
  data[0] = first.byteslice 3, first.bytesize
71
75
  end
72
76
  if first.encoding == UTF_8
73
- data.map {|line| line.rstrip }
77
+ trim_end ? data.map {|line| line.rstrip } : data.map {|line| line.chomp }
74
78
  else
75
- data.map {|line| (line.encode UTF_8).rstrip }
79
+ trim_end ? data.map {|line| (line.encode UTF_8).rstrip } : data.map {|line| (line.encode UTF_8).chomp }
76
80
  end
77
81
  end
78
82
 
@@ -84,10 +88,12 @@ module Helpers
84
88
  # If a BOM is found at the beginning of the data, a best attempt is made to
85
89
  # encode it to UTF-8 from the specified source encoding.
86
90
  #
87
- # data - the source data String to prepare
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)
88
94
  #
89
95
  # returns a String Array of prepared lines
90
- def self.prepare_source_string data
96
+ def prepare_source_string data, trim_end = true
91
97
  return [] if data.nil_or_empty?
92
98
  if (leading_2_bytes = (leading_bytes = data.unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
93
99
  data = (data.byteslice 2, data.bytesize).encode UTF_8, ::Encoding::UTF_16LE
@@ -99,7 +105,11 @@ module Helpers
99
105
  elsif data.encoding != UTF_8
100
106
  data = data.encode UTF_8
101
107
  end
102
- [].tap {|lines| data.each_line {|line| lines << line.rstrip } }
108
+ if trim_end
109
+ [].tap {|lines| data.each_line {|line| lines << line.rstrip } }
110
+ else
111
+ [].tap {|lines| data.each_line {|line| lines << line.chomp } }
112
+ end
103
113
  end
104
114
 
105
115
  # Internal: Efficiently checks whether the specified String resembles a URI
@@ -110,29 +120,17 @@ module Helpers
110
120
  # str - the String to check
111
121
  #
112
122
  # returns true if the String is a URI, false if it is not
113
- def self.uriish? str
123
+ def uriish? str
114
124
  (str.include? ':') && (UriSniffRx.match? str)
115
125
  end
116
126
 
117
- # Internal: Efficiently retrieves the URI prefix of the specified String
118
- #
119
- # Uses the Asciidoctor::UriSniffRx regex to match the URI prefix in the
120
- # specified String (e.g., http://), if present.
121
- #
122
- # str - the String to check
123
- #
124
- # returns the string URI prefix if the string is a URI, otherwise nil
125
- def self.uri_prefix str
126
- (str.include? ':') && UriSniffRx =~ str ? $& : nil
127
- end
128
-
129
127
  # Internal: Encode a URI component String for safe inclusion in a URI.
130
128
  #
131
129
  # str - the URI component String to encode
132
130
  #
133
131
  # Returns the String with all reserved URI characters encoded (e.g., /, &, =, space, etc).
134
132
  if RUBY_ENGINE == 'opal'
135
- def self.encode_uri_component str
133
+ def encode_uri_component str
136
134
  # patch necessary to adhere with RFC-3986 (and thus CGI.escape)
137
135
  # see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description
138
136
  %x(
@@ -143,17 +141,17 @@ module Helpers
143
141
  end
144
142
  else
145
143
  CGI = ::CGI
146
- def self.encode_uri_component str
144
+ def encode_uri_component str
147
145
  CGI.escape str
148
146
  end
149
147
  end
150
148
 
151
- # Internal: Encode a URI String (namely the path portion).
149
+ # Internal: Apply URI path encoding to spaces in the specified string (i.e., convert spaces to %20).
152
150
  #
153
151
  # str - the String to encode
154
152
  #
155
- # Returns the String with all spaces replaced with %20.
156
- def self.encode_uri str
153
+ # Returns the specified String with all spaces replaced with %20.
154
+ def encode_spaces_in_uri str
157
155
  (str.include? ' ') ? (str.gsub ' ', '%20') : str
158
156
  end
159
157
 
@@ -167,7 +165,7 @@ module Helpers
167
165
  # # => "part1/chapter1"
168
166
  #
169
167
  # Returns the String filename with the file extension removed
170
- def self.rootname filename
168
+ def rootname filename
171
169
  if (last_dot_idx = filename.rindex '.')
172
170
  (filename.index '/', last_dot_idx) ? filename : (filename.slice 0, last_dot_idx)
173
171
  else
@@ -190,7 +188,7 @@ module Helpers
190
188
  # # => "tiger"
191
189
  #
192
190
  # Returns the String filename with leading directories removed and, if specified, the extension removed
193
- def self.basename filename, drop_ext = nil
191
+ def basename filename, drop_ext = nil
194
192
  if drop_ext
195
193
  ::File.basename filename, (drop_ext == true ? (extname filename) : drop_ext)
196
194
  else
@@ -203,7 +201,7 @@ module Helpers
203
201
  # path - The path String to check; expects a posix path
204
202
  #
205
203
  # Returns true if the path has a file extension, false otherwise
206
- def self.extname? path
204
+ def extname? path
207
205
  (last_dot_idx = path.rindex '.') && !(path.index '/', last_dot_idx)
208
206
  end
209
207
 
@@ -217,7 +215,7 @@ module Helpers
217
215
  #
218
216
  # Returns the String file extension (with the leading dot included) or the fallback value if the path has no file extension.
219
217
  if ::File::ALT_SEPARATOR
220
- def self.extname path, fallback = ''
218
+ def extname path, fallback = ''
221
219
  if (last_dot_idx = path.rindex '.')
222
220
  (path.index '/', last_dot_idx) || (path.index ::File::ALT_SEPARATOR, last_dot_idx) ? fallback : (path.slice last_dot_idx, path.length)
223
221
  else
@@ -225,7 +223,7 @@ module Helpers
225
223
  end
226
224
  end
227
225
  else
228
- def self.extname path, fallback = ''
226
+ def extname path, fallback = ''
229
227
  if (last_dot_idx = path.rindex '.')
230
228
  (path.index '/', last_dot_idx) ? fallback : (path.slice last_dot_idx, path.length)
231
229
  else
@@ -235,7 +233,7 @@ module Helpers
235
233
  end
236
234
 
237
235
  # Internal: Make a directory, ensuring all parent directories exist.
238
- def self.mkdir_p dir
236
+ def mkdir_p dir
239
237
  unless ::File.directory? dir
240
238
  unless (parent_dir = ::File.dirname dir) == '.'
241
239
  mkdir_p parent_dir
@@ -252,13 +250,14 @@ module Helpers
252
250
  'M' => 1000, 'CM' => 900, 'D' => 500, 'CD' => 400, 'C' => 100, 'XC' => 90,
253
251
  'L' => 50, 'XL' => 40, 'X' => 10, 'IX' => 9, 'V' => 5, 'IV' => 4, 'I' => 1
254
252
  }
253
+ private_constant :ROMAN_NUMERALS
255
254
 
256
255
  # Internal: Converts an integer to a Roman numeral.
257
256
  #
258
257
  # val - the [Integer] value to convert
259
258
  #
260
259
  # Returns the [String] roman numeral for this integer
261
- def self.int_to_roman val
260
+ def int_to_roman val
262
261
  ROMAN_NUMERALS.map do |l, i|
263
262
  repeat, val = val.divmod i
264
263
  l * repeat
@@ -272,7 +271,7 @@ module Helpers
272
271
  # current - the value to increment as a String or Integer
273
272
  #
274
273
  # returns the next value in the sequence according to the current value's type
275
- def self.nextval current
274
+ def nextval current
276
275
  if ::Integer === current
277
276
  current + 1
278
277
  else
@@ -291,14 +290,14 @@ module Helpers
291
290
  #
292
291
  # Returns a Class if the specified object is a Class (but not a Module) or
293
292
  # a String that resolves to a Class; otherwise, nil
294
- def self.resolve_class object
293
+ def resolve_class object
295
294
  ::Class === object ? object : (::String === object ? (class_for_name object) : nil)
296
295
  end
297
296
 
298
297
  # Internal: Resolves a Class object (not a Module) for the qualified name.
299
298
  #
300
299
  # Returns Class
301
- def self.class_for_name qualified_name
300
+ def class_for_name qualified_name
302
301
  raise unless ::Class === (resolved = ::Object.const_get qualified_name, false)
303
302
  resolved
304
303
  rescue
@@ -39,7 +39,7 @@ class Inline < AbstractNode
39
39
  #
40
40
  # Returns the [String] value of the alt attribute.
41
41
  def alt
42
- attr 'alt'
42
+ (attr 'alt') || ''
43
43
  end
44
44
 
45
45
  # For a reference node (:ref or :bibref), the text is the reftext (and the reftext attribute is not set).
@@ -1,117 +1,117 @@
1
1
  module Asciidoctor
2
- module_function
2
+ class << self
3
+ # Public: Parse the AsciiDoc source input into a {Document}
4
+ #
5
+ # Accepts input as an IO (or StringIO), String or String Array object. If the
6
+ # input is a File, the object is expected to be opened for reading and is not
7
+ # closed afterwards by this method. Information about the file (filename,
8
+ # directory name, etc) gets assigned to attributes on the Document object.
9
+ #
10
+ # input - the AsciiDoc source as a IO, String or Array.
11
+ # options - a String, Array or Hash of options to control processing (default: {})
12
+ # String and Array values are converted into a Hash.
13
+ # See {Document#initialize} for details about these options.
14
+ #
15
+ # Returns the Document
16
+ def load input, options = {}
17
+ options = options.merge
3
18
 
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
19
+ if (timings = options[:timings])
20
+ timings.start :read
21
+ end
23
22
 
24
- if (logger = options[:logger]) && logger != LoggerManager.logger
25
- LoggerManager.logger = logger
26
- end
23
+ if (logger = options[:logger]) && logger != LoggerManager.logger
24
+ LoggerManager.logger = logger
25
+ end
27
26
 
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
27
+ if !(attrs = options[:attributes])
28
+ attrs = {}
29
+ elsif ::Hash === attrs
30
+ attrs = attrs.merge
31
+ elsif (defined? ::Java::JavaUtil::Map) && ::Java::JavaUtil::Map === attrs
32
+ attrs = attrs.dup
33
+ elsif ::Array === attrs
34
+ attrs = {}.tap do |accum|
35
+ attrs.each do |entry|
36
+ k, _, v = entry.partition '='
37
+ accum[k] = v
38
+ end
39
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
40
+ elsif ::String === attrs
41
+ # condense and convert non-escaped spaces to null, unescape escaped spaces, then split on null
42
+ attrs = {}.tap do |accum|
43
+ attrs.gsub(SpaceDelimiterRx, '\1' + NULL).gsub(EscapedSpaceRx, '\1').split(NULL).each do |entry|
44
+ k, _, v = entry.partition '='
45
+ accum[k] = v
46
+ end
47
47
  end
48
+ elsif (attrs.respond_to? :keys) && (attrs.respond_to? :[])
49
+ # coerce attrs to a real Hash
50
+ attrs = {}.tap {|accum| attrs.keys.each {|k| accum[k] = attrs[k] } }
51
+ else
52
+ raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors.join ' < '})
48
53
  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
54
 
56
- if ::File === input
57
- options[:input_mtime] = input.mtime
58
- # NOTE defer setting infile and indir until we get a better sense of their purpose
59
- # TODO cli checks if input path can be read and is file, but might want to add check to API too
60
- attrs['docfile'] = input_path = ::File.absolute_path input.path
61
- attrs['docdir'] = ::File.dirname input_path
62
- attrs['docname'] = Helpers.basename input_path, (attrs['docfilesuffix'] = Helpers.extname input_path)
63
- source = input.read
64
- elsif input.respond_to? :read
65
- # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
66
- # just fail the rewind operation silently to handle all cases
67
- input.rewind rescue nil
68
- source = input.read
69
- elsif ::String === input
70
- source = input
71
- elsif ::Array === input
72
- source = input.drop 0
73
- elsif input
74
- raise ::ArgumentError, %(unsupported input type: #{input.class})
75
- end
55
+ if ::File === input
56
+ options[:input_mtime] = input.mtime
57
+ # NOTE defer setting infile and indir until we get a better sense of their purpose
58
+ # TODO cli checks if input path can be read and is file, but might want to add check to API too
59
+ attrs['docfile'] = input_path = ::File.absolute_path input.path
60
+ attrs['docdir'] = ::File.dirname input_path
61
+ attrs['docname'] = Helpers.basename input_path, (attrs['docfilesuffix'] = Helpers.extname input_path)
62
+ source = input.read
63
+ elsif input.respond_to? :read
64
+ # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
65
+ # just fail the rewind operation silently to handle all cases
66
+ input.rewind rescue nil
67
+ source = input.read
68
+ elsif ::String === input
69
+ source = input
70
+ elsif ::Array === input
71
+ source = input.drop 0
72
+ elsif input
73
+ raise ::ArgumentError, %(unsupported input type: #{input.class})
74
+ end
76
75
 
77
- if timings
78
- timings.record :read
79
- timings.start :parse
80
- end
76
+ if timings
77
+ timings.record :read
78
+ timings.start :parse
79
+ end
81
80
 
82
- options[:attributes] = attrs
83
- doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse
81
+ options[:attributes] = attrs
82
+ doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse
84
83
 
85
- timings.record :parse if timings
86
- doc
87
- rescue => ex
88
- begin
89
- context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
90
- if ex.respond_to? :exception
91
- # The original message must be explicitly preserved when wrapping a Ruby exception
92
- wrapped_ex = ex.exception %(#{context} - #{ex.message})
93
- # JRuby automatically sets backtrace; MRI did not until 2.6
94
- wrapped_ex.set_backtrace ex.backtrace
95
- else
96
- # Likely a Java exception class
97
- wrapped_ex = ex.class.new context, ex
98
- wrapped_ex.stack_trace = ex.stack_trace
84
+ timings.record :parse if timings
85
+ doc
86
+ rescue => ex
87
+ begin
88
+ context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
89
+ if ex.respond_to? :exception
90
+ # The original message must be explicitly preserved when wrapping a Ruby exception
91
+ wrapped_ex = ex.exception %(#{context} - #{ex.message})
92
+ # JRuby automatically sets backtrace; MRI did not until 2.6
93
+ wrapped_ex.set_backtrace ex.backtrace
94
+ else
95
+ # Likely a Java exception class
96
+ wrapped_ex = ex.class.new context, ex
97
+ wrapped_ex.stack_trace = ex.stack_trace
98
+ end
99
+ rescue
100
+ wrapped_ex = ex
99
101
  end
100
- rescue
101
- wrapped_ex = ex
102
+ raise wrapped_ex
102
103
  end
103
- raise wrapped_ex
104
- end
105
104
 
106
- # Public: Parse the contents of the AsciiDoc source file into an Asciidoctor::Document
107
- #
108
- # input - the String AsciiDoc source filename
109
- # options - a String, Array or Hash of options to control processing (default: {})
110
- # String and Array values are converted into a Hash.
111
- # See Asciidoctor::Document#initialize for details about options.
112
- #
113
- # Returns the Asciidoctor::Document
114
- def load_file filename, options = {}
115
- ::File.open(filename, FILE_READ_MODE) {|file| load file, options }
105
+ # Public: Parse the contents of the AsciiDoc source file into an Asciidoctor::Document
106
+ #
107
+ # input - the String AsciiDoc source filename
108
+ # options - a String, Array or Hash of options to control processing (default: {})
109
+ # String and Array values are converted into a Hash.
110
+ # See Asciidoctor::Document#initialize for details about options.
111
+ #
112
+ # Returns the Asciidoctor::Document
113
+ def load_file filename, options = {}
114
+ ::File.open(filename, FILE_READ_MODE) {|file| load file, options }
115
+ end
116
116
  end
117
117
  end