asciidoctor 2.0.9 → 2.0.14

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +193 -16
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +12 -13
  5. data/README-fr.adoc +11 -15
  6. data/README-jp.adoc +242 -185
  7. data/README-zh_CN.adoc +17 -18
  8. data/README.adoc +133 -131
  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 +30 -30
  44. data/lib/asciidoctor.rb +40 -14
  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 +63 -71
  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 +5 -9
  53. data/lib/asciidoctor/converter/html5.rb +58 -45
  54. data/lib/asciidoctor/converter/manpage.rb +61 -38
  55. data/lib/asciidoctor/converter/template.rb +3 -0
  56. data/lib/asciidoctor/document.rb +44 -51
  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 +20 -13
  63. data/lib/asciidoctor/rx.rb +7 -6
  64. data/lib/asciidoctor/substitutors.rb +69 -50
  65. data/lib/asciidoctor/syntax_highlighter.rb +15 -7
  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
@@ -1217,7 +1210,7 @@ class Document < AbstractBlock
1217
1210
  when '', 'font'
1218
1211
  else
1219
1212
  attrs['icons'] = ''
1220
- attrs['icontype'] = icons_val
1213
+ attrs['icontype'] = icons_val unless icons_val == 'image'
1221
1214
  end
1222
1215
  end
1223
1216
 
@@ -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