asciidoctor 2.0.7 → 2.0.12

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 (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
@@ -38,12 +38,13 @@ class AbstractBlock < AbstractNode
38
38
  @blocks = []
39
39
  @subs = []
40
40
  @id = @title = @caption = @numeral = @style = @default_subs = @source_location = nil
41
- case context
42
- when :document, :section
41
+ if context == :document || context == :section
43
42
  @level = @next_section_index = 0
44
43
  @next_section_ordinal = 1
44
+ elsif AbstractBlock === parent
45
+ @level = parent.level
45
46
  else
46
- @level = AbstractBlock === parent ? parent.level : nil
47
+ @level = nil
47
48
  end
48
49
  end
49
50
 
@@ -140,6 +141,11 @@ class AbstractBlock < AbstractNode
140
141
  (Integer @numeral) rescue @numeral
141
142
  end
142
143
 
144
+ # Deprecated: Legacy property to set the numeral of this section by coercing the value to a String.
145
+ def number= val
146
+ @numeral = val.to_s
147
+ end
148
+
143
149
  # Public: Walk the document tree and find all block-level nodes that match the specified selector (context, style, id,
144
150
  # role, and/or custom filter).
145
151
  #
@@ -222,6 +228,8 @@ class AbstractBlock < AbstractNode
222
228
  text = sub_specialchars text
223
229
  (ReplaceableTextRx.match? text) ? (sub_replacements text) : text
224
230
  end
231
+ else
232
+ ''
225
233
  end
226
234
  end
227
235
 
@@ -335,17 +343,17 @@ class AbstractBlock < AbstractNode
335
343
  if (val = reftext) && !val.empty?
336
344
  val
337
345
  # NOTE xrefstyle only applies to blocks with a title and a caption or number
338
- elsif xrefstyle && @title && @caption
346
+ elsif xrefstyle && @title && !@caption.nil_or_empty?
339
347
  case xrefstyle
340
348
  when 'full'
341
349
  quoted_title = sub_placeholder (sub_quotes @document.compat_mode ? %q(``%s'') : '"`%s`"'), title
342
- if @numeral && (caption_attr_name = CAPTION_ATTR_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
350
+ if @numeral && (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
343
351
  %(#{prefix} #{@numeral}, #{quoted_title})
344
352
  else
345
353
  %(#{@caption.chomp '. '}, #{quoted_title})
346
354
  end
347
355
  when 'short'
348
- if @numeral && (caption_attr_name = CAPTION_ATTR_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
356
+ if @numeral && (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
349
357
  %(#{prefix} #{@numeral})
350
358
  else
351
359
  @caption.chomp '. '
@@ -369,15 +377,15 @@ class AbstractBlock < AbstractNode
369
377
  # The parts of a complete caption are: <prefix> <number>. <title>
370
378
  # This partial caption represents the part the precedes the title.
371
379
  #
372
- # value - The explicit String caption to assign to this block (default: nil).
373
- # caption_context - The Symbol context to use when resolving caption-related attributes.
374
- # If not provided, the name of the context for this block is used.
380
+ # value - The String caption to assign to this block or nil to use document attribute.
381
+ # caption_context - The Symbol context to use when resolving caption-related attributes. If not provided, the name of
382
+ # the context for this block is used. Only certain contexts allow the caption to be looked up.
375
383
  # (default: @context)
376
384
  #
377
385
  # Returns nothing.
378
- def assign_caption value = nil, caption_context = @context
386
+ def assign_caption value, caption_context = @context
379
387
  unless @caption || !@title || (@caption = value || @document.attributes['caption'])
380
- if (attr_name = CAPTION_ATTR_NAMES[caption_context]) && (prefix = @document.attributes[attr_name])
388
+ if (attr_name = CAPTION_ATTRIBUTE_NAMES[caption_context]) && (prefix = @document.attributes[attr_name])
381
389
  @caption = %(#{prefix} #{@numeral = @document.increment_and_store_counter %(#{caption_context}-number), self}. )
382
390
  nil
383
391
  end
@@ -65,7 +65,7 @@ class AbstractNode
65
65
  #
66
66
  # parent - The Block to set as the parent of this Block
67
67
  #
68
- # Returns the new parent Block associated with this Block
68
+ # Returns the value of the parent argument
69
69
  def parent= parent
70
70
  @parent, @document = parent, parent.document
71
71
  end
@@ -161,10 +161,10 @@ class AbstractNode
161
161
  nil
162
162
  end
163
163
 
164
- # Public: Retrieve the Set of option names that are set on this node
164
+ # Public: Retrieve the Set of option names that are enabled on this node
165
165
  #
166
166
  # Returns a [Set] of option names
167
- def options
167
+ def enabled_options
168
168
  ::Set.new.tap {|accum| @attributes.each_key {|k| accum << (k.slice 0, k.length - 7) if k.to_s.end_with? '-option' } }
169
169
  end
170
170
 
@@ -216,6 +216,15 @@ class AbstractNode
216
216
  (val = @attributes['role']) ? (%( #{val} ).include? %( #{name} )) : false
217
217
  end
218
218
 
219
+ # Public: Sets the value of the role attribute on this ndoe.
220
+ #
221
+ # names - A single role name, a space-separated String of role names, or an Array of role names
222
+ #
223
+ # Returns the value of the names argument
224
+ def role= names
225
+ @attributes['role'] = (::Array === names) ? (names.join ' ') : names
226
+ end
227
+
219
228
  # Public: Adds the given role directly to this node.
220
229
  #
221
230
  # Returns a [Boolean] indicating whether the role was added.
@@ -310,14 +319,10 @@ class AbstractNode
310
319
  # Returns A String reference or data URI for the target image
311
320
  def image_uri(target_image, asset_dir_key = 'imagesdir')
312
321
  if (doc = @document).safe < SafeMode::SECURE && (doc.attr? 'data-uri')
313
- if ((Helpers.uriish? target_image) && (target_image = Helpers.encode_uri target_image)) ||
322
+ if ((Helpers.uriish? target_image) && (target_image = Helpers.encode_spaces_in_uri target_image)) ||
314
323
  (asset_dir_key && (images_base = doc.attr asset_dir_key) && (Helpers.uriish? images_base) &&
315
324
  (target_image = normalize_web_path target_image, images_base, false))
316
- if doc.attr? 'allow-uri-read'
317
- generate_data_uri_from_uri target_image, (doc.attr? 'cache-uri')
318
- else
319
- target_image
320
- end
325
+ (doc.attr? 'allow-uri-read') ? (generate_data_uri_from_uri target_image, (doc.attr? 'cache-uri')) : target_image
321
326
  else
322
327
  generate_data_uri target_image, asset_dir_key
323
328
  end
@@ -474,7 +479,7 @@ class AbstractNode
474
479
  # Returns the resolved [String] path
475
480
  def normalize_web_path(target, start = nil, preserve_uri_target = true)
476
481
  if preserve_uri_target && (Helpers.uriish? target)
477
- Helpers.encode_uri target
482
+ Helpers.encode_spaces_in_uri target
478
483
  else
479
484
  @document.path_resolver.web_path target, start
480
485
  end
@@ -518,6 +523,7 @@ class AbstractNode
518
523
  # * :normalize a Boolean that indicates whether the data should be normalized (default: false)
519
524
  # * :start the String relative base path to use when resolving the target (default: nil)
520
525
  # * :warn_on_failure a Boolean that indicates whether warnings are issued if the target cannot be read (default: true)
526
+ # * :warn_if_empty a Boolean that indicates whether a warning is issued if contents of target is empty (default: false)
521
527
  # Returns the contents of the resolved target or nil if the resolved target cannot be read
522
528
  # --
523
529
  # TODO refactor other methods in this class to use this method were possible (repurposing if necessary)
@@ -529,22 +535,22 @@ class AbstractNode
529
535
  Helpers.require_library 'open-uri/cached', 'open-uri-cached' if doc.attr? 'cache-uri'
530
536
  begin
531
537
  if opts[:normalize]
532
- (Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
538
+ contents = (Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
533
539
  else
534
- ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
540
+ contents = ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
535
541
  end
536
542
  rescue
537
543
  logger.warn %(could not retrieve contents of #{opts[:label] || 'asset'} at URI: #{target}) if opts.fetch :warn_on_failure, true
538
- return
539
544
  end
540
545
  else
541
546
  logger.warn %(cannot retrieve contents of #{opts[:label] || 'asset'} at URI: #{target} (allow-uri-read attribute not enabled)) if opts.fetch :warn_on_failure, true
542
- return
543
547
  end
544
548
  else
545
549
  target = normalize_system_path target, opts[:start], nil, target_name: (opts[:label] || 'asset')
546
- read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
550
+ contents = read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
547
551
  end
552
+ logger.warn %(contents of #{opts[:label] || 'asset'} is empty: #{target}) if contents && opts[:warn_if_empty] && contents.empty?
553
+ contents
548
554
  end
549
555
 
550
556
  # Deprecated: Check whether the specified String is a URI by
@@ -22,19 +22,20 @@ module Asciidoctor
22
22
  # => { 'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)' }
23
23
  #
24
24
  class AttributeList
25
- BACKSLASH = '\\'
26
25
  APOS = '\''
26
+ BACKSLASH = '\\'
27
+ QUOT = '"'
27
28
 
28
29
  # Public: Regular expressions for detecting the boundary of a value
29
30
  BoundaryRxs = {
30
- '"' => /.*?[^\\](?=")/,
31
+ QUOT => /.*?[^\\](?=")/,
31
32
  APOS => /.*?[^\\](?=')/,
32
33
  ',' => /.*?(?=[ \t]*(,|$))/
33
34
  }
34
35
 
35
36
  # Public: Regular expressions for unescaping quoted characters
36
37
  EscapedQuotes = {
37
- '"' => '\\"',
38
+ QUOT => '\\"',
38
39
  APOS => '\\\''
39
40
  }
40
41
 
@@ -45,7 +46,9 @@ class AttributeList
45
46
  BlankRx = /[ \t]+/
46
47
 
47
48
  # Public: Regular expressions for skipping delimiters
48
- SkipRxs = { ',' => /[ \t]*(,|$)/ }
49
+ SkipRxs = {
50
+ ',' => /[ \t]*(,|$)/
51
+ }
49
52
 
50
53
  def initialize source, block = nil, delimiter = ','
51
54
  @scanner = ::StringScanner.new source
@@ -65,8 +68,6 @@ class AttributeList
65
68
  return @attributes if @attributes
66
69
 
67
70
  @attributes = {}
68
- # QUESTION do we want to store the attribute list as the zero-index attribute?
69
- #attributes[0] = @scanner.string
70
71
  index = 0
71
72
 
72
73
  while parse_attribute index, positional_attrs
@@ -83,75 +84,73 @@ class AttributeList
83
84
  end
84
85
 
85
86
  def self.rekey attributes, positional_attrs
86
- index = 0
87
- positional_attrs.each do |key|
88
- index += 1
89
- if (val = attributes[index])
87
+ positional_attrs.each_with_index do |key, index|
88
+ if key && (val = attributes[index + 1])
90
89
  # QUESTION should we delete the positional key?
91
90
  attributes[key] = val
92
- end if key
91
+ end
93
92
  end
94
93
  attributes
95
94
  end
96
95
 
97
96
  private
98
97
 
99
- def parse_attribute index = 0, positional_attrs = []
100
- single_quoted_value = false
98
+ def parse_attribute index, positional_attrs
99
+ continue = true
101
100
  skip_blank
102
- # example: "quote"
103
- if (first = @scanner.peek(1)) == '"'
101
+ case @scanner.peek 1
102
+ # example: "quote" || "foo
103
+ when QUOT
104
104
  name = parse_attribute_value @scanner.get_byte
105
- value = nil
106
- # example: 'quote'
107
- elsif first == APOS
105
+ # example: 'quote' || 'foo
106
+ when APOS
108
107
  name = parse_attribute_value @scanner.get_byte
109
- value = nil
110
- single_quoted_value = true unless name.start_with? APOS
108
+ single_quoted = true unless name.start_with? APOS
111
109
  else
112
- name = scan_name
113
-
114
- skipped = 0
115
- c = nil
110
+ skipped = ((name = scan_name) && skip_blank) || 0
116
111
  if @scanner.eos?
117
- return false unless name
118
- else
119
- skipped = skip_blank || 0
120
- c = @scanner.get_byte
121
- end
122
-
123
- # example: quote
124
- if !c || c == @delimiter
125
- value = nil
126
- # example: Sherlock Holmes || =foo=
127
- elsif c != '=' || !name
128
- name = %(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
129
- value = nil
130
- else
131
- skip_blank
132
- if @scanner.peek(1)
133
- # example: foo="bar" || foo="ba\"zaar"
134
- if (c = @scanner.get_byte) == '"'
112
+ return unless name || (@scanner.string.rstrip.end_with? @delimiter)
113
+ # example: quote (at eos)
114
+ continue = nil
115
+ # example: quote,
116
+ elsif (c = @scanner.get_byte) == @delimiter
117
+ @scanner.unscan
118
+ elsif name
119
+ # example: foo=...
120
+ if c == '='
121
+ skip_blank
122
+ case (c = @scanner.get_byte)
123
+ # example: foo="bar" || foo="ba\"zaar" || foo="bar
124
+ when QUOT
135
125
  value = parse_attribute_value c
136
- # example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar'
137
- elsif c == APOS
126
+ # example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar' || foo='bar
127
+ when APOS
138
128
  value = parse_attribute_value c
139
- single_quoted_value = true unless value.start_with? APOS
129
+ single_quoted = true unless value.start_with? APOS
140
130
  # example: foo=,
141
- elsif c == @delimiter
131
+ when @delimiter
132
+ value = ''
133
+ @scanner.unscan
134
+ # example: foo= (at eos)
135
+ when nil
142
136
  value = ''
143
- # example: foo=bar (all spaces ignored)
137
+ # example: foo=bar || foo=None
144
138
  else
145
139
  value = %(#{c}#{scan_to_delimiter})
146
140
  return true if value == 'None'
147
141
  end
142
+ # example: foo bar
143
+ else
144
+ name = %(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
148
145
  end
146
+ # example: =foo= || !foo
147
+ else
148
+ name = %(#{c}#{scan_to_delimiter})
149
149
  end
150
150
  end
151
151
 
152
152
  if value
153
- # example: options="opt1,opt2,opt3"
154
- # opts is an alias for options
153
+ # example: options="opt1,opt2,opt3" || opts="opts1,opt2,opt3"
155
154
  case name
156
155
  when 'options', 'opts'
157
156
  if value.include? ','
@@ -161,7 +160,7 @@ class AttributeList
161
160
  @attributes[%(#{value}-option)] = '' unless value.empty?
162
161
  end
163
162
  else
164
- if single_quoted_value && @block
163
+ if single_quoted && @block
165
164
  case name
166
165
  when 'title', 'reftext'
167
166
  @attributes[name] = value
@@ -173,33 +172,26 @@ class AttributeList
173
172
  end
174
173
  end
175
174
  else
176
- resolved_name = single_quoted_value && @block ? (@block.apply_subs name) : name
175
+ name = @block.apply_subs name if single_quoted && @block
177
176
  if (positional_attr_name = positional_attrs[index])
178
- @attributes[positional_attr_name] = resolved_name
177
+ @attributes[positional_attr_name] = name
179
178
  end
180
- # QUESTION should we always assign the positional key?
181
- @attributes[index + 1] = resolved_name
182
- # QUESTION should we assign the resolved name as an attribute?
183
- #@attributes[resolved_name] = nil
179
+ # QUESTION should we assign the positional key even when it's claimed by a positional attribute?
180
+ @attributes[index + 1] = name
184
181
  end
185
182
 
186
- true
183
+ continue
187
184
  end
188
185
 
189
186
  def parse_attribute_value quote
190
187
  # empty quoted value
191
- if @scanner.peek(1) == quote
188
+ if (@scanner.peek 1) == quote
192
189
  @scanner.get_byte
193
- return ''
194
- end
195
-
196
- if (value = scan_to_quote quote)
190
+ ''
191
+ elsif (value = scan_to_quote quote)
197
192
  @scanner.get_byte
198
- if value.include? BACKSLASH
199
- value.gsub EscapedQuotes[quote], quote
200
- else
201
- value
202
- end
193
+ (value.include? BACKSLASH) ? (value.gsub EscapedQuotes[quote], quote) : value
194
+ # leading quote only
203
195
  else
204
196
  %(#{quote}#{scan_to_delimiter})
205
197
  end
@@ -42,6 +42,8 @@ module Asciidoctor
42
42
  non_posix_env = ::File::ALT_SEPARATOR == RS
43
43
  err = @err || $stderr
44
44
  show_timings = false
45
+ # NOTE in Ruby 2.7, RubyGems sets SOURCE_DATE_EPOCH if it's not set
46
+ ::ENV.delete 'SOURCE_DATE_EPOCH' if (::ENV.key? 'IGNORE_SOURCE_DATE_EPOCH') && (::Gem.respond_to? :source_date_epoch)
45
47
 
46
48
  @options.map do |key, val|
47
49
  case key
@@ -76,8 +76,8 @@ module Asciidoctor
76
76
  opts.on('-n', '--section-numbers', 'auto-number section titles in the HTML backend; disabled by default') do
77
77
  self[:attributes]['sectnums'] = ''
78
78
  end
79
- opts.on('--eruby ERUBY', ['erb', 'erubis'],
80
- 'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubis] (default: erb)') do |eruby|
79
+ opts.on('--eruby ERUBY', ['erb', 'erubi', 'erubis'],
80
+ 'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubi, erubis] (default: erb)') do |eruby|
81
81
  self[:eruby] = eruby
82
82
  end
83
83
  opts.on('-a', '--attribute name[=value]', 'a document attribute to set in the form of name, name!, or name=value pair',
@@ -205,7 +205,7 @@ module Asciidoctor
205
205
  # shave off the file to process so that options errors appear correctly
206
206
  if args.size == 1 && args[0] == '-'
207
207
  infiles << args.pop
208
- elsif
208
+ else
209
209
  args.each do |file|
210
210
  if file.start_with? '-'
211
211
  # warn, but don't panic; we may have enough to proceed, so we won't force a failure
@@ -1,193 +1,198 @@
1
1
  module Asciidoctor
2
- module_function
2
+ class << self
3
+ # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
4
+ # convert it to the specified backend format.
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
+ # If the :to_file option is true, and the input is a File, the output is
12
+ # written to a file adjacent to the input file, having an extension that
13
+ # corresponds to the backend format. Otherwise, if the :to_file option is
14
+ # specified, the file is written to that file. If :to_file is not an absolute
15
+ # path, it is resolved relative to :to_dir, if given, otherwise the
16
+ # Document#base_dir. If the target directory does not exist, it will not be
17
+ # created unless the :mkdirs option is set to true. If the file cannot be
18
+ # written because the target directory does not exist, or because it falls
19
+ # outside of the Document#base_dir in safe mode, an IOError is raised.
20
+ #
21
+ # If the output is going to be written to a file, the header and footer are
22
+ # included unless specified otherwise (writing to a file implies creating a
23
+ # standalone document). Otherwise, the header and footer are not included by
24
+ # default and the converted result is returned.
25
+ #
26
+ # input - the String AsciiDoc source filename
27
+ # options - a String, Array or Hash of options to control processing (default: {})
28
+ # String and Array values are converted into a Hash.
29
+ # See Asciidoctor::Document#initialize for details about options.
30
+ #
31
+ # Returns the Document object if the converted String is written to a
32
+ # file, otherwise the converted String
33
+ def convert input, options = {}
34
+ (options = options.merge).delete :parse
35
+ to_dir = options.delete :to_dir
36
+ mkdirs = options.delete :mkdirs
3
37
 
4
- # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
5
- # convert it to the specified backend format.
6
- #
7
- # Accepts input as an IO (or StringIO), String or String Array object. If the
8
- # input is a File, the object is expected to be opened for reading and is not
9
- # closed afterwards by this method. Information about the file (filename,
10
- # directory name, etc) gets assigned to attributes on the Document object.
11
- #
12
- # If the :to_file option is true, and the input is a File, the output is
13
- # written to a file adjacent to the input file, having an extension that
14
- # corresponds to the backend format. Otherwise, if the :to_file option is
15
- # specified, the file is written to that file. If :to_file is not an absolute
16
- # path, it is resolved relative to :to_dir, if given, otherwise the
17
- # Document#base_dir. If the target directory does not exist, it will not be
18
- # created unless the :mkdirs option is set to true. If the file cannot be
19
- # written because the target directory does not exist, or because it falls
20
- # outside of the Document#base_dir in safe mode, an IOError is raised.
21
- #
22
- # If the output is going to be written to a file, the header and footer are
23
- # included unless specified otherwise (writing to a file implies creating a
24
- # standalone document). Otherwise, the header and footer are not included by
25
- # default and the converted result is returned.
26
- #
27
- # input - the String AsciiDoc source filename
28
- # options - a String, Array or Hash of options to control processing (default: {})
29
- # String and Array values are converted into a Hash.
30
- # See Asciidoctor::Document#initialize for details about options.
31
- #
32
- # Returns the Document object if the converted String is written to a
33
- # file, otherwise the converted String
34
- def convert input, options = {}
35
- (options = options.merge).delete :parse
36
- to_dir = options.delete :to_dir
37
- mkdirs = options.delete :mkdirs
38
-
39
- case (to_file = options.delete :to_file)
40
- when true, nil
41
- unless (write_to_target = to_dir)
42
- sibling_path = ::File.absolute_path input.path if ::File === input
38
+ case (to_file = options.delete :to_file)
39
+ when true, nil
40
+ unless (write_to_target = to_dir)
41
+ sibling_path = ::File.absolute_path input.path if ::File === input
42
+ end
43
+ to_file = nil
44
+ when false
45
+ to_file = nil
46
+ when '/dev/null'
47
+ return load input, options
48
+ else
49
+ options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
43
50
  end
44
- to_file = nil
45
- when false
46
- to_file = nil
47
- when '/dev/null'
48
- return load input, options
49
- else
50
- options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
51
- end
52
51
 
53
- unless options.key? :standalone
54
- if sibling_path || write_to_target
55
- options[:standalone] = true
56
- elsif options.key? :header_footer
57
- options[:standalone] = options[:header_footer]
52
+ unless options.key? :standalone
53
+ if sibling_path || write_to_target
54
+ options[:standalone] = options.fetch :header_footer, true
55
+ elsif options.key? :header_footer
56
+ options[:standalone] = options[:header_footer]
57
+ end
58
58
  end
59
- end
60
59
 
61
- # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
62
- if sibling_path
63
- options[:to_dir] = outdir = ::File.dirname sibling_path
64
- elsif write_to_target
65
- if to_dir
66
- if to_file
67
- options[:to_dir] = ::File.dirname ::File.expand_path ::File.join to_dir, to_file
68
- else
69
- options[:to_dir] = ::File.expand_path to_dir
60
+ # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
61
+ if sibling_path
62
+ options[:to_dir] = outdir = ::File.dirname sibling_path
63
+ elsif write_to_target
64
+ if to_dir
65
+ if to_file
66
+ options[:to_dir] = ::File.dirname ::File.expand_path to_file, to_dir
67
+ else
68
+ options[:to_dir] = ::File.expand_path to_dir
69
+ end
70
+ elsif to_file
71
+ options[:to_dir] = ::File.dirname ::File.expand_path to_file
70
72
  end
71
- elsif to_file
72
- options[:to_dir] = ::File.dirname ::File.expand_path to_file
73
73
  end
74
- end
75
74
 
76
- # NOTE :to_dir is always set when outputting to a file
77
- # NOTE :to_file option only passed if assigned an explicit path
78
- doc = load input, options
75
+ # NOTE :to_dir is always set when outputting to a file
76
+ # NOTE :to_file option only passed if assigned an explicit path
77
+ doc = load input, options
79
78
 
80
- if sibling_path # write to file in same directory
81
- outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
82
- raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
83
- elsif write_to_target # write to explicit file or directory
84
- working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
85
- # QUESTION should the jail be the working_dir or doc.base_dir???
86
- jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
87
- if to_dir
88
- outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
89
- if to_file
90
- outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
91
- # reestablish outdir as the final target directory (in the case to_file had directory segments)
79
+ if sibling_path # write to file in same directory
80
+ outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
81
+ raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
82
+ elsif write_to_target # write to explicit file or directory
83
+ working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
84
+ # QUESTION should the jail be the working_dir or doc.base_dir???
85
+ jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
86
+ if to_dir
87
+ outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
88
+ if to_file
89
+ outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
90
+ # reestablish outdir as the final target directory (in the case to_file had directory segments)
91
+ outdir = ::File.dirname outfile
92
+ else
93
+ outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
94
+ end
95
+ elsif to_file
96
+ outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
97
+ # establish outdir as the final target directory (in the case to_file had directory segments)
92
98
  outdir = ::File.dirname outfile
93
- else
94
- outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
95
99
  end
96
- elsif to_file
97
- outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
98
- # establish outdir as the final target directory (in the case to_file had directory segments)
99
- outdir = ::File.dirname outfile
100
- end
101
100
 
102
- if ::File === input && outfile == (::File.absolute_path input.path)
103
- raise ::IOError, %(input file and output file cannot be the same: #{outfile})
101
+ if ::File === input && outfile == (::File.absolute_path input.path)
102
+ raise ::IOError, %(input file and output file cannot be the same: #{outfile})
103
+ end
104
+
105
+ if mkdirs
106
+ Helpers.mkdir_p outdir
107
+ else
108
+ # NOTE we intentionally refer to the directory as it was passed to the API
109
+ raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
110
+ end
111
+ else # write to stream
112
+ outfile = to_file
113
+ outdir = nil
104
114
  end
105
115
 
106
- if mkdirs
107
- Helpers.mkdir_p outdir
116
+ if outfile && !stream_output
117
+ output = doc.convert 'outfile' => outfile, 'outdir' => outdir
108
118
  else
109
- # NOTE we intentionally refer to the directory as it was passed to the API
110
- raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
119
+ output = doc.convert
111
120
  end
112
- else # write to stream
113
- outfile = to_file
114
- outdir = nil
115
- end
116
-
117
- if outfile && !stream_output
118
- output = doc.convert 'outfile' => outfile, 'outdir' => outdir
119
- else
120
- output = doc.convert
121
- end
122
121
 
123
- if outfile
124
- doc.write output, outfile
122
+ if outfile
123
+ doc.write output, outfile
125
124
 
126
- # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
127
- # NOTE skip if stylesdir is a URI
128
- if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
129
- (doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
130
- if (stylesheet = doc.attr 'stylesheet')
131
- if DEFAULT_STYLESHEET_KEYS.include? stylesheet
132
- copy_asciidoctor_stylesheet = true
133
- elsif !(Helpers.uriish? stylesheet)
134
- copy_user_stylesheet = true
135
- end
136
- end
137
- copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
138
- if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
139
- stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
140
- if mkdirs
141
- Helpers.mkdir_p stylesoutdir
142
- else
143
- raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
125
+ # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
126
+ # NOTE skip if stylesdir is a URI
127
+ if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
128
+ (doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
129
+ if (stylesheet = doc.attr 'stylesheet')
130
+ if DEFAULT_STYLESHEET_KEYS.include? stylesheet
131
+ copy_asciidoctor_stylesheet = true
132
+ elsif !(Helpers.uriish? stylesheet)
133
+ copy_user_stylesheet = true
134
+ end
144
135
  end
145
-
146
- if copy_asciidoctor_stylesheet
147
- Stylesheets.instance.write_primary_stylesheet stylesoutdir
148
- # FIXME should Stylesheets also handle the user stylesheet?
149
- elsif copy_user_stylesheet
150
- if (stylesheet_src = doc.attr 'copycss').empty?
151
- stylesheet_src = doc.normalize_system_path stylesheet
136
+ copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
137
+ if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
138
+ stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
139
+ if mkdirs
140
+ Helpers.mkdir_p stylesoutdir
152
141
  else
153
- # NOTE in this case, copycss is a source location (but cannot be a URI)
154
- stylesheet_src = doc.normalize_system_path stylesheet_src
142
+ raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
155
143
  end
156
- stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
157
- # NOTE don't warn if src can't be read and dest already exists (see #2323)
158
- if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
159
- warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
160
- ::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
144
+
145
+ if copy_asciidoctor_stylesheet
146
+ Stylesheets.instance.write_primary_stylesheet stylesoutdir
147
+ # FIXME should Stylesheets also handle the user stylesheet?
148
+ elsif copy_user_stylesheet
149
+ if (stylesheet_src = doc.attr 'copycss') == '' || stylesheet_src == true
150
+ stylesheet_src = doc.normalize_system_path stylesheet
151
+ else
152
+ # NOTE in this case, copycss is a source location (but cannot be a URI)
153
+ stylesheet_src = doc.normalize_system_path stylesheet_src.to_s
154
+ end
155
+ stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
156
+ # NOTE don't warn if src can't be read and dest already exists (see #2323)
157
+ if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
158
+ warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
159
+ if (stylesheet_outdir = ::File.dirname stylesheet_dest) != stylesoutdir && !(::File.directory? stylesheet_outdir)
160
+ if mkdirs
161
+ Helpers.mkdir_p stylesheet_outdir
162
+ else
163
+ raise ::IOError, %(target stylesheet directory does not exist: #{stylesheet_outdir} (hint: set :mkdirs option))
164
+ end
165
+ end
166
+ ::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
167
+ end
161
168
  end
169
+ syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
162
170
  end
163
- syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
164
171
  end
172
+ doc
173
+ else
174
+ output
165
175
  end
166
- doc
167
- else
168
- output
169
176
  end
170
- end
171
177
 
172
- # Public: Parse the contents of the AsciiDoc source file into an
173
- # Asciidoctor::Document and convert it to the specified backend format.
174
- #
175
- # input - the String AsciiDoc source filename
176
- # options - a String, Array or Hash of options to control processing (default: {})
177
- # String and Array values are converted into a Hash.
178
- # See Asciidoctor::Document#initialize for details about options.
179
- #
180
- # Returns the Document object if the converted String is written to a
181
- # file, otherwise the converted String
182
- def convert_file filename, options = {}
183
- ::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
184
- end
178
+ # Public: Parse the contents of the AsciiDoc source file into an
179
+ # Asciidoctor::Document and convert it to the specified backend format.
180
+ #
181
+ # input - the String AsciiDoc source filename
182
+ # options - a String, Array or Hash of options to control processing (default: {})
183
+ # String and Array values are converted into a Hash.
184
+ # See Asciidoctor::Document#initialize for details about options.
185
+ #
186
+ # Returns the Document object if the converted String is written to a
187
+ # file, otherwise the converted String
188
+ def convert_file filename, options = {}
189
+ ::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
190
+ end
185
191
 
186
- # Deprecated: Use {Asciidoctor.convert} instead.
187
- alias render convert
188
- module_function :render
192
+ # Deprecated: Use {Asciidoctor.convert} instead.
193
+ alias render convert
189
194
 
190
- # Deprecated: Use {Asciidoctor.convert_file} instead.
191
- alias render_file convert_file
192
- module_function :render_file
195
+ # Deprecated: Use {Asciidoctor.convert_file} instead.
196
+ alias render_file convert_file
197
+ end
193
198
  end