asciidoctor 2.0.10 → 2.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +211 -22
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +12 -13
  5. data/README-fr.adoc +11 -15
  6. data/README-jp.adoc +9 -17
  7. data/README-zh_CN.adoc +17 -18
  8. data/README.adoc +140 -132
  9. data/asciidoctor.gemspec +6 -6
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-be.adoc +23 -0
  12. data/data/locale/attributes-bg.adoc +4 -3
  13. data/data/locale/attributes-ca.adoc +6 -5
  14. data/data/locale/attributes-cs.adoc +4 -3
  15. data/data/locale/attributes-da.adoc +6 -5
  16. data/data/locale/attributes-de.adoc +4 -4
  17. data/data/locale/attributes-en.adoc +4 -4
  18. data/data/locale/attributes-es.adoc +6 -5
  19. data/data/locale/attributes-fa.adoc +4 -3
  20. data/data/locale/attributes-fi.adoc +4 -3
  21. data/data/locale/attributes-fr.adoc +6 -5
  22. data/data/locale/attributes-hu.adoc +4 -3
  23. data/data/locale/attributes-id.adoc +4 -3
  24. data/data/locale/attributes-it.adoc +6 -5
  25. data/data/locale/attributes-ja.adoc +4 -3
  26. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  27. data/data/locale/attributes-nb.adoc +4 -3
  28. data/data/locale/attributes-nl.adoc +6 -5
  29. data/data/locale/attributes-nn.adoc +4 -3
  30. data/data/locale/attributes-pl.adoc +8 -7
  31. data/data/locale/attributes-pt.adoc +6 -5
  32. data/data/locale/attributes-pt_BR.adoc +6 -5
  33. data/data/locale/attributes-ro.adoc +4 -3
  34. data/data/locale/attributes-ru.adoc +6 -5
  35. data/data/locale/attributes-sr.adoc +4 -4
  36. data/data/locale/attributes-sr_Latn.adoc +4 -4
  37. data/data/locale/attributes-sv.adoc +4 -4
  38. data/data/locale/attributes-tr.adoc +4 -3
  39. data/data/locale/attributes-uk.adoc +6 -5
  40. data/data/locale/attributes-zh_CN.adoc +4 -3
  41. data/data/locale/attributes-zh_TW.adoc +4 -3
  42. data/data/reference/syntax.adoc +14 -7
  43. data/data/stylesheets/asciidoctor-default.css +27 -28
  44. data/lib/asciidoctor.rb +38 -12
  45. data/lib/asciidoctor/abstract_block.rb +9 -4
  46. data/lib/asciidoctor/abstract_node.rb +16 -6
  47. data/lib/asciidoctor/attribute_list.rb +64 -72
  48. data/lib/asciidoctor/cli/invoker.rb +2 -0
  49. data/lib/asciidoctor/cli/options.rb +10 -9
  50. data/lib/asciidoctor/convert.rb +167 -162
  51. data/lib/asciidoctor/converter.rb +13 -12
  52. data/lib/asciidoctor/converter/docbook5.rb +29 -12
  53. data/lib/asciidoctor/converter/html5.rb +69 -46
  54. data/lib/asciidoctor/converter/manpage.rb +68 -49
  55. data/lib/asciidoctor/converter/template.rb +3 -0
  56. data/lib/asciidoctor/document.rb +43 -50
  57. data/lib/asciidoctor/extensions.rb +2 -4
  58. data/lib/asciidoctor/helpers.rb +20 -15
  59. data/lib/asciidoctor/load.rb +102 -101
  60. data/lib/asciidoctor/parser.rb +40 -32
  61. data/lib/asciidoctor/path_resolver.rb +14 -12
  62. data/lib/asciidoctor/reader.rb +22 -13
  63. data/lib/asciidoctor/rx.rb +7 -6
  64. data/lib/asciidoctor/substitutors.rb +78 -57
  65. data/lib/asciidoctor/syntax_highlighter.rb +8 -5
  66. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  67. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  68. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  69. data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -7
  70. data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -19
  71. data/lib/asciidoctor/table.rb +52 -23
  72. data/lib/asciidoctor/version.rb +1 -1
  73. data/man/asciidoctor.1 +8 -8
  74. data/man/asciidoctor.adoc +4 -4
  75. metadata +16 -15
@@ -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 }]
@@ -259,12 +259,14 @@ class Document < AbstractBlock
259
259
  options[:catalog_assets] = true if parent_doc.options[:catalog_assets]
260
260
  @catalog = parent_doc.catalog.merge footnotes: []
261
261
  # QUESTION should we support setting attribute in parent document from nested document?
262
- # NOTE we must dup or else all the assignments to the overrides clobbers the real attributes
263
- @attribute_overrides = attr_overrides = parent_doc.attributes.merge
264
- parent_doctype = attr_overrides.delete 'doctype'
262
+ @attribute_overrides = attr_overrides = (parent_doc.instance_variable_get :@attribute_overrides).merge parent_doc.attributes
265
263
  attr_overrides.delete 'compat-mode'
264
+ parent_doctype = attr_overrides.delete 'doctype'
265
+ attr_overrides.delete 'notitle'
266
+ attr_overrides.delete 'showtitle'
267
+ # QUESTION if toc is hard unset in parent document, should it be hard unset in nested document?
266
268
  attr_overrides.delete 'toc'
267
- attr_overrides.delete 'toc-placement'
269
+ @attributes['toc-placement'] = (attr_overrides.delete 'toc-placement') || 'auto'
268
270
  attr_overrides.delete 'toc-position'
269
271
  @safe = parent_doc.safe
270
272
  @attributes['compat-mode'] = '' if (@compat_mode = parent_doc.compat_mode)
@@ -326,7 +328,7 @@ class Document < AbstractBlock
326
328
  @sourcemap = options[:sourcemap]
327
329
  @timings = options.delete :timings
328
330
  @path_resolver = PathResolver.new
329
- initialize_extensions = (defined? ::Asciidoctor::Extensions) ? true : nil
331
+ initialize_extensions = (defined? ::Asciidoctor::Extensions) || (options.key? :extensions) ? ::Asciidoctor::Extensions : nil
330
332
  @extensions = nil # initialize furthur down if initialize_extensions is true
331
333
  options[:standalone] = options[:header_footer] if (options.key? :header_footer) && !(options.key? :standalone)
332
334
  end
@@ -339,61 +341,46 @@ class Document < AbstractBlock
339
341
  (@options = options).freeze
340
342
 
341
343
  attrs = @attributes
342
- #attrs['encoding'] = 'UTF-8'
343
- attrs['sectids'] = ''
344
- attrs['toc-placement'] = 'auto'
344
+ unless parent_doc
345
+ attrs['attribute-undefined'] = Compliance.attribute_undefined
346
+ attrs['attribute-missing'] = Compliance.attribute_missing
347
+ attrs.update DEFAULT_ATTRIBUTES
348
+ # TODO if lang attribute is set, @safe mode < SafeMode::SERVER, and !parent_doc,
349
+ # load attributes from data/locale/attributes-<lang>.adoc
350
+ end
351
+
345
352
  if standalone
346
- attrs['copycss'] = ''
347
353
  # sync embedded attribute with :standalone option value
348
354
  attr_overrides['embedded'] = nil
355
+ attrs['copycss'] = ''
356
+ attrs['iconfont-remote'] = ''
357
+ attrs['stylesheet'] = ''
358
+ attrs['webfonts'] = ''
349
359
  else
350
- attrs['notitle'] = ''
351
360
  # sync embedded attribute with :standalone option value
352
361
  attr_overrides['embedded'] = ''
362
+ if (attr_overrides.key? 'showtitle') && (attr_overrides.keys & %w(notitle showtitle))[-1] == 'showtitle'
363
+ attr_overrides['notitle'] = { nil => '', false => '@', '@' => false}[attr_overrides['showtitle']]
364
+ elsif attr_overrides.key? 'notitle'
365
+ attr_overrides['showtitle'] = { nil => '', false => '@', '@' => false}[attr_overrides['notitle']]
366
+ else
367
+ attrs['notitle'] = ''
368
+ end
353
369
  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
370
 
382
371
  attr_overrides['asciidoctor'] = ''
383
372
  attr_overrides['asciidoctor-version'] = ::Asciidoctor::VERSION
384
373
 
385
374
  attr_overrides['safe-mode-name'] = (safe_mode_name = SafeMode.name_for_value @safe)
386
- attr_overrides["safe-mode-#{safe_mode_name}"] = ''
375
+ attr_overrides[%(safe-mode-#{safe_mode_name})] = ''
387
376
  attr_overrides['safe-mode-level'] = @safe
388
377
 
389
- # the only way to set the max-include-depth attribute is via the API; default to 64 like AsciiDoc Python
378
+ # the only way to set the max-include-depth attribute is via the API; default to 64 like AsciiDoc.py
390
379
  attr_overrides['max-include-depth'] ||= 64
391
380
 
392
381
  # the only way to set the allow-uri-read attribute is via the API; disabled by default
393
382
  attr_overrides['allow-uri-read'] ||= nil
394
383
 
395
- attr_overrides['user-home'] = USER_HOME
396
-
397
384
  # remap legacy attribute names
398
385
  attr_overrides['sectnums'] = attr_overrides.delete 'numbered' if attr_overrides.key? 'numbered'
399
386
  attr_overrides['hardbreaks-option'] = attr_overrides.delete 'hardbreaks' if attr_overrides.key? 'hardbreaks'
@@ -429,7 +416,7 @@ class Document < AbstractBlock
429
416
  attr_overrides['docfile'] = attr_overrides['docfile'][(attr_overrides['docdir'].length + 1)..-1]
430
417
  end
431
418
  attr_overrides['docdir'] = ''
432
- attr_overrides['user-home'] = '.'
419
+ attr_overrides['user-home'] ||= '.'
433
420
  if @safe >= SafeMode::SECURE
434
421
  attr_overrides['max-attribute-value-size'] = 4096 unless attr_overrides.key? 'max-attribute-value-size'
435
422
  # assign linkcss (preventing css embedding) unless explicitly disabled from the commandline or API
@@ -438,6 +425,8 @@ class Document < AbstractBlock
438
425
  # restrict document from enabling icons
439
426
  attr_overrides['icons'] ||= nil
440
427
  end
428
+ else
429
+ attr_overrides['user-home'] ||= USER_HOME
441
430
  end
442
431
 
443
432
  # the only way to set the max-attribute-value-size attribute is via the API; disabled by default
@@ -505,10 +494,10 @@ class Document < AbstractBlock
505
494
  ::AsciidoctorJ::Extensions::ExtensionRegistry === ext_registry)
506
495
  @extensions = ext_registry.activate self
507
496
  end
508
- elsif ::Proc === (ext_block = options[:extensions])
497
+ elsif (ext_block = options[:extensions]).nil?
498
+ @extensions = Extensions::Registry.new.activate self unless Extensions.groups.empty?
499
+ elsif ::Proc === ext_block
509
500
  @extensions = Extensions.create(&ext_block).activate self
510
- elsif !Extensions.groups.empty?
511
- @extensions = Extensions::Registry.new.activate self
512
501
  end
513
502
  end
514
503
 
@@ -577,13 +566,17 @@ class Document < AbstractBlock
577
566
  # returns the next number in the sequence for the specified counter
578
567
  def counter name, seed = nil
579
568
  return @parent_document.counter name, seed if @parent_document
580
- if (attr_seed = !(attr_val = @attributes[name]).nil_or_empty?) && (@counters.key? name)
581
- @attributes[name] = @counters[name] = Helpers.nextval attr_val
569
+ if (locked = attribute_locked? name) && (curr_val = @counters[name])
570
+ next_val = @counters[name] = Helpers.nextval curr_val
571
+ elsif !(curr_val = @attributes[name]).nil_or_empty?
572
+ next_val = @counters[name] = Helpers.nextval curr_val
582
573
  elsif seed
583
- @attributes[name] = @counters[name] = seed == seed.to_i.to_s ? seed.to_i : seed
574
+ next_val = @counters[name] = seed == seed.to_i.to_s ? seed.to_i : seed
584
575
  else
585
- @attributes[name] = @counters[name] = Helpers.nextval attr_seed ? attr_val : 0
576
+ next_val = @counters[name] = 1
586
577
  end
578
+ @attributes[name] = next_val unless locked
579
+ next_val
587
580
  end
588
581
 
589
582
  # Public: Increment the specified counter and store it in the block's attributes
@@ -772,7 +765,7 @@ class Document < AbstractBlock
772
765
  end
773
766
 
774
767
  def notitle
775
- !@attributes.key?('showtitle') && @attributes.key?('notitle')
768
+ @attributes.key? 'notitle'
776
769
  end
777
770
 
778
771
  def noheader
@@ -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
 
@@ -1021,8 +1021,6 @@ module Extensions
1021
1021
  else
1022
1022
  @docinfo_processor_extensions
1023
1023
  end
1024
- else
1025
- nil
1026
1024
  end
1027
1025
  end
1028
1026
 
@@ -56,25 +56,27 @@ module Helpers
56
56
  # If a BOM is found at the beginning of the data, a best attempt is made to
57
57
  # encode it to UTF-8 from the specified source encoding.
58
58
  #
59
- # 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)
60
62
  #
61
63
  # returns a String Array of prepared lines
62
- def prepare_source_array data
64
+ def prepare_source_array data, trim_end = true
63
65
  return [] if data.empty?
64
66
  if (leading_2_bytes = (leading_bytes = (first = data[0]).unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
65
67
  data[0] = first.byteslice 2, first.bytesize
66
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
67
- 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 }
68
70
  elsif leading_2_bytes == BOM_BYTES_UTF_16BE
69
71
  data[0] = first.byteslice 2, first.bytesize
70
- 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 }
71
73
  elsif leading_bytes == BOM_BYTES_UTF_8
72
74
  data[0] = first.byteslice 3, first.bytesize
73
75
  end
74
76
  if first.encoding == UTF_8
75
- data.map {|line| line.rstrip }
77
+ trim_end ? data.map {|line| line.rstrip } : data.map {|line| line.chomp }
76
78
  else
77
- 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 }
78
80
  end
79
81
  end
80
82
 
@@ -86,10 +88,12 @@ module Helpers
86
88
  # If a BOM is found at the beginning of the data, a best attempt is made to
87
89
  # encode it to UTF-8 from the specified source encoding.
88
90
  #
89
- # 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)
90
94
  #
91
95
  # returns a String Array of prepared lines
92
- def prepare_source_string data
96
+ def prepare_source_string data, trim_end = true
93
97
  return [] if data.nil_or_empty?
94
98
  if (leading_2_bytes = (leading_bytes = data.unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
95
99
  data = (data.byteslice 2, data.bytesize).encode UTF_8, ::Encoding::UTF_16LE
@@ -101,7 +105,11 @@ module Helpers
101
105
  elsif data.encoding != UTF_8
102
106
  data = data.encode UTF_8
103
107
  end
104
- [].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
105
113
  end
106
114
 
107
115
  # Internal: Efficiently checks whether the specified String resembles a URI
@@ -266,13 +274,10 @@ module Helpers
266
274
  def nextval current
267
275
  if ::Integer === current
268
276
  current + 1
277
+ elsif (intval = current.to_i).to_s == current.to_s
278
+ intval + 1
269
279
  else
270
- intval = current.to_i
271
- if intval.to_s != current.to_s
272
- (current[0].ord + 1).chr
273
- else
274
- intval + 1
275
- end
280
+ current.succ
276
281
  end
277
282
  end
278
283
 
@@ -1,117 +1,118 @@
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 (options.key? :logger) && (logger = options[:logger]) != LoggerManager.logger
24
+ LoggerManager.logger = logger || NullLogger.new
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
+ # File#mtime on JRuby for Windows doesn't honor TZ environment variable; see https://github.com/jruby/jruby/issues/6659
57
+ options[:input_mtime] = RUBY_ENGINE == 'jruby' ? (::Time.at input.mtime.to_i) : 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
76
76
 
77
- if timings
78
- timings.record :read
79
- timings.start :parse
80
- end
77
+ if timings
78
+ timings.record :read
79
+ timings.start :parse
80
+ end
81
81
 
82
- options[:attributes] = attrs
83
- doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse
82
+ options[:attributes] = attrs
83
+ doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse
84
84
 
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
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
99
+ end
100
+ rescue
101
+ wrapped_ex = ex
99
102
  end
100
- rescue
101
- wrapped_ex = ex
103
+ raise wrapped_ex
102
104
  end
103
- raise wrapped_ex
104
- end
105
105
 
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 }
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 }
116
+ end
116
117
  end
117
118
  end