asciidoctor 2.0.7 → 2.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +169 -7
  3. data/LICENSE +2 -1
  4. data/README-de.adoc +5 -15
  5. data/README-fr.adoc +4 -14
  6. data/README-jp.adoc +234 -186
  7. data/README-zh_CN.adoc +7 -17
  8. data/README.adoc +18 -18
  9. data/asciidoctor.gemspec +4 -4
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-bg.adoc +4 -3
  12. data/data/locale/attributes-ca.adoc +6 -5
  13. data/data/locale/attributes-cs.adoc +4 -3
  14. data/data/locale/attributes-da.adoc +6 -5
  15. data/data/locale/attributes-de.adoc +4 -4
  16. data/data/locale/attributes-en.adoc +4 -4
  17. data/data/locale/attributes-es.adoc +6 -5
  18. data/data/locale/attributes-fa.adoc +4 -3
  19. data/data/locale/attributes-fi.adoc +4 -3
  20. data/data/locale/attributes-fr.adoc +6 -5
  21. data/data/locale/attributes-hu.adoc +4 -3
  22. data/data/locale/attributes-id.adoc +4 -3
  23. data/data/locale/attributes-it.adoc +4 -3
  24. data/data/locale/attributes-ja.adoc +4 -3
  25. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  26. data/data/locale/attributes-nb.adoc +4 -3
  27. data/data/locale/attributes-nl.adoc +4 -3
  28. data/data/locale/attributes-nn.adoc +4 -3
  29. data/data/locale/attributes-pl.adoc +8 -7
  30. data/data/locale/attributes-pt.adoc +6 -5
  31. data/data/locale/attributes-pt_BR.adoc +6 -5
  32. data/data/locale/attributes-ro.adoc +4 -3
  33. data/data/locale/attributes-ru.adoc +6 -5
  34. data/data/locale/attributes-sr.adoc +4 -4
  35. data/data/locale/attributes-sr_Latn.adoc +4 -4
  36. data/data/locale/attributes-sv.adoc +4 -4
  37. data/data/locale/attributes-tr.adoc +4 -3
  38. data/data/locale/attributes-uk.adoc +6 -5
  39. data/data/locale/attributes-zh_CN.adoc +4 -3
  40. data/data/locale/attributes-zh_TW.adoc +4 -3
  41. data/data/stylesheets/asciidoctor-default.css +33 -30
  42. data/lib/asciidoctor.rb +89 -791
  43. data/lib/asciidoctor/abstract_block.rb +19 -11
  44. data/lib/asciidoctor/abstract_node.rb +21 -15
  45. data/lib/asciidoctor/attribute_list.rb +59 -67
  46. data/lib/asciidoctor/cli/invoker.rb +2 -0
  47. data/lib/asciidoctor/cli/options.rb +3 -3
  48. data/lib/asciidoctor/convert.rb +167 -162
  49. data/lib/asciidoctor/converter.rb +14 -13
  50. data/lib/asciidoctor/converter/docbook5.rb +10 -26
  51. data/lib/asciidoctor/converter/html5.rb +62 -43
  52. data/lib/asciidoctor/converter/manpage.rb +13 -12
  53. data/lib/asciidoctor/converter/template.rb +6 -3
  54. data/lib/asciidoctor/document.rb +25 -41
  55. data/lib/asciidoctor/extensions.rb +3 -3
  56. data/lib/asciidoctor/helpers.rb +38 -39
  57. data/lib/asciidoctor/inline.rb +1 -1
  58. data/lib/asciidoctor/load.rb +101 -101
  59. data/lib/asciidoctor/parser.rb +30 -25
  60. data/lib/asciidoctor/path_resolver.rb +35 -25
  61. data/lib/asciidoctor/reader.rb +14 -7
  62. data/lib/asciidoctor/rx.rb +722 -0
  63. data/lib/asciidoctor/substitutors.rb +61 -39
  64. data/lib/asciidoctor/syntax_highlighter.rb +22 -8
  65. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  66. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  67. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  68. data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -3
  69. data/lib/asciidoctor/syntax_highlighter/rouge.rb +15 -7
  70. data/lib/asciidoctor/table.rb +52 -23
  71. data/lib/asciidoctor/version.rb +1 -1
  72. data/man/asciidoctor.1 +6 -6
  73. data/man/asciidoctor.adoc +4 -3
  74. metadata +10 -9
@@ -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