asciidoctor 1.5.7.1 → 1.5.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +95 -5
  3. data/Gemfile +23 -13
  4. data/README-de.adoc +482 -0
  5. data/README-fr.adoc +128 -119
  6. data/README-jp.adoc +2 -3
  7. data/README-zh_CN.adoc +2 -3
  8. data/README.adoc +131 -106
  9. data/asciidoctor.gemspec +9 -7
  10. data/data/locale/attributes-ar.adoc +1 -1
  11. data/data/locale/attributes-bg.adoc +1 -1
  12. data/data/locale/attributes-ca.adoc +1 -1
  13. data/data/locale/attributes-cs.adoc +1 -1
  14. data/data/locale/attributes-da.adoc +1 -1
  15. data/data/locale/attributes-de.adoc +1 -1
  16. data/data/locale/attributes-en.adoc +1 -1
  17. data/data/locale/attributes-es.adoc +1 -1
  18. data/data/locale/attributes-fa.adoc +1 -1
  19. data/data/locale/attributes-fi.adoc +1 -1
  20. data/data/locale/attributes-fr.adoc +1 -1
  21. data/data/locale/attributes-hu.adoc +1 -1
  22. data/data/locale/attributes-id.adoc +1 -1
  23. data/data/locale/attributes-it.adoc +1 -1
  24. data/data/locale/attributes-ja.adoc +1 -1
  25. data/data/locale/attributes-kr.adoc +1 -1
  26. data/data/locale/attributes-nb.adoc +1 -1
  27. data/data/locale/attributes-nl.adoc +1 -1
  28. data/data/locale/attributes-nn.adoc +1 -1
  29. data/data/locale/attributes-pl.adoc +1 -1
  30. data/data/locale/attributes-pt.adoc +1 -1
  31. data/data/locale/attributes-pt_BR.adoc +1 -1
  32. data/data/locale/attributes-ro.adoc +1 -1
  33. data/data/locale/attributes-ru.adoc +1 -1
  34. data/data/locale/attributes-sr.adoc +5 -4
  35. data/data/locale/attributes-sr_Latn.adoc +5 -4
  36. data/data/locale/attributes-sv.adoc +23 -0
  37. data/data/locale/attributes-tr.adoc +1 -1
  38. data/data/locale/attributes-uk.adoc +1 -1
  39. data/data/locale/attributes-zh_CN.adoc +1 -1
  40. data/data/locale/attributes-zh_TW.adoc +1 -1
  41. data/data/stylesheets/asciidoctor-default.css +23 -23
  42. data/lib/asciidoctor.rb +110 -104
  43. data/lib/asciidoctor/abstract_block.rb +55 -32
  44. data/lib/asciidoctor/abstract_node.rb +32 -17
  45. data/lib/asciidoctor/attribute_list.rb +8 -7
  46. data/lib/asciidoctor/block.rb +5 -7
  47. data/lib/asciidoctor/cli/options.rb +5 -9
  48. data/lib/asciidoctor/converter.rb +2 -2
  49. data/lib/asciidoctor/converter/docbook45.rb +7 -20
  50. data/lib/asciidoctor/converter/docbook5.rb +36 -37
  51. data/lib/asciidoctor/converter/factory.rb +10 -8
  52. data/lib/asciidoctor/converter/html5.rb +90 -65
  53. data/lib/asciidoctor/converter/manpage.rb +72 -62
  54. data/lib/asciidoctor/converter/template.rb +8 -6
  55. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +5 -0
  56. data/lib/asciidoctor/document.rb +62 -10
  57. data/lib/asciidoctor/extensions.rb +74 -16
  58. data/lib/asciidoctor/helpers.rb +11 -14
  59. data/lib/asciidoctor/list.rb +2 -2
  60. data/lib/asciidoctor/parser.rb +223 -195
  61. data/lib/asciidoctor/path_resolver.rb +15 -7
  62. data/lib/asciidoctor/reader.rb +65 -36
  63. data/lib/asciidoctor/section.rb +6 -4
  64. data/lib/asciidoctor/substitutors.rb +170 -149
  65. data/lib/asciidoctor/table.rb +16 -8
  66. data/lib/asciidoctor/version.rb +1 -1
  67. data/man/asciidoctor.1 +6 -5
  68. data/man/asciidoctor.adoc +3 -2
  69. data/test/api_test.rb +236 -0
  70. data/test/attribute_list_test.rb +242 -0
  71. data/test/attributes_test.rb +65 -52
  72. data/test/blocks_test.rb +408 -260
  73. data/test/converter_test.rb +7 -7
  74. data/test/document_test.rb +60 -54
  75. data/test/extensions_test.rb +218 -32
  76. data/test/fixtures/doctime-localtime.adoc +2 -0
  77. data/test/fixtures/section-a.adoc +4 -0
  78. data/test/fixtures/subs.adoc +0 -1
  79. data/test/invoker_test.rb +56 -18
  80. data/test/links_test.rb +105 -81
  81. data/test/lists_test.rb +636 -265
  82. data/test/logger_test.rb +1 -1
  83. data/test/manpage_test.rb +140 -3
  84. data/test/paragraphs_test.rb +42 -42
  85. data/test/parser_test.rb +63 -183
  86. data/test/paths_test.rb +21 -4
  87. data/test/preamble_test.rb +9 -9
  88. data/test/reader_test.rb +78 -28
  89. data/test/sections_test.rb +273 -151
  90. data/test/substitutions_test.rb +53 -19
  91. data/test/tables_test.rb +286 -163
  92. data/test/test_helper.rb +4 -3
  93. data/test/text_test.rb +65 -65
  94. metadata +16 -21
@@ -20,6 +20,7 @@ module Asciidoctor
20
20
  MockBoundaryRx = /<\/?BOUNDARY>/
21
21
  EmDashCharRefRx = /&#8212;(?:&#8203;)?/
22
22
  EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
23
+ WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
23
24
 
24
25
  # Converts HTML entity references back to their original form, escapes
25
26
  # special man characters and strips trailing whitespace.
@@ -28,11 +29,21 @@ module Asciidoctor
28
29
  #
29
30
  # str - the String to convert
30
31
  # opts - an Hash of options to control processing (default: {})
31
- # * :preserve_space a Boolean that indicates whether to preserve spaces (only expanding tabs) if true
32
- # or to collapse all adjacent whitespace to a single space if false (default: true)
32
+ # * :whitespace an enum that indicates how to handle whitespace; supported options are:
33
+ # :preserve - preserve spaces (only expanding tabs); :normalize - normalize whitespace
34
+ # (remove spaces around newlines); :collapse - collapse adjacent whitespace to a single
35
+ # space (default: :collapse)
33
36
  # * :append_newline a Boolean that indicates whether to append an endline to the result (default: false)
34
37
  def manify str, opts = {}
35
- str = ((opts.fetch :preserve_space, true) ? (str.gsub TAB, ET) : (str.tr_s WHITESPACE, ' ')).
38
+ case opts.fetch :whitespace, :collapse
39
+ when :preserve
40
+ str = str.gsub TAB, ET
41
+ when :normalize
42
+ str = str.gsub WrappedIndentRx, LF
43
+ else
44
+ str = str.tr_s WHITESPACE, ' '
45
+ end
46
+ str = str.
36
47
  gsub(LiteralBackslashRx, '\&(rs'). # literal backslash (not a troff escape sequence)
37
48
  gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
38
49
  # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
@@ -78,6 +89,8 @@ module Asciidoctor
78
89
  mantitle = node.attr 'mantitle'
79
90
  manvolnum = node.attr 'manvolnum', '1'
80
91
  manname = node.attr 'manname', mantitle
92
+ manmanual = node.attr 'manmanual'
93
+ mansource = node.attr 'mansource'
81
94
  docdate = (node.attr? 'reproducible') ? nil : (node.attr 'docdate')
82
95
  # NOTE the first line enables the table (tbl) preprocessor, necessary for non-Linux systems
83
96
  result = [%('\\" t
@@ -85,12 +98,12 @@ module Asciidoctor
85
98
  .\\" Author: #{(node.attr? 'authors') ? (node.attr 'authors') : '[see the "AUTHOR(S)" section]'}
86
99
  .\\" Generator: Asciidoctor #{node.attr 'asciidoctor-version'})]
87
100
  result << %(.\\" Date: #{docdate}) if docdate
88
- result << %(.\\" Manual: #{(manual = node.attr 'manmanual') || '\ \&'}
89
- .\\" Source: #{(source = node.attr 'mansource') || '\ \&'}
101
+ result << %(.\\" Manual: #{manmanual ? (manmanual.tr_s WHITESPACE, ' ') : '\ \&'}
102
+ .\\" Source: #{mansource ? (mansource.tr_s WHITESPACE, ' ') : '\ \&'}
90
103
  .\\" Language: English
91
104
  .\\")
92
105
  # TODO add document-level setting to disable capitalization of manname
93
- result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{source ? (manify source) : '\ \&'}" "#{manual ? (manify manual) : '\ \&'}")
106
+ result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{mansource ? (manify mansource) : '\ \&'}" "#{manmanual ? (manify manmanual) : '\ \&'}")
94
107
  # define portability settings
95
108
  # see http://bugs.debian.org/507673
96
109
  # see http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
@@ -131,8 +144,8 @@ module Asciidoctor
131
144
  unless node.noheader
132
145
  if node.attr? 'manpurpose'
133
146
  mannames = node.attr 'mannames', [manname]
134
- result << %(.SH "#{(node.attr 'manname-title').upcase}"
135
- #{mannames.map {|n| manify n } * ', '} \\- #{manify node.attr 'manpurpose'})
147
+ result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}"
148
+ #{mannames.map {|n| manify n }.join ', '} \\- #{manify node.attr('manpurpose'), :whitespace => :normalize})
136
149
  end
137
150
  end
138
151
 
@@ -144,22 +157,21 @@ module Asciidoctor
144
157
  result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
145
158
  end
146
159
 
147
- # FIXME we really need an API that returns the authors as an array
148
- if (num_authors = (node.attr 'authorcount') || 0) > 0
149
- if num_authors == 1
150
- result << %(.SH "AUTHOR"
151
- .sp
152
- #{node.attr 'author'})
153
- else
160
+ unless (authors = node.authors).empty?
161
+ if authors.size > 1
154
162
  result << '.SH "AUTHORS"'
155
- (1.upto num_authors).each do |i|
163
+ authors.each do |author|
156
164
  result << %(.sp
157
- #{node.attr "author_#{i}"})
165
+ #{author.name})
158
166
  end
167
+ else
168
+ result << %(.SH "AUTHOR"
169
+ .sp
170
+ #{authors[0].name})
159
171
  end
160
172
  end
161
173
 
162
- result * LF
174
+ result.join LF
163
175
  end
164
176
 
165
177
  # NOTE embedded doesn't really make sense in the manpage backend
@@ -173,7 +185,7 @@ module Asciidoctor
173
185
 
174
186
  # QUESTION should we add an AUTHOR(S) section?
175
187
 
176
- result * LF
188
+ result.join LF
177
189
  end
178
190
 
179
191
  def section node
@@ -188,7 +200,7 @@ module Asciidoctor
188
200
  end
189
201
  result << %(.#{macro} "#{manify stitle}"
190
202
  #{node.content})
191
- result * LF
203
+ result.join LF
192
204
  end
193
205
 
194
206
  def admonition node
@@ -206,7 +218,7 @@ module Asciidoctor
206
218
  #{resolve_content node}
207
219
  .sp .5v
208
220
  .RE)
209
- result * LF
221
+ result.join LF
210
222
  end
211
223
 
212
224
  alias audio skip_with_warning
@@ -223,12 +235,12 @@ r lw(\n(.lu*75u/100u).'
223
235
  num = 0
224
236
  node.items.each do |item|
225
237
  result << %(\\fB(#{num += 1})\\fP\\h'-2n':T{)
226
- result << (manify item.text)
238
+ result << (manify item.text, :whitespace => :normalize)
227
239
  result << item.content if item.blocks?
228
240
  result << 'T}'
229
241
  end
230
242
  result << '.TE'
231
- result * LF
243
+ result.join LF
232
244
  end
233
245
 
234
246
  # TODO implement horizontal (if it makes sense)
@@ -243,20 +255,20 @@ r lw(\n(.lu*75u/100u).'
243
255
  case node.style
244
256
  when 'qanda'
245
257
  result << %(.sp
246
- #{counter}. #{manify([*terms].map {|dt| dt.text } * ' ')}
258
+ #{counter}. #{manify [*terms].map {|dt| dt.text }.join ' '}
247
259
  .RS 4)
248
260
  else
249
261
  result << %(.sp
250
- #{manify([*terms].map {|dt| dt.text } * ', ')}
262
+ #{manify [*terms].map {|dt| dt.text }.join(', '), :whitespace => :normalize}
251
263
  .RS 4)
252
264
  end
253
265
  if dd
254
- result << (manify dd.text) if dd.text?
266
+ result << (manify dd.text, :whitespace => :normalize) if dd.text?
255
267
  result << dd.content if dd.blocks?
256
268
  end
257
269
  result << '.RE'
258
270
  end
259
- result * LF
271
+ result.join LF
260
272
  end
261
273
 
262
274
  def example node
@@ -267,7 +279,7 @@ r lw(\n(.lu*75u/100u).'
267
279
  result << %(.RS 4
268
280
  #{resolve_content node}
269
281
  .RE)
270
- result * LF
282
+ result.join LF
271
283
  end
272
284
 
273
285
  def floating_title node
@@ -284,10 +296,10 @@ r lw(\n(.lu*75u/100u).'
284
296
  result << %(.sp
285
297
  .if n .RS 4
286
298
  .nf
287
- #{manify node.content}
299
+ #{manify node.content, :whitespace => :preserve}
288
300
  .fi
289
301
  .if n .RE)
290
- result * LF
302
+ result.join LF
291
303
  end
292
304
 
293
305
  def literal node
@@ -298,10 +310,10 @@ r lw(\n(.lu*75u/100u).'
298
310
  result << %(.sp
299
311
  .if n .RS 4
300
312
  .nf
301
- #{manify node.content}
313
+ #{manify node.content, :whitespace => :preserve}
302
314
  .fi
303
315
  .if n .RE)
304
- result * LF
316
+ result.join LF
305
317
  end
306
318
 
307
319
  def olist node
@@ -320,11 +332,11 @@ r lw(\n(.lu*75u/100u).'
320
332
  . sp -1
321
333
  . IP " #{idx + 1}." 4.2
322
334
  .\\}
323
- #{manify item.text})
335
+ #{manify item.text, :whitespace => :normalize})
324
336
  result << item.content if item.blocks?
325
337
  result << '.RE'
326
338
  end
327
- result * LF
339
+ result.join LF
328
340
  end
329
341
 
330
342
  def open node
@@ -344,10 +356,10 @@ r lw(\n(.lu*75u/100u).'
344
356
  %(.sp
345
357
  .B #{manify node.title}
346
358
  .br
347
- #{manify node.content})
359
+ #{manify node.content, :whitespace => :normalize})
348
360
  else
349
361
  %(.sp
350
- #{manify node.content})
362
+ #{manify node.content, :whitespace => :normalize})
351
363
  end
352
364
  end
353
365
 
@@ -357,29 +369,27 @@ r lw(\n(.lu*75u/100u).'
357
369
  result = []
358
370
  if node.title?
359
371
  result << %(.sp
360
- .in +.3i
372
+ .RS 3
361
373
  .B #{manify node.title}
362
374
  .br
363
- .in)
375
+ .RE)
364
376
  end
365
377
  attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
366
378
  attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
367
- result << %(.in +.3i
368
- .ll -.3i
369
- .nf
379
+ result << %(.RS 3
380
+ .ll -.6i
370
381
  #{resolve_content node}
371
- .fi
372
382
  .br
373
- .in
383
+ .RE
374
384
  .ll)
375
385
  if attribution_line
376
- result << %(.in +.5i
377
- .ll -.5i
386
+ result << %(.RS 5
387
+ .ll -.10i
378
388
  #{attribution_line}
379
- .in
389
+ .RE
380
390
  .ll)
381
391
  end
382
- result * LF
392
+ result.join LF
383
393
  end
384
394
 
385
395
  alias sidebar skip_with_warning
@@ -448,7 +458,7 @@ allbox tab(:);'
448
458
  row_header[row_index][cell_index + 1] ||= []
449
459
  row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
450
460
  end
451
- row_text[row_index] << %(#{manify cell.text}#{LF})
461
+ row_text[row_index] << %(#{manify cell.text, :whitespace => :normalize}#{LF})
452
462
  elsif tsec == :body
453
463
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
454
464
  row_header[row_index][cell_index] << %(#{cell_halign}t)
@@ -460,11 +470,11 @@ allbox tab(:);'
460
470
  when :asciidoc
461
471
  cell_content = cell.content
462
472
  when :literal
463
- cell_content = %(.nf#{LF}#{manify cell.text}#{LF}.fi)
473
+ cell_content = %(.nf#{LF}#{manify cell.text, :whitespace => :preserve}#{LF}.fi)
464
474
  when :verse
465
- cell_content = %(.nf#{LF}#{manify cell.text}#{LF}.fi)
475
+ cell_content = %(.nf#{LF}#{manify cell.text, :whitespace => :preserve}#{LF}.fi)
466
476
  else
467
- cell_content = manify cell.content.join
477
+ cell_content = manify cell.content.join, :whitespace => :normalize
468
478
  end
469
479
  row_text[row_index] << %(#{cell_content}#{LF})
470
480
  elsif tsec == :foot
@@ -474,7 +484,7 @@ allbox tab(:);'
474
484
  row_header[row_index][cell_index + 1] ||= []
475
485
  row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
476
486
  end
477
- row_text[row_index] << %(#{manify cell.text}#{LF})
487
+ row_text[row_index] << %(#{manify cell.text, :whitespace => :normalize}#{LF})
478
488
  end
479
489
  if cell.colspan && cell.colspan > 1
480
490
  (cell.colspan - 1).times do |i|
@@ -511,13 +521,13 @@ allbox tab(:);'
511
521
  #row_header.each do |row|
512
522
  # result << LF
513
523
  # row.each_with_index do |cell, i|
514
- # result << (cell * ' ')
524
+ # result << (cell.join ' ')
515
525
  # result << ' ' if row.size > i + 1
516
526
  # end
517
527
  #end
518
528
  # FIXME temporary fix to get basic table to display
519
529
  result << LF
520
- result << row_header[0].map { 'lt' } * ' '
530
+ result << ('lt ' * row_header[0].size).chop
521
531
 
522
532
  result << %(.#{LF})
523
533
  row_text.each do |row|
@@ -550,11 +560,11 @@ allbox tab(:);'
550
560
  . sp -1
551
561
  . IP \\(bu 2.3
552
562
  .\\}
553
- #{manify item.text}]
563
+ #{manify item.text, :whitespace => :normalize}]
554
564
  result << item.content if item.blocks?
555
565
  result << '.RE'
556
566
  }
557
- result * LF
567
+ result.join LF
558
568
  end
559
569
 
560
570
  # FIXME git uses [verse] for the synopsis; detect this special case
@@ -569,7 +579,7 @@ allbox tab(:);'
569
579
  attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
570
580
  result << %(.sp
571
581
  .nf
572
- #{manify node.content}
582
+ #{manify node.content, :whitespace => :preserve}
573
583
  .fi
574
584
  .br)
575
585
  if attribution_line
@@ -579,7 +589,7 @@ allbox tab(:);'
579
589
  .in
580
590
  .ll)
581
591
  end
582
- result * LF
592
+ result.join LF
583
593
  end
584
594
 
585
595
  def video node
@@ -590,7 +600,7 @@ allbox tab(:);'
590
600
  .B #{manify node.title}
591
601
  .br) if node.title?
592
602
  result << %(<#{node.media_uri(node.attr 'target')}#{start_param}#{end_param}> (video))
593
- result * LF
603
+ result.join LF
594
604
  end
595
605
 
596
606
  def inline_anchor node
@@ -655,7 +665,7 @@ allbox tab(:);'
655
665
  if (keys = node.attr 'keys').size == 1
656
666
  keys[0]
657
667
  else
658
- keys * %(#{ESC_BS}0+#{ESC_BS}0)
668
+ keys.join %(#{ESC_BS}0+#{ESC_BS}0)
659
669
  end
660
670
  end
661
671
 
@@ -663,7 +673,7 @@ allbox tab(:);'
663
673
  caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
664
674
  menu = node.attr 'menu'
665
675
  if !(submenus = node.attr 'submenus').empty?
666
- submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) } * caret
676
+ submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) }.join caret
667
677
  %(#{ESC_BS}fI#{menu}#{ESC_BS}fP#{caret}#{submenu_path}#{caret}#{ESC_BS}fI#{node.attr 'menuitem'}#{ESC_BS}fP)
668
678
  elsif (menuitem = node.attr 'menuitem')
669
679
  %(#{ESC_BS}fI#{menu}#{caret}#{menuitem}#{ESC_BS}fP)
@@ -691,7 +701,7 @@ allbox tab(:);'
691
701
  end
692
702
 
693
703
  def resolve_content node
694
- node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content})
704
+ node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, :whitespace => :normalize})
695
705
  end
696
706
 
697
707
  def write_alternate_pages mannames, manvolnum, target
@@ -20,8 +20,8 @@ module Asciidoctor
20
20
  # backend format (e.g., "html5").
21
21
  #
22
22
  # As an optimization, scan results and templates are cached for the lifetime
23
- # of the Ruby process. If the {https://rubygems.org/gems/thread_safe
24
- # thread_safe} gem is installed, these caches are guaranteed to be thread
23
+ # of the Ruby process. If the {https://rubygems.org/gems/concurrent-ruby
24
+ # concurrent-ruby} gem is installed, these caches are guaranteed to be thread
25
25
  # safe. If this gem is not present, there is no such guarantee and a warning
26
26
  # will be issued.
27
27
  class Converter::TemplateConverter < Converter::Base
@@ -33,10 +33,11 @@ module Asciidoctor
33
33
  :slim => { :disable_escape => true, :sort_attrs => false, :pretty => false }
34
34
  }
35
35
 
36
- # QUESTION are we handling how we load the thread_safe support correctly?
37
36
  begin
38
- require 'thread_safe' unless defined? ::ThreadSafe
39
- @caches = { :scans => ::ThreadSafe::Cache.new, :templates => ::ThreadSafe::Cache.new }
37
+ unless defined? ::Concurrent::Hash
38
+ require ::RUBY_MIN_VERSION_1_9 ? 'concurrent/hash' : 'asciidoctor/core_ext/1.8.7/concurrent/hash'
39
+ end
40
+ @caches = { :scans => ::Concurrent::Hash.new, :templates => ::Concurrent::Hash.new }
40
41
  rescue ::LoadError
41
42
  @caches = { :scans => {}, :templates => {} }
42
43
  end
@@ -75,7 +76,7 @@ module Asciidoctor
75
76
  end
76
77
  case opts[:template_cache]
77
78
  when true
78
- logger.warn 'gem \'thread_safe\' is not installed. This gem is recommended when using the built-in template cache.' unless defined? ::ThreadSafe
79
+ logger.warn 'gem \'concurrent-ruby\' is not installed. This gem is recommended when using the built-in template cache.' unless defined? ::Concurrent::Hash
79
80
  @caches = self.class.caches
80
81
  when ::Hash
81
82
  @caches = opts[:template_cache]
@@ -255,6 +256,7 @@ module Asciidoctor
255
256
  unless @active_engines[extsym]
256
257
  # NOTE slim doesn't get automatically loaded by Tilt
257
258
  Helpers.require_library 'slim' unless defined? ::Slim
259
+ ::Slim::Engine.define_options :asciidoc => {}
258
260
  # align safe mode of AsciiDoc embedded in Slim template with safe mode of current document
259
261
  # NOTE safe mode won't get updated if using template cache and changing safe mode
260
262
  (@engine_options[extsym][:asciidoc] ||= {})[:safe] ||= @safe if @safe && ::Slim::VERSION >= '3.0'
@@ -0,0 +1,5 @@
1
+ require 'thread_safe'
2
+
3
+ module Concurrent
4
+ Hash = ::ThreadSafe::Cache
5
+ end
@@ -82,6 +82,10 @@ module Asciidoctor
82
82
  # can take the process to completion by calling the {Document#convert} method.
83
83
  class Document < AbstractBlock
84
84
 
85
+ ImageReference = ::Struct.new :target, :imagesdir do
86
+ alias to_s target
87
+ end
88
+
85
89
  Footnote = ::Struct.new :index, :id, :text
86
90
 
87
91
  class AttributeEntry
@@ -133,6 +137,9 @@ class Document < AbstractBlock
133
137
  end
134
138
  end
135
139
 
140
+ # Public: The Author class represents information about an author extracted from document attributes
141
+ Author = ::Struct.new :name, :firstname, :middlename, :lastname, :initials, :email
142
+
136
143
  # Public A read-only integer value indicating the level of security that
137
144
  # should be enforced while processing this document. The value must be
138
145
  # set in the Document constructor using the :safe option.
@@ -287,7 +294,7 @@ class Document < AbstractBlock
287
294
  (options[:attributes] || {}).each do |key, val|
288
295
  if key.end_with? '@'
289
296
  if key.start_with? '!'
290
- key, val = (key.slice 1, key.length), false
297
+ key, val = (key.slice 1, key.length - 2), false
291
298
  elsif key.end_with? '!@'
292
299
  key, val = (key.slice 0, key.length - 2), false
293
300
  else
@@ -327,7 +334,7 @@ class Document < AbstractBlock
327
334
  end
328
335
 
329
336
  @parsed = false
330
- @header = nil
337
+ @header = @header_attributes = nil
331
338
  @counters = {}
332
339
  @attributes_modified = ::Set.new
333
340
  @docinfo_processor_extensions = {}
@@ -493,14 +500,12 @@ class Document < AbstractBlock
493
500
  if (localdate = attrs['localdate'])
494
501
  localyear = (attrs['localyear'] ||= ((localdate.index '-') == 4 ? (localdate.slice 0, 4) : nil))
495
502
  else
496
- localdate = attrs['localdate'] = (now.strftime '%Y-%m-%d')
503
+ localdate = attrs['localdate'] = (now.strftime '%F')
497
504
  localyear = (attrs['localyear'] ||= now.year.to_s)
498
505
  end
499
- localtime = (attrs['localtime'] ||= begin
500
- now.strftime '%H:%M:%S %Z'
501
- rescue # Asciidoctor.js fails if timezone string has characters outside basic Latin (see asciidoctor.js#23)
502
- now.strftime '%H:%M:%S %z'
503
- end)
506
+ # %Z is OS dependent and may contain characters that aren't UTF-8 encoded (see asciidoctor#2770 and asciidoctor.js#23)
507
+ # Ruby 1.8 doesn't support %:z
508
+ localtime = (attrs['localtime'] ||= now.strftime %(%T #{now.utc_offset == 0 ? 'UTC' : '%z'}))
504
509
  attrs['localdatetime'] ||= %(#{localdate} #{localtime})
505
510
 
506
511
  # docdate, doctime and docdatetime should default to
@@ -643,7 +648,9 @@ class Document < AbstractBlock
643
648
  when :footnotes, :indexterms
644
649
  @catalog[type] << value
645
650
  else
646
- @catalog[type] << value if @options[:catalog_assets]
651
+ if @options[:catalog_assets]
652
+ @catalog[type] << (type == :images ? (ImageReference.new value[0], value[1]) : value)
653
+ end
647
654
  end
648
655
  end
649
656
 
@@ -752,6 +759,27 @@ class Document < AbstractBlock
752
759
  @attributes['author']
753
760
  end
754
761
 
762
+ # Public: Convenience method to retrieve the authors of this document as an Array of Author objects.
763
+ #
764
+ # This method is backed by the author-related attributes on the document.
765
+ #
766
+ # returns the authors of this document as an Array
767
+ def authors
768
+ if (attrs = @attributes).key? 'author'
769
+ authors = [(Author.new attrs['author'], attrs['firstname'], attrs['middlename'], attrs['lastname'], attrs['authorinitials'], attrs['email'])]
770
+ if (num_authors = attrs['authorcount'] || 0) > 1
771
+ idx = 1
772
+ while idx < num_authors
773
+ idx += 1
774
+ authors << (Author.new attrs[%(author_#{idx})], attrs[%(firstname_#{idx})], attrs[%(middlename_#{idx})], attrs[%(lastname_#{idx})], attrs[%(authorinitials_#{idx})], attrs[%(email_#{idx})])
775
+ end
776
+ end
777
+ authors
778
+ else
779
+ []
780
+ end
781
+ end
782
+
755
783
  # Public: Convenience method to retrieve the document attribute 'revdate'
756
784
  #
757
785
  # returns the date of last revision for the document as a String
@@ -968,6 +996,27 @@ class Document < AbstractBlock
968
996
  @attribute_overrides.key?(name)
969
997
  end
970
998
 
999
+ # Public: Assign a value to the specified attribute in the document header.
1000
+ #
1001
+ # The assignment will be visible when the header attributes are restored,
1002
+ # typically between processor phases (e.g., between parse and convert).
1003
+ #
1004
+ # name - The String attribute name to assign
1005
+ # value - The Object value to assign to the attribute (default: '')
1006
+ # overwrite - A Boolean indicating whether to assign the attribute
1007
+ # if already present in the attributes Hash (default: true)
1008
+ #
1009
+ # Returns a [Boolean] indicating whether the assignment was performed
1010
+ def set_header_attribute name, value = '', overwrite = true
1011
+ attrs = @header_attributes || @attributes
1012
+ if overwrite == false && (attrs.key? name)
1013
+ false
1014
+ else
1015
+ attrs[name] = value
1016
+ true
1017
+ end
1018
+ end
1019
+
971
1020
  # Internal: Apply substitutions to the attribute value
972
1021
  #
973
1022
  # If the value is an inline passthrough macro (e.g., pass:<subs>[value]),
@@ -1168,11 +1217,14 @@ class Document < AbstractBlock
1168
1217
  @converter.write output, target
1169
1218
  else
1170
1219
  if target.respond_to? :write
1220
+ # QUESTION should we set encoding using target.set_encoding?
1171
1221
  unless output.nil_or_empty?
1172
1222
  target.write output.chomp
1173
1223
  # ensure there's a trailing endline
1174
1224
  target.write LF
1175
1225
  end
1226
+ elsif COERCE_ENCODING
1227
+ ::IO.write target, output, :encoding => ::Encoding::UTF_8
1176
1228
  else
1177
1229
  ::IO.write target, output
1178
1230
  end
@@ -1265,7 +1317,7 @@ class Document < AbstractBlock
1265
1317
  content += @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact
1266
1318
  end
1267
1319
 
1268
- content * LF
1320
+ content.join LF
1269
1321
  end
1270
1322
  end
1271
1323