asciidoctor 2.0.10 → 2.0.17

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +294 -30
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +16 -20
  5. data/README-fr.adoc +15 -22
  6. data/README-jp.adoc +15 -26
  7. data/README-zh_CN.adoc +21 -25
  8. data/README.adoc +161 -138
  9. data/asciidoctor.gemspec +6 -13
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-be.adoc +23 -0
  12. data/data/locale/attributes-bg.adoc +4 -3
  13. data/data/locale/attributes-ca.adoc +6 -5
  14. data/data/locale/attributes-cs.adoc +4 -3
  15. data/data/locale/attributes-da.adoc +6 -5
  16. data/data/locale/attributes-de.adoc +4 -4
  17. data/data/locale/attributes-en.adoc +4 -4
  18. data/data/locale/attributes-es.adoc +6 -5
  19. data/data/locale/attributes-fa.adoc +4 -3
  20. data/data/locale/attributes-fi.adoc +4 -3
  21. data/data/locale/attributes-fr.adoc +8 -7
  22. data/data/locale/attributes-hu.adoc +4 -3
  23. data/data/locale/attributes-id.adoc +4 -3
  24. data/data/locale/attributes-it.adoc +6 -5
  25. data/data/locale/attributes-ja.adoc +4 -3
  26. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  27. data/data/locale/attributes-nb.adoc +4 -3
  28. data/data/locale/attributes-nl.adoc +6 -5
  29. data/data/locale/attributes-nn.adoc +4 -3
  30. data/data/locale/attributes-pl.adoc +8 -7
  31. data/data/locale/attributes-pt.adoc +6 -5
  32. data/data/locale/attributes-pt_BR.adoc +6 -5
  33. data/data/locale/attributes-ro.adoc +4 -3
  34. data/data/locale/attributes-ru.adoc +6 -5
  35. data/data/locale/attributes-sr.adoc +4 -4
  36. data/data/locale/attributes-sr_Latn.adoc +4 -4
  37. data/data/locale/attributes-sv.adoc +4 -4
  38. data/data/locale/attributes-th.adoc +23 -0
  39. data/data/locale/attributes-tr.adoc +4 -3
  40. data/data/locale/attributes-uk.adoc +6 -5
  41. data/data/locale/attributes-vi.adoc +23 -0
  42. data/data/locale/attributes-zh_CN.adoc +4 -3
  43. data/data/locale/attributes-zh_TW.adoc +4 -3
  44. data/data/reference/syntax.adoc +14 -7
  45. data/data/stylesheets/asciidoctor-default.css +76 -76
  46. data/data/stylesheets/coderay-asciidoctor.css +9 -9
  47. data/lib/asciidoctor/abstract_block.rb +20 -13
  48. data/lib/asciidoctor/abstract_node.rb +23 -12
  49. data/lib/asciidoctor/attribute_list.rb +64 -72
  50. data/lib/asciidoctor/block.rb +6 -6
  51. data/lib/asciidoctor/cli/invoker.rb +3 -2
  52. data/lib/asciidoctor/cli/options.rb +32 -31
  53. data/lib/asciidoctor/convert.rb +168 -162
  54. data/lib/asciidoctor/converter/docbook5.rb +49 -34
  55. data/lib/asciidoctor/converter/html5.rb +180 -139
  56. data/lib/asciidoctor/converter/manpage.rb +118 -90
  57. data/lib/asciidoctor/converter/template.rb +15 -13
  58. data/lib/asciidoctor/converter.rb +19 -16
  59. data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
  60. data/lib/asciidoctor/document.rb +77 -86
  61. data/lib/asciidoctor/extensions.rb +22 -16
  62. data/lib/asciidoctor/helpers.rb +20 -15
  63. data/lib/asciidoctor/list.rb +2 -6
  64. data/lib/asciidoctor/load.rb +103 -101
  65. data/lib/asciidoctor/logging.rb +10 -8
  66. data/lib/asciidoctor/parser.rb +211 -220
  67. data/lib/asciidoctor/path_resolver.rb +17 -15
  68. data/lib/asciidoctor/reader.rb +87 -79
  69. data/lib/asciidoctor/rx.rb +9 -7
  70. data/lib/asciidoctor/section.rb +7 -0
  71. data/lib/asciidoctor/substitutors.rb +167 -148
  72. data/lib/asciidoctor/syntax_highlighter/coderay.rb +3 -2
  73. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +13 -5
  74. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  75. data/lib/asciidoctor/syntax_highlighter/pygments.rb +19 -11
  76. data/lib/asciidoctor/syntax_highlighter/rouge.rb +35 -20
  77. data/lib/asciidoctor/syntax_highlighter.rb +16 -16
  78. data/lib/asciidoctor/table.rb +70 -43
  79. data/lib/asciidoctor/timings.rb +3 -3
  80. data/lib/asciidoctor/version.rb +1 -1
  81. data/lib/asciidoctor.rb +45 -19
  82. data/man/asciidoctor.1 +29 -31
  83. data/man/asciidoctor.adoc +35 -29
  84. metadata +17 -70
@@ -32,7 +32,7 @@ class SyntaxHighlighter::CodeRayAdapter < SyntaxHighlighter::Base
32
32
  end
33
33
 
34
34
  def docinfo? location
35
- @requires_stylesheet && location == :footer
35
+ @requires_stylesheet && location == :head
36
36
  end
37
37
 
38
38
  def docinfo location, doc, opts
@@ -78,7 +78,8 @@ class SyntaxHighlighter::CodeRayAdapter < SyntaxHighlighter::Base
78
78
  end
79
79
 
80
80
  extend Styles # exports static methods
81
- include Loader, Styles # adds methods to instance
81
+ include Styles # adds methods to instance
82
+ include Loader # adds methods to instance
82
83
 
83
84
  CodeCellStartTagCs = '<td class="code"><pre>'
84
85
 
@@ -9,18 +9,26 @@ class SyntaxHighlighter::HighlightJsAdapter < SyntaxHighlighter::Base
9
9
  end
10
10
 
11
11
  def format node, lang, opts
12
- super node, lang, (opts.merge transform: proc {|_, code| code['class'] = %(language-#{lang || 'none'} hljs) } )
12
+ super node, lang, (opts.merge transform: proc {|_, code| code['class'] = %(language-#{lang || 'none'} hljs) })
13
13
  end
14
14
 
15
15
  def docinfo? location
16
- location == :footer
16
+ true
17
17
  end
18
18
 
19
19
  def docinfo location, doc, opts
20
20
  base_url = doc.attr 'highlightjsdir', %(#{opts[:cdn_base_url]}/highlight.js/#{HIGHLIGHT_JS_VERSION})
21
- %(<link rel="stylesheet" href="#{base_url}/styles/#{doc.attr 'highlightjs-theme', 'github'}.min.css"#{opts[:self_closing_tag_slash]}>
22
- <script src="#{base_url}/highlight.min.js"></script>
23
- #{(doc.attr? 'highlightjs-languages') ? ((doc.attr 'highlightjs-languages').split ',').map {|lang| %[<script src="#{base_url}/languages/#{lang.lstrip}.min.js"></script>\n] }.join : ''}<script>hljs.initHighlighting()</script>)
21
+ if location == :head
22
+ %(<link rel="stylesheet" href="#{base_url}/styles/#{doc.attr 'highlightjs-theme', 'github'}.min.css"#{opts[:self_closing_tag_slash]}>)
23
+ else # :footer
24
+ %(<script src="#{base_url}/highlight.min.js"></script>
25
+ #{(doc.attr? 'highlightjs-languages') ? ((doc.attr 'highlightjs-languages').split ',').map {|lang| %[<script src="#{base_url}/languages/#{lang.lstrip}.min.js"></script>\n] }.join : ''}<script>
26
+ if (!hljs.initHighlighting.called) {
27
+ hljs.initHighlighting.called = true
28
+ ;[].slice.call(document.querySelectorAll('pre.highlight > code')).forEach(function (el) { hljs.highlightBlock(el) })
29
+ }
30
+ </script>)
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -14,14 +14,17 @@ class SyntaxHighlighter::PrettifyAdapter < SyntaxHighlighter::Base
14
14
  end
15
15
 
16
16
  def docinfo? location
17
- location == :footer
17
+ true
18
18
  end
19
19
 
20
20
  def docinfo location, doc, opts
21
21
  base_url = doc.attr 'prettifydir', %(#{opts[:cdn_base_url]}/prettify/r298)
22
- prettify_theme_url = ((prettify_theme = doc.attr 'prettify-theme', 'prettify').start_with? 'http://', 'https://') ? prettify_theme : %(#{base_url}/#{prettify_theme}.min.css)
23
- %(<link rel="stylesheet" href="#{prettify_theme_url}"#{opts[:self_closing_tag_slash]}>
24
- <script src="#{base_url}/run_prettify.min.js"></script>)
22
+ if location == :head
23
+ prettify_theme_url = ((prettify_theme = doc.attr 'prettify-theme', 'prettify').start_with? 'http://', 'https://') ? prettify_theme : %(#{base_url}/#{prettify_theme}.min.css)
24
+ %(<link rel="stylesheet" href="#{prettify_theme_url}"#{opts[:self_closing_tag_slash]}>)
25
+ else # :footer
26
+ %(<script src="#{base_url}/run_prettify.min.js"></script>)
27
+ end
25
28
  end
26
29
  end
27
30
  end
@@ -5,8 +5,7 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
5
5
 
6
6
  def initialize *args
7
7
  super
8
- @requires_stylesheet = nil
9
- @style = nil
8
+ @requires_stylesheet = @style = nil
10
9
  end
11
10
 
12
11
  def highlight?
@@ -34,13 +33,19 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
34
33
  highlighted = highlighted.sub WrapperTagRx, PreTagCs
35
34
  opts[:callouts] ? [highlighted, (idx = highlighted.index CodeCellStartTagCs) ? idx + CodeCellStartTagCs.length : nil] : highlighted
36
35
  else
37
- node.sub_specialchars source # handles nil response from ::Pygments::Lexer#highlight
36
+ node.sub_source source, false # handles nil response from ::Pygments::Lexer#highlight
38
37
  end
39
38
  elsif (highlighted = lexer.highlight source, options: highlight_opts)
40
- highlighted = highlighted.gsub StyledLinenoSpanTagRx, LinenoSpanTagCs if linenos && noclasses
39
+ if linenos
40
+ if noclasses
41
+ highlighted = highlighted.gsub StyledLinenoSpanTagRx, LinenoSpanTagCs
42
+ elsif highlighted.include? LegacyLinenoSpanStartTagCs
43
+ highlighted = highlighted.gsub LegacyLinenoSpanTagRx, LinenoSpanTagCs
44
+ end
45
+ end
41
46
  highlighted.sub WrapperTagRx, '\1'
42
47
  else
43
- node.sub_specialchars source # handles nil response from ::Pygments::Lexer#highlight
48
+ node.sub_source source, false # handles nil response from ::Pygments::Lexer#highlight
44
49
  end
45
50
  end
46
51
 
@@ -53,7 +58,7 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
53
58
  end
54
59
 
55
60
  def docinfo? location
56
- @requires_stylesheet && location == :footer
61
+ @requires_stylesheet && location == :head
57
62
  end
58
63
 
59
64
  def docinfo location, doc, opts
@@ -115,6 +120,7 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
115
120
  end
116
121
  @@stylesheet_cache = ::Hash.new do |cache, key|
117
122
  if (stylesheet = ::Pygments.css BASE_SELECTOR, classprefix: TOKEN_CLASS_PREFIX, style: key)
123
+ stylesheet = stylesheet.slice (stylesheet.index BASE_SELECTOR), stylesheet.length unless stylesheet.start_with? BASE_SELECTOR
118
124
  @@stylesheet_cache = cache.merge key => stylesheet
119
125
  stylesheet
120
126
  end
@@ -123,27 +129,29 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
123
129
  DEFAULT_STYLE = 'default'
124
130
  BASE_SELECTOR = 'pre.pygments'
125
131
  TOKEN_CLASS_PREFIX = 'tok-'
126
-
127
132
  BaseStyleRx = /^#{BASE_SELECTOR.gsub '.', '\\.'} +\{([^}]+?)\}/
128
133
 
129
134
  private_constant :BASE_SELECTOR, :TOKEN_CLASS_PREFIX, :BaseStyleRx
130
135
  end
131
136
 
132
137
  extend Styles # exports static methods
133
- include Loader, Styles # adds methods to instance
138
+ include Styles # adds methods to instance
139
+ include Loader # adds methods to instance
134
140
 
135
141
  CodeCellStartTagCs = '<td class="code">'
142
+ LegacyLinenoSpanStartTagCs = '<span class="lineno">'
143
+ LegacyLinenoSpanTagRx = %r(#{LegacyLinenoSpanStartTagCs}( *\d+) ?</span>)
136
144
  LinenoColumnStartTagsCs = '<td class="linenos"><div class="linenodiv"><pre>'
137
- LinenoSpanTagCs = '<span class="lineno">\1</span>'
145
+ LinenoSpanTagCs = '<span class="linenos">\1</span>'
138
146
  PreTagCs = '<pre>\1</pre>'
139
147
  StyledLinenoColumnStartTagsRx = /<td><div class="linenodiv" style="[^"]+?"><pre style="[^"]+?">/
140
- StyledLinenoSpanTagRx = %r(<span style="background-color: #f0f0f0; padding: 0 5px 0 5px">( *\d+ )</span>)
148
+ StyledLinenoSpanTagRx = %r((?<=^|<span></span>)<span style="[^"]+">( *\d+) ?</span>)
141
149
  WRAPPER_CLASS = 'lineno' # doesn't appear in output; Pygments appends "table" to this value to make nested table class
142
150
  # NOTE <pre> has style attribute when pygments-css=style
143
151
  # NOTE <div> has trailing newline when pygments-linenums-mode=table
144
152
  # NOTE initial <span></span> preserves leading blank lines
145
153
  WrapperTagRx = %r(<div class="#{WRAPPER_CLASS}"><pre\b[^>]*?>(.*)</pre></div>\n*)m
146
154
 
147
- private_constant :CodeCellStartTagCs, :LinenoColumnStartTagsCs, :LinenoSpanTagCs, :PreTagCs, :StyledLinenoColumnStartTagsRx, :StyledLinenoSpanTagRx, :WrapperTagRx, :WRAPPER_CLASS
155
+ private_constant :CodeCellStartTagCs, :LegacyLinenoSpanStartTagCs, :LegacyLinenoSpanTagRx, :LinenoColumnStartTagsCs, :LinenoSpanTagCs, :PreTagCs, :StyledLinenoColumnStartTagsRx, :StyledLinenoSpanTagRx, :WrapperTagRx, :WRAPPER_CLASS
148
156
  end
149
157
  end
@@ -13,40 +13,30 @@ class SyntaxHighlighter::RougeAdapter < SyntaxHighlighter::Base
13
13
  end
14
14
 
15
15
  def highlight node, source, lang, opts
16
- lexer = (::Rouge::Lexer.find_fancy lang) || ::Rouge::Lexers::PlainText
17
- lexer_opts = lexer.tag == 'php' && !(node.option? 'mixed') ? { start_inline: true } : {}
18
16
  @style ||= (style = opts[:style]) && (style_available? style) || DEFAULT_STYLE
19
- if opts[:css_mode] == :class
20
- @requires_stylesheet = true
21
- formatter = ::Rouge::Formatters::HTML.new inline_theme: @style
17
+ @requires_stylesheet = true if opts[:css_mode] == :class
18
+ lexer = create_lexer node, source, lang, opts
19
+ formatter = create_formatter node, source, lang, opts
20
+ highlighted = formatter.format lexer.lex source
21
+ if opts[:number_lines] && opts[:callouts]
22
+ [highlighted, (idx = highlighted.index CodeCellStartTagCs) ? idx + CodeCellStartTagCs.length : nil]
22
23
  else
23
- formatter = ::Rouge::Formatters::HTMLInline.new (::Rouge::Theme.find @style).new
24
+ highlighted
24
25
  end
25
- if (highlight_lines = opts[:highlight_lines])
26
- formatter = RougeExt::Formatters::HTMLLineHighlighter.new formatter, lines: highlight_lines
27
- end
28
- if opts[:number_lines]
29
- formatter = RougeExt::Formatters::HTMLTable.new formatter, start_line: opts[:start_line_number]
30
- if opts[:callouts]
31
- return [(highlighted = formatter.format lexer.lex source, lexer_opts), (idx = highlighted.index CodeCellStartTagCs) ? idx + CodeCellStartTagCs.length : nil]
32
- end
33
- end
34
- formatter.format lexer.lex source, lexer_opts
35
26
  end
36
27
 
37
28
  def format node, lang, opts
38
29
  if (query_idx = lang && (lang.index '?'))
39
30
  lang = lang.slice 0, query_idx
40
31
  end
41
- if opts[:css_mode] != :class && (@style = (style = opts[:style]) && (style_available? style) || DEFAULT_STYLE) &&
42
- (pre_style_attr_val = base_style @style)
32
+ if opts[:css_mode] != :class && (@style = (style = opts[:style]) && (style_available? style) || DEFAULT_STYLE) && (pre_style_attr_val = base_style @style)
43
33
  opts[:transform] = proc {|pre| pre['style'] = pre_style_attr_val }
44
34
  end
45
35
  super
46
36
  end
47
37
 
48
38
  def docinfo? location
49
- @requires_stylesheet && location == :footer
39
+ @requires_stylesheet && location == :head
50
40
  end
51
41
 
52
42
  def docinfo location, doc, opts
@@ -67,6 +57,30 @@ class SyntaxHighlighter::RougeAdapter < SyntaxHighlighter::Base
67
57
  ::File.write (::File.join to_dir, (stylesheet_basename @style)), (read_stylesheet @style), mode: FILE_WRITE_MODE
68
58
  end
69
59
 
60
+ def create_lexer node, source, lang, opts
61
+ if lang.include? '?'
62
+ # NOTE cgi-style options only properly supported in Rouge >= 2.1
63
+ if (lexer = ::Rouge::Lexer.find_fancy lang)
64
+ unless lexer.tag != 'php' || (node.option? 'mixed') || ((lexer_opts = lexer.options).key? 'start_inline')
65
+ lexer = lexer.class.new lexer_opts.merge 'start_inline' => true
66
+ end
67
+ end
68
+ elsif (lexer = ::Rouge::Lexer.find lang)
69
+ lexer = lexer.tag == 'php' && !(node.option? 'mixed') ? (lexer.new start_inline: true) : lexer.new
70
+ end if lang
71
+ lexer || ::Rouge::Lexers::PlainText.new
72
+ end
73
+
74
+ def create_formatter node, source, lang, opts
75
+ formatter = opts[:css_mode] == :class ?
76
+ (::Rouge::Formatters::HTML.new inline_theme: @style) :
77
+ (::Rouge::Formatters::HTMLInline.new (::Rouge::Theme.find @style).new)
78
+ if (highlight_lines = opts[:highlight_lines])
79
+ formatter = RougeExt::Formatters::HTMLLineHighlighter.new formatter, lines: highlight_lines
80
+ end
81
+ opts[:number_lines] ? (RougeExt::Formatters::HTMLTable.new formatter, start_line: opts[:start_line_number]) : formatter
82
+ end
83
+
70
84
  module Loader
71
85
  private
72
86
 
@@ -119,7 +133,8 @@ class SyntaxHighlighter::RougeAdapter < SyntaxHighlighter::Base
119
133
  end
120
134
 
121
135
  extend Styles # exports static methods
122
- include Loader, Styles # adds methods to instance
136
+ include Styles # adds methods to instance
137
+ include Loader # adds methods to instance
123
138
 
124
139
  CodeCellStartTagCs = '<td class="code">'
125
140
 
@@ -20,7 +20,7 @@ module SyntaxHighlighter
20
20
  end
21
21
 
22
22
  # Public: Indicates whether this syntax highlighter has docinfo (i.e., markup) to insert into the output document at
23
- # the specified location.
23
+ # the specified location. Should be called by converter after main content has been converted.
24
24
  #
25
25
  # location - The Symbol representing the location slot (:head or :footer).
26
26
  #
@@ -28,6 +28,7 @@ module SyntaxHighlighter
28
28
  def docinfo? location; end
29
29
 
30
30
  # Public: Generates docinfo markup for this syntax highlighter to insert at the specified location in the output document.
31
+ # Should be called by converter after main content has been converted.
31
32
  #
32
33
  # location - The Symbol representing the location slot (:head or :footer).
33
34
  # doc - The Document in which this syntax highlighter is being used.
@@ -98,9 +99,10 @@ module SyntaxHighlighter
98
99
  raise ::NotImplementedError, %(#{SyntaxHighlighter} subclass #{self.class} must implement the ##{__method__} method since #write_stylesheet? returns true)
99
100
  end
100
101
 
101
- private_class_method def self.included into
102
+ def self.included into
102
103
  into.extend Config
103
- end || :included
104
+ end
105
+ private_class_method :included # use separate declaration for Ruby 2.0.x
104
106
 
105
107
  module Config
106
108
  # Public: Statically register the current class in the registry for the specified names.
@@ -139,7 +141,7 @@ module SyntaxHighlighter
139
141
  # name - The String name of the syntax highlighter to create.
140
142
  # backend - The String name of the backend for which this syntax highlighter is being used (default: 'html5').
141
143
  # opts - A Hash of options providing information about the context in which this syntax highlighter is used:
142
- # :doc - The Document for which this syntax highlighter was created.
144
+ # :document - The Document for which this syntax highlighter was created.
143
145
  #
144
146
  # Returns a [SyntaxHighlighter] instance for the specified name.
145
147
  def create name, backend = 'html5', opts = {}
@@ -151,7 +153,7 @@ module SyntaxHighlighter
151
153
  end
152
154
 
153
155
  private
154
-
156
+
155
157
  def registry
156
158
  raise ::NotImplementedError, %(#{Factory} subclass #{self.class} must implement the ##{__method__} method)
157
159
  end
@@ -165,19 +167,17 @@ module SyntaxHighlighter
165
167
  end
166
168
 
167
169
  private
168
-
169
- def registry
170
- @registry
171
- end
170
+
171
+ attr_reader :registry
172
172
  end
173
173
 
174
174
  module DefaultFactory
175
175
  include Factory
176
176
 
177
- private
178
-
179
177
  @@registry = {}
180
178
 
179
+ private
180
+
181
181
  def registry
182
182
  @@registry
183
183
  end
@@ -215,8 +215,6 @@ module SyntaxHighlighter
215
215
  'rouge' => %(#{__dir__}/syntax_highlighter/rouge),
216
216
  }
217
217
 
218
- private
219
-
220
218
  @@mutex = ::Mutex.new
221
219
  end
222
220
  end
@@ -235,9 +233,11 @@ module SyntaxHighlighter
235
233
  def format node, lang, opts
236
234
  class_attr_val = opts[:nowrap] ? %(#{@pre_class} highlight nowrap) : %(#{@pre_class} highlight)
237
235
  if (transform = opts[:transform])
238
- pre = { 'class' => class_attr_val }
239
- code = lang ? { 'data-lang' => lang } : {}
240
- transform[pre, code]
236
+ transform[(pre = { 'class' => class_attr_val }), (code = lang ? { 'data-lang' => lang } : {})]
237
+ # NOTE: make sure data-lang is the last attribute on the code tag to remain consistent with 1.5.x
238
+ if (lang = code.delete 'data-lang')
239
+ code['data-lang'] = lang
240
+ end
241
241
  %(<pre#{pre.map {|k, v| %[ #{k}="#{v}"] }.join}><code#{code.map {|k, v| %[ #{k}="#{v}"] }.join}>#{node.content}</code></pre>)
242
242
  else
243
243
  %(<pre class="#{class_attr_val}"><code#{lang ? %[ data-lang="#{lang}"] : ''}>#{node.content}</code></pre>)
@@ -58,7 +58,7 @@ class Table < AbstractBlock
58
58
  @rows = Rows.new
59
59
  @columns = []
60
60
 
61
- @has_header_option = attributes['header-option'] ? true : false
61
+ @has_header_option = false
62
62
 
63
63
  # smells like we need a utility method here
64
64
  # to resolve an integer width from potential bogus input
@@ -78,10 +78,10 @@ class Table < AbstractBlock
78
78
  @attributes['orientation'] = 'landscape' if attributes['rotate-option']
79
79
  end
80
80
 
81
- # Internal: Returns whether the current row being processed is
82
- # the header row
81
+ # Internal: Returns the current state of the header option (true or :implicit) if
82
+ # the row being processed is (or is assumed to be) the header row, otherwise nil
83
83
  def header_row?
84
- @has_header_option && @rows.body.empty?
84
+ (val = @has_header_option) && @rows.body.empty? ? val : nil
85
85
  end
86
86
 
87
87
  # Internal: Creates the Column objects from the column spec
@@ -154,22 +154,19 @@ class Table < AbstractBlock
154
154
  # returns nothing
155
155
  def partition_header_footer(attrs)
156
156
  # set rowcount before splitting up body rows
157
- @attributes['rowcount'] = @rows.body.size
158
-
159
- num_body_rows = @rows.body.size
160
- if num_body_rows > 0 && @has_header_option
161
- head = @rows.body.shift
162
- num_body_rows -= 1
163
- # styles aren't applied to header row
164
- head.each {|c| c.style = nil }
165
- # QUESTION why does AsciiDoc use an array for head? is it
166
- # possible to have more than one based on the syntax?
167
- @rows.head = [head]
157
+ num_body_rows = @attributes['rowcount'] = (body = @rows.body).size
158
+
159
+ if num_body_rows > 0
160
+ if @has_header_option
161
+ @rows.head = [body.shift.map {|cell| cell.reinitialize true }]
162
+ num_body_rows -= 1
163
+ elsif @has_header_option.nil?
164
+ @has_header_option = false
165
+ body.unshift(body.shift.map {|cell| cell.reinitialize false })
166
+ end
168
167
  end
169
168
 
170
- if num_body_rows > 0 && attrs['footer-option']
171
- @rows.foot = [@rows.body.pop]
172
- end
169
+ @rows.foot = [body.pop] if num_body_rows > 0 && attrs['footer-option']
173
170
 
174
171
  nil
175
172
  end
@@ -232,14 +229,23 @@ class Table::Cell < AbstractBlock
232
229
  # Public: An alias to the parent block (which is always a Column)
233
230
  alias column parent
234
231
 
235
- # Internal: Returns the nested Document in an AsciiDoc table cell (only set when style is :asciidoc)
232
+ # Public: Returns the nested Document in an AsciiDoc table cell (only set when style is :asciidoc)
236
233
  attr_reader :inner_document
237
234
 
238
235
  def initialize column, cell_text, attributes = {}, opts = {}
239
236
  super column, :table_cell
237
+ @cursor = @reinitialize_args = nil
240
238
  @source_location = opts[:cursor].dup if @document.sourcemap
239
+ # NOTE: column is always set when parsing; may not be set when building table from the API
241
240
  if column
242
- cell_style = column.attributes['style'] unless (in_header_row = column.table.header_row?)
241
+ if (in_header_row = column.table.header_row?)
242
+ if in_header_row == :implicit && (cell_style = column.style || (attributes && attributes['style']))
243
+ @reinitialize_args = [column, cell_text, attributes && attributes.merge, opts] if cell_style == :asciidoc || cell_style == :literal
244
+ cell_style = nil
245
+ end
246
+ else
247
+ cell_style = column.style
248
+ end
243
249
  # REVIEW feels hacky to inherit all attributes from column
244
250
  update_attributes column.attributes
245
251
  end
@@ -253,7 +259,8 @@ class Table::Cell < AbstractBlock
253
259
  cell_style = attributes['style'] || cell_style unless in_header_row
254
260
  update_attributes attributes
255
261
  end
256
- if cell_style == :asciidoc
262
+ case cell_style
263
+ when :asciidoc
257
264
  asciidoc = true
258
265
  inner_document_cursor = opts[:cursor]
259
266
  if (cell_text = cell_text.rstrip).start_with? LF
@@ -264,7 +271,7 @@ class Table::Cell < AbstractBlock
264
271
  else
265
272
  cell_text = cell_text.lstrip
266
273
  end
267
- elsif cell_style == :literal
274
+ when :literal
268
275
  literal = true
269
276
  cell_text = cell_text.rstrip
270
277
  # QUESTION should we use same logic as :asciidoc cell? strip leading space if text doesn't start with newline?
@@ -306,8 +313,12 @@ class Table::Cell < AbstractBlock
306
313
  @content_model = :verbatim
307
314
  @subs = BASIC_SUBS
308
315
  else
309
- if normal_psv && (cell_text.start_with? '[[') && LeadingInlineAnchorRx =~ cell_text
310
- Parser.catalog_inline_anchor $1, $2, self, opts[:cursor], @document
316
+ if normal_psv
317
+ if in_header_row
318
+ @cursor = opts[:cursor] # used in deferred catalog_inline_anchor call
319
+ else
320
+ catalog_inline_anchor cell_text, opts[:cursor]
321
+ end
311
322
  end
312
323
  @content_model = :simple
313
324
  @subs = NORMAL_SUBS
@@ -316,6 +327,25 @@ class Table::Cell < AbstractBlock
316
327
  @style = cell_style
317
328
  end
318
329
 
330
+ def reinitialize has_header
331
+ if has_header
332
+ @reinitialize_args = nil
333
+ elsif @reinitialize_args
334
+ return Table::Cell.new(*@reinitialize_args)
335
+ else
336
+ @style = @attributes['style']
337
+ end
338
+ catalog_inline_anchor if @cursor
339
+ self
340
+ end
341
+
342
+ def catalog_inline_anchor cell_text = @text, cursor = nil
343
+ cursor, @cursor = @cursor, nil unless cursor
344
+ if (cell_text.start_with? '[[') && LeadingInlineAnchorRx =~ cell_text
345
+ Parser.catalog_inline_anchor $1, $2, self, cursor, @document
346
+ end
347
+ end
348
+
319
349
  # Public: Get the String text of this cell with substitutions applied.
320
350
  #
321
351
  # Used for cells in the head row as well as text-only (non-AsciiDoc) cells in
@@ -328,18 +358,14 @@ class Table::Cell < AbstractBlock
328
358
  apply_subs @text, @subs
329
359
  end
330
360
 
331
- # Public: Set the String text.
361
+ # Public: Set the String text for this cell.
332
362
  #
333
363
  # This method shouldn't be used for cells that have the AsciiDoc style.
334
- #
335
- # Returns the new String text assigned to this Cell
336
- def text= val
337
- @text = val
338
- end
364
+ attr_writer :text
339
365
 
340
366
  # Public: Handles the body data (tbody, tfoot), applying styles and partitioning into paragraphs
341
367
  #
342
- # This method should not be used for cells in the head row or that have the literal or verse style.
368
+ # This method should not be used for cells in the head row or that have the literal style.
343
369
  #
344
370
  # Returns the converted String for this Cell
345
371
  def content
@@ -377,7 +403,7 @@ class Table::Cell < AbstractBlock
377
403
  end
378
404
 
379
405
  def to_s
380
- "#{super.to_s} - [text: #@text, colspan: #{@colspan || 1}, rowspan: #{@rowspan || 1}, attributes: #@attributes]"
406
+ %(#{super} - [text: #{@text}, colspan: #{@colspan || 1}, rowspan: #{@rowspan || 1}, attributes: #{@attributes}])
381
407
  end
382
408
  end
383
409
 
@@ -385,7 +411,7 @@ end
385
411
  # class are primarily responsible for tracking the buffer of a cell as the parser
386
412
  # moves through the lines of the table using tail recursion. When a cell boundary
387
413
  # is located, the previous cell is closed, an instance of Table::Cell is
388
- # instantiated, the row is closed if the cell satisifies the column count and,
414
+ # instantiated, the row is closed if the cell satisfies the column count and,
389
415
  # finally, a new buffer is allocated to track the next cell.
390
416
  class Table::ParserContext
391
417
  include Logging
@@ -505,12 +531,13 @@ class Table::ParserContext
505
531
  #
506
532
  # returns true if the buffer has unclosed quotes, false if it doesn't or it
507
533
  # isn't quoted data
508
- def buffer_has_unclosed_quotes? append = nil
509
- if (record = append ? (@buffer + append).strip : @buffer.strip) == '"'
534
+ def buffer_has_unclosed_quotes? append = nil, q = '"'
535
+ if (record = append ? (@buffer + append).strip : @buffer.strip) == q
510
536
  true
511
- elsif record.start_with? '"'
512
- if ((trailing_quote = record.end_with? '"') && (record.end_with? '""')) || (record.start_with? '""')
513
- ((record = record.gsub '""', '').start_with? '"') && !(record.end_with? '"')
537
+ elsif record.start_with? q
538
+ qq = q + q
539
+ if ((trailing_quote = record.end_with? q) && (record.end_with? qq)) || (record.start_with? qq)
540
+ ((record = record.gsub qq, '').start_with? q) && !(record.end_with? q)
514
541
  else
515
542
  !trailing_quote
516
543
  end
@@ -603,20 +630,20 @@ class Table::ParserContext
603
630
  @buffer = ''
604
631
  cellspec = nil
605
632
  repeat = 1
606
- if @format == 'csv' && !cell_text.empty? && cell_text.include?('"')
633
+ if @format == 'csv' && !cell_text.empty? && (cell_text.include? (q = '"'))
607
634
  # this may not be perfect logic, but it hits the 99%
608
- if cell_text.start_with?('"') && cell_text.end_with?('"')
635
+ if (cell_text.start_with? q) && (cell_text.end_with? q)
609
636
  # unquote
610
637
  if (cell_text = cell_text.slice(1, cell_text.length - 2))
611
638
  # trim whitespace and collapse escaped quotes
612
- cell_text = cell_text.strip.squeeze('"')
639
+ cell_text = cell_text.strip.squeeze q
613
640
  else
614
641
  logger.error message_with_context 'unclosed quote in CSV data; setting cell to empty', source_location: @reader.cursor_at_prev_line
615
642
  cell_text = ''
616
643
  end
617
644
  else
618
645
  # collapse escaped quotes
619
- cell_text = cell_text.squeeze('"')
646
+ cell_text = cell_text.squeeze q
620
647
  end
621
648
  end
622
649
  end
@@ -635,7 +662,7 @@ class Table::ParserContext
635
662
  # QUESTION is this right for cells that span columns?
636
663
  unless (column = @table.columns[@current_row.size])
637
664
  logger.error message_with_context 'dropping cell because it exceeds specified number of columns', source_location: @reader.cursor_before_mark
638
- return
665
+ return nil
639
666
  end
640
667
  end
641
668
 
@@ -49,9 +49,9 @@ module Asciidoctor
49
49
 
50
50
  def print_report to = $stdout, subject = nil
51
51
  to.puts %(Input file: #{subject}) if subject
52
- to.puts %( Time to read and parse source: #{'%05.5f' % read_parse.to_f})
53
- to.puts %( Time to convert document: #{'%05.5f' % convert.to_f})
54
- to.puts %( Total time (read, parse and convert): #{'%05.5f' % read_parse_convert.to_f})
52
+ to.puts %( Time to read and parse source: #{sprintf '%05.5f', read_parse.to_f})
53
+ to.puts %( Time to convert document: #{sprintf '%05.5f', convert.to_f})
54
+ to.puts %( Total time (read, parse and convert): #{sprintf '%05.5f', read_parse_convert.to_f})
55
55
  end
56
56
 
57
57
  private
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Asciidoctor
3
- VERSION = '2.0.10'
3
+ VERSION = '2.0.17'
4
4
  end