asciidoctor 1.5.8 → 2.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +162 -17
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +12 -13
  5. data/README-fr.adoc +11 -12
  6. data/README-jp.adoc +11 -12
  7. data/README-zh_CN.adoc +12 -13
  8. data/README.adoc +6 -7
  9. data/asciidoctor.gemspec +19 -24
  10. data/bin/asciidoctor +5 -4
  11. data/data/reference/syntax.adoc +283 -0
  12. data/data/stylesheets/asciidoctor-default.css +56 -52
  13. data/data/stylesheets/coderay-asciidoctor.css +7 -9
  14. data/lib/asciidoctor.rb +171 -232
  15. data/lib/asciidoctor/abstract_block.rb +96 -105
  16. data/lib/asciidoctor/abstract_node.rb +118 -139
  17. data/lib/asciidoctor/attribute_list.rb +10 -14
  18. data/lib/asciidoctor/block.rb +20 -19
  19. data/lib/asciidoctor/callouts.rb +4 -2
  20. data/lib/asciidoctor/cli.rb +3 -2
  21. data/lib/asciidoctor/cli/invoker.rb +14 -21
  22. data/lib/asciidoctor/cli/options.rb +64 -54
  23. data/lib/asciidoctor/converter.rb +357 -185
  24. data/lib/asciidoctor/converter/composite.rb +40 -48
  25. data/lib/asciidoctor/converter/docbook5.rb +604 -640
  26. data/lib/asciidoctor/converter/html5.rb +949 -963
  27. data/lib/asciidoctor/converter/manpage.rb +569 -548
  28. data/lib/asciidoctor/converter/template.rb +231 -272
  29. data/lib/asciidoctor/core_ext.rb +5 -18
  30. data/lib/asciidoctor/core_ext/float/truncate.rb +19 -0
  31. data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
  32. data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
  33. data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
  34. data/lib/asciidoctor/document.rb +399 -377
  35. data/lib/asciidoctor/extensions.rb +72 -140
  36. data/lib/asciidoctor/helpers.rb +122 -83
  37. data/lib/asciidoctor/inline.rb +5 -1
  38. data/lib/asciidoctor/list.rb +13 -11
  39. data/lib/asciidoctor/logging.rb +17 -16
  40. data/lib/asciidoctor/parser.rb +390 -423
  41. data/lib/asciidoctor/path_resolver.rb +10 -5
  42. data/lib/asciidoctor/reader.rb +286 -263
  43. data/lib/asciidoctor/rouge_ext.rb +39 -0
  44. data/lib/asciidoctor/section.rb +9 -8
  45. data/lib/asciidoctor/stylesheets.rb +19 -37
  46. data/lib/asciidoctor/substitutors.rb +364 -509
  47. data/lib/asciidoctor/syntax_highlighter.rb +238 -0
  48. data/lib/asciidoctor/syntax_highlighter/coderay.rb +87 -0
  49. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +26 -0
  50. data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
  51. data/lib/asciidoctor/syntax_highlighter/prettify.rb +27 -0
  52. data/lib/asciidoctor/syntax_highlighter/pygments.rb +149 -0
  53. data/lib/asciidoctor/syntax_highlighter/rouge.rb +129 -0
  54. data/lib/asciidoctor/table.rb +73 -66
  55. data/lib/asciidoctor/timings.rb +4 -2
  56. data/lib/asciidoctor/version.rb +2 -1
  57. data/lib/asciidoctor/writer.rb +30 -0
  58. data/man/asciidoctor.1 +19 -15
  59. data/man/asciidoctor.adoc +14 -12
  60. metadata +69 -216
  61. data/CONTRIBUTING.adoc +0 -185
  62. data/Gemfile +0 -60
  63. data/Rakefile +0 -129
  64. data/bin/asciidoctor-safe +0 -15
  65. data/features/open_block.feature +0 -92
  66. data/features/pass_block.feature +0 -66
  67. data/features/step_definitions.rb +0 -49
  68. data/features/text_formatting.feature +0 -57
  69. data/features/xref.feature +0 -1039
  70. data/lib/asciidoctor/converter/base.rb +0 -59
  71. data/lib/asciidoctor/converter/docbook45.rb +0 -93
  72. data/lib/asciidoctor/converter/factory.rb +0 -226
  73. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
  74. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
  75. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
  76. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
  77. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
  78. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
  79. data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
  80. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
  81. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
  82. data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
  83. data/test/api_test.rb +0 -1240
  84. data/test/attribute_list_test.rb +0 -242
  85. data/test/attributes_test.rb +0 -1623
  86. data/test/blocks_test.rb +0 -3870
  87. data/test/converter_test.rb +0 -470
  88. data/test/document_test.rb +0 -1853
  89. data/test/extensions_test.rb +0 -1560
  90. data/test/fixtures/asciidoc_index.txt +0 -521
  91. data/test/fixtures/basic-docinfo-footer.html +0 -6
  92. data/test/fixtures/basic-docinfo-footer.xml +0 -8
  93. data/test/fixtures/basic-docinfo.html +0 -1
  94. data/test/fixtures/basic-docinfo.xml +0 -4
  95. data/test/fixtures/basic.asciidoc +0 -5
  96. data/test/fixtures/chapter-a.adoc +0 -3
  97. data/test/fixtures/child-include.adoc +0 -5
  98. data/test/fixtures/circle.svg +0 -9
  99. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
  100. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
  101. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
  102. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
  103. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
  104. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
  105. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
  106. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
  107. data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
  108. data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
  109. data/test/fixtures/docinfo-footer.html +0 -1
  110. data/test/fixtures/docinfo-footer.xml +0 -9
  111. data/test/fixtures/docinfo.html +0 -1
  112. data/test/fixtures/docinfo.xml +0 -3
  113. data/test/fixtures/doctime-localtime.adoc +0 -2
  114. data/test/fixtures/dot.gif +0 -0
  115. data/test/fixtures/encoding.asciidoc +0 -13
  116. data/test/fixtures/file-with-missing-include.adoc +0 -1
  117. data/test/fixtures/grandchild-include.adoc +0 -3
  118. data/test/fixtures/hello-asciidoctor.pdf +0 -69
  119. data/test/fixtures/include-file.asciidoc +0 -24
  120. data/test/fixtures/include-file.jsx +0 -8
  121. data/test/fixtures/include-file.ml +0 -3
  122. data/test/fixtures/include-file.xml +0 -5
  123. data/test/fixtures/lists.adoc +0 -96
  124. data/test/fixtures/master.adoc +0 -5
  125. data/test/fixtures/mismatched-end-tag.adoc +0 -7
  126. data/test/fixtures/other-chapters.adoc +0 -11
  127. data/test/fixtures/outer-include.adoc +0 -5
  128. data/test/fixtures/parent-include-restricted.adoc +0 -5
  129. data/test/fixtures/parent-include.adoc +0 -5
  130. data/test/fixtures/sample.asciidoc +0 -30
  131. data/test/fixtures/section-a.adoc +0 -4
  132. data/test/fixtures/stylesheets/custom.css +0 -3
  133. data/test/fixtures/subdir/index.adoc +0 -3
  134. data/test/fixtures/subdir/inner-include.adoc +0 -3
  135. data/test/fixtures/subdir/middle-include.adoc +0 -5
  136. data/test/fixtures/subs-docinfo.html +0 -2
  137. data/test/fixtures/subs.adoc +0 -6
  138. data/test/fixtures/tagged-class-enclosed.rb +0 -25
  139. data/test/fixtures/tagged-class.rb +0 -23
  140. data/test/fixtures/tip.gif +0 -0
  141. data/test/fixtures/unclosed-tag.adoc +0 -3
  142. data/test/fixtures/unexpected-end-tag.adoc +0 -4
  143. data/test/invoker_test.rb +0 -745
  144. data/test/links_test.rb +0 -855
  145. data/test/lists_test.rb +0 -5151
  146. data/test/logger_test.rb +0 -211
  147. data/test/manpage_test.rb +0 -660
  148. data/test/options_test.rb +0 -262
  149. data/test/paragraphs_test.rb +0 -562
  150. data/test/parser_test.rb +0 -742
  151. data/test/paths_test.rb +0 -395
  152. data/test/preamble_test.rb +0 -173
  153. data/test/reader_test.rb +0 -2161
  154. data/test/sections_test.rb +0 -3575
  155. data/test/substitutions_test.rb +0 -2066
  156. data/test/tables_test.rb +0 -2036
  157. data/test/test_helper.rb +0 -447
  158. data/test/text_test.rb +0 -309
@@ -1,4 +1,4 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # Public: Methods for managing inline elements in AsciiDoc block
4
4
  class Inline < AbstractNode
@@ -49,11 +49,15 @@ class Inline < AbstractNode
49
49
  attr 'alt'
50
50
  end
51
51
 
52
+ # For a reference node (:ref or :bibref), the text is the reftext (and the reftext attribute is not set).
53
+ #
52
54
  # (see AbstractNode#reftext?)
53
55
  def reftext?
54
56
  @text && (@type == :ref || @type == :bibref)
55
57
  end
56
58
 
59
+ # For a reference node (:ref or :bibref), the text is the reftext (and the reftext attribute is not set).
60
+ #
57
61
  # (see AbstractNode#reftext)
58
62
  def reftext
59
63
  (val = @text) ? (apply_reftext_subs val) : nil
@@ -1,4 +1,4 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # Public: Methods for managing AsciiDoc lists (ordered, unordered and description lists)
4
4
  class List < AbstractBlock
@@ -41,6 +41,9 @@ class List < AbstractBlock
41
41
  end
42
42
 
43
43
  # Public: Methods for managing items for AsciiDoc olists, ulist, and dlists.
44
+ #
45
+ # In a description list (dlist), each item is a tuple that consists of a 2-item Array of ListItem terms and a ListItem
46
+ # description (i.e., [[term, term, ...], desc]. If a description is not set, then the second entry in the tuple is nil.
44
47
  class ListItem < AbstractBlock
45
48
 
46
49
  # A contextual alias for the list parent node; counterpart to the items alias on List
@@ -100,7 +103,7 @@ class ListItem < AbstractBlock
100
103
  !simple?
101
104
  end
102
105
 
103
- # Public: Fold the first paragraph block into the text
106
+ # Internal: Fold the first paragraph block into the text
104
107
  #
105
108
  # Here are the rules for when a folding occurs:
106
109
  #
@@ -112,14 +115,14 @@ class ListItem < AbstractBlock
112
115
  #
113
116
  # Returns nothing
114
117
  def fold_first(continuation_connects_first_block = false, content_adjacent = false)
115
- if (first_block = @blocks[0]) && Block === first_block &&
116
- ((first_block.context == :paragraph && !continuation_connects_first_block) ||
117
- ((content_adjacent || !continuation_connects_first_block) && first_block.context == :literal &&
118
- first_block.option?('listparagraph')))
119
-
120
- block = blocks.shift
121
- block.lines.unshift @text unless @text.nil_or_empty?
122
- @text = block.source
118
+ if (first_block = @blocks[0])
119
+ if first_block.context == :literal
120
+ if (content_adjacent || !continuation_connects_first_block) # && first_block.attributes['listparagraph-option']
121
+ @text = @text.nil_or_empty? ? @blocks.shift.source : %(#{@text}#{LF}#{@blocks.shift.source})
122
+ end
123
+ elsif first_block.context == :paragraph && !continuation_connects_first_block
124
+ @text = @text.nil_or_empty? ? @blocks.shift.source : %(#{@text}#{LF}#{@blocks.shift.source})
125
+ end
123
126
  end
124
127
  nil
125
128
  end
@@ -127,6 +130,5 @@ class ListItem < AbstractBlock
127
130
  def to_s
128
131
  %(#<#{self.class}@#{object_id} {list_context: #{parent.context.inspect}, text: #{@text.inspect}, blocks: #{(@blocks || []).size}}>)
129
132
  end
130
-
131
133
  end
132
134
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'logger'
2
3
 
3
4
  module Asciidoctor
@@ -22,7 +23,7 @@ class Logger < ::Logger
22
23
  SEVERITY_LABELS = { 'WARN' => 'WARNING', 'FATAL' => 'FAILED' }
23
24
 
24
25
  def call severity, _, progname, msg
25
- %(#{progname}: #{SEVERITY_LABELS[severity] || severity}: #{::String === msg ? msg : msg.inspect}\n)
26
+ %(#{progname}: #{SEVERITY_LABELS[severity] || severity}: #{::String === msg ? msg : msg.inspect}#{LF})
26
27
  end
27
28
  end
28
29
 
@@ -34,8 +35,7 @@ class Logger < ::Logger
34
35
  end
35
36
 
36
37
  class MemoryLogger < ::Logger
37
- # NOTE Ruby 1.8.7 returns constants as strings instead of symbols
38
- SEVERITY_LABELS = ::Hash[Severity.constants.map {|c| [(Severity.const_get c), c.to_sym] }]
38
+ SEVERITY_LABELS = {}.tap {|accum| (Severity.constants false).each {|c| accum[Severity.const_get c, false] = c } }
39
39
 
40
40
  attr_reader :messages
41
41
 
@@ -46,7 +46,7 @@ class MemoryLogger < ::Logger
46
46
 
47
47
  def add severity, message = nil, progname = nil
48
48
  message = block_given? ? yield : progname unless message
49
- @messages << { :severity => SEVERITY_LABELS[severity || UNKNOWN], :message => message }
49
+ @messages << { severity: SEVERITY_LABELS[severity || UNKNOWN], message: message }
50
50
  true
51
51
  end
52
52
 
@@ -59,7 +59,7 @@ class MemoryLogger < ::Logger
59
59
  end
60
60
 
61
61
  def max_severity
62
- empty? ? nil : @messages.map {|m| Severity.const_get m[:severity] }.max
62
+ empty? ? nil : @messages.map {|m| Severity.const_get m[:severity], false }.max
63
63
  end
64
64
  end
65
65
 
@@ -87,36 +87,37 @@ module LoggerManager
87
87
  @logger ||= (@logger_class.new pipe)
88
88
  end
89
89
 
90
- def logger= logger
91
- @logger = logger || (@logger_class.new $stderr)
90
+ def logger= new_logger
91
+ @logger = new_logger || (@logger_class.new $stderr)
92
92
  end
93
93
 
94
94
  private
95
+
95
96
  def memoize_logger
96
97
  class << self
97
- alias_method :logger, :logger
98
- if RUBY_ENGINE == 'opal'
99
- define_method :logger do @logger end
100
- else
101
- attr_reader :logger
102
- end
98
+ alias logger logger # suppresses warning from CRuby
99
+ attr_reader :logger
103
100
  end
104
101
  end
105
102
  end
106
103
  end
107
104
 
108
105
  module Logging
109
- def self.included into
106
+ # Private: Mixes the {Logging} module as static methods into any class that includes the {Logging} module.
107
+ #
108
+ # into - The Class that includes the {Logging} module
109
+ #
110
+ # Returns nothing
111
+ private_class_method def self.included into
110
112
  into.extend Logging
111
113
  end
112
114
 
113
- private
114
115
  def logger
115
116
  LoggerManager.logger
116
117
  end
117
118
 
118
119
  def message_with_context text, context = {}
119
- ({ :text => text }.merge context).extend Logger::AutoFormattingMessage
120
+ ({ text: text }.merge context).extend Logger::AutoFormattingMessage
120
121
  end
121
122
  end
122
123
  end
@@ -1,6 +1,6 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
- # Public: Methods to parse lines of AsciiDoc into an object hierarchy
3
+ # Internal: Methods to parse lines of AsciiDoc into an object hierarchy
4
4
  # representing the structure of the document. All methods are class methods and
5
5
  # should be invoked from the Parser class. The main entry point is ::next_block.
6
6
  # No Parser instances shall be discovered running around. (Any attempt to
@@ -27,17 +27,17 @@ class Parser
27
27
 
28
28
  BlockMatchData = Struct.new :context, :masq, :tip, :terminator
29
29
 
30
- # Regexp for replacing tab character
31
- TabRx = /\t/
30
+ # String for matching tab character
31
+ TAB = ?\t
32
32
 
33
33
  # Regexp for leading tab indentation
34
34
  TabIndentRx = /^\t+/
35
35
 
36
- StartOfBlockProc = lambda {|l| ((l.start_with? '[') && (BlockAttributeLineRx.match? l)) || (is_delimited_block? l) }
36
+ StartOfBlockProc = proc {|l| ((l.start_with? '[') && (BlockAttributeLineRx.match? l)) || (is_delimited_block? l) }
37
37
 
38
- StartOfListProc = lambda {|l| AnyListRx.match? l }
38
+ StartOfListProc = proc {|l| AnyListRx.match? l }
39
39
 
40
- StartOfBlockOrListProc = lambda {|l| (is_delimited_block? l) || ((l.start_with? '[') && (BlockAttributeLineRx.match? l)) || (AnyListRx.match? l) }
40
+ StartOfBlockOrListProc = proc {|l| (is_delimited_block? l) || ((l.start_with? '[') && (BlockAttributeLineRx.match? l)) || (AnyListRx.match? l) }
41
41
 
42
42
  NoOp = nil
43
43
 
@@ -66,16 +66,13 @@ class Parser
66
66
  'm' => :monospaced,
67
67
  'h' => :header,
68
68
  'l' => :literal,
69
- 'v' => :verse,
70
69
  'a' => :asciidoc
71
70
  }
72
71
 
73
- # Public: Make sure the Parser object doesn't get initialized.
72
+ # Hide the default constructor to make sure this class doesn't get instantiated.
74
73
  #
75
- # Raises RuntimeError if this constructor is invoked.
76
- def initialize
77
- raise 'Au contraire, mon frere. No parser instances will be running around.'
78
- end
74
+ # Raises NoMethodError if an attempt is made to invoke the constructor.
75
+ private_class_method :new
79
76
 
80
77
  # Public: Parses AsciiDoc source read from the Reader into the Document
81
78
  #
@@ -131,48 +128,56 @@ class Parser
131
128
 
132
129
  # yep, document title logic in AsciiDoc is just insanity
133
130
  # definitely an area for spec refinement
134
- assigned_doctitle = nil
131
+
135
132
  unless (val = doc_attrs['doctitle']).nil_or_empty?
136
- document.title = assigned_doctitle = val
133
+ document.title = doctitle_attr_val = val
137
134
  end
138
135
 
139
136
  # if the first line is the document title, add a header to the document and parse the header metadata
140
137
  if implicit_doctitle
141
138
  source_location = reader.cursor if document.sourcemap
142
- document.id, _, doctitle, _, atx = parse_section_title reader, document
143
- document.title = assigned_doctitle = doctitle unless assigned_doctitle
139
+ document.id, _, l0_section_title, _, atx = parse_section_title reader, document
140
+ if doctitle_attr_val
141
+ # NOTE doctitle attribute (set above or below implicit doctitle) overrides implicit doctitle
142
+ l0_section_title = nil
143
+ else
144
+ document.title = l0_section_title
145
+ doc_attrs['doctitle'] = doctitle_attr_val = document.apply_header_subs l0_section_title
146
+ end
144
147
  document.header.source_location = source_location if source_location
145
- # default to compat-mode if document uses atx-style doctitle
148
+ # default to compat-mode if document has setext doctitle
146
149
  doc_attrs['compat-mode'] = '' unless atx || (document.attribute_locked? 'compat-mode')
147
150
  if (separator = block_attrs['separator'])
148
151
  doc_attrs['title-separator'] = separator unless document.attribute_locked? 'title-separator'
149
152
  end
150
- doc_attrs['doctitle'] = section_title = doctitle
151
153
  if (doc_id = block_attrs['id'])
152
154
  document.id = doc_id
153
155
  else
154
156
  doc_id = document.id
155
157
  end
156
- if (doc_role = block_attrs['role'])
157
- doc_attrs['docrole'] = doc_role
158
+ if (role = block_attrs['role'])
159
+ doc_attrs['role'] = role
158
160
  end
159
- if (doc_reftext = block_attrs['reftext'])
160
- doc_attrs['reftext'] = doc_reftext
161
+ if (reftext = block_attrs['reftext'])
162
+ doc_attrs['reftext'] = reftext
161
163
  end
162
- block_attrs = {}
164
+ block_attrs.clear
165
+ (modified_attrs = document.instance_variable_get :@attributes_modified).delete 'doctitle'
163
166
  parse_header_metadata reader, document
167
+ if modified_attrs.include? 'doctitle'
168
+ if (val = doc_attrs['doctitle']).nil_or_empty? || val == doctitle_attr_val
169
+ doc_attrs['doctitle'] = doctitle_attr_val
170
+ else
171
+ document.title = val
172
+ end
173
+ elsif !l0_section_title
174
+ modified_attrs << 'doctitle'
175
+ end
164
176
  document.register :refs, [doc_id, document] if doc_id
165
177
  end
166
178
 
167
- unless (val = doc_attrs['doctitle']).nil_or_empty? || val == section_title
168
- document.title = assigned_doctitle = val
169
- end
170
-
171
- # restore doctitle attribute to original assignment
172
- doc_attrs['doctitle'] = assigned_doctitle if assigned_doctitle
173
-
174
179
  # parse title and consume name section of manpage document
175
- parse_manpage_header(reader, document, block_attrs) if document.doctype == 'manpage'
180
+ parse_manpage_header reader, document, block_attrs if document.doctype == 'manpage'
176
181
 
177
182
  # NOTE block_attrs are the block-level attributes (not document attributes) that
178
183
  # precede the first line of content (document title, first section or first block)
@@ -187,7 +192,7 @@ class Parser
187
192
  doc_attrs['manvolnum'] = manvolnum = $2
188
193
  doc_attrs['mantitle'] = (((mantitle = $1).include? ATTR_REF_HEAD) ? (document.sub_attributes mantitle) : mantitle).downcase
189
194
  else
190
- logger.error message_with_context 'non-conforming manpage title', :source_location => (reader.cursor_at_line 1)
195
+ logger.error message_with_context 'non-conforming manpage title', source_location: (reader.cursor_at_line 1)
191
196
  # provide sensible fallbacks
192
197
  doc_attrs['mantitle'] = doc_attrs['doctitle'] || doc_attrs['docname'] || 'command'
193
198
  doc_attrs['manvolnum'] = manvolnum = '1'
@@ -206,7 +211,7 @@ class Parser
206
211
  if (name_section_level = is_next_line_section? reader, {})
207
212
  if name_section_level == 1
208
213
  name_section = initialize_section reader, document, {}
209
- name_section_buffer = (reader.read_lines_until :break_on_blank_lines => true, :skip_line_comments => true).map(&:lstrip).join ' '
214
+ name_section_buffer = (reader.read_lines_until break_on_blank_lines: true, skip_line_comments: true).map {|l| l.lstrip }.join ' '
210
215
  if ManpageNamePurposeRx =~ name_section_buffer
211
216
  doc_attrs['manname-title'] ||= name_section.title
212
217
  doc_attrs['manname-id'] = name_section.id if name_section.id
@@ -236,8 +241,8 @@ class Parser
236
241
  end
237
242
  if error_msg
238
243
  reader.restore_save
239
- logger.error message_with_context error_msg, :source_location => reader.cursor
240
- doc_attrs['manname'] = (manname = doc_attrs['docname'] || 'command')
244
+ logger.error message_with_context error_msg, source_location: reader.cursor
245
+ doc_attrs['manname'] = manname = doc_attrs['docname'] || 'command'
241
246
  doc_attrs['mannames'] = [manname]
242
247
  if document.backend == 'manpage'
243
248
  doc_attrs['docname'] = manname
@@ -275,7 +280,7 @@ class Parser
275
280
  # source
276
281
  # # => "= Greetings\n\nThis is my doc.\n\n== Salutations\n\nIt is awesome."
277
282
  #
278
- # reader = Reader.new source, nil, :normalize => true
283
+ # reader = Reader.new source, nil, normalize: true
279
284
  # # create empty document to parent the section
280
285
  # # and hold attributes extracted from header
281
286
  # doc = Document.new
@@ -293,11 +298,11 @@ class Parser
293
298
  # check if we are at the start of processing the document
294
299
  # NOTE we could drop a hint in the attributes to indicate
295
300
  # that we are at a section title (so we don't have to check)
296
- if parent.context == :document && parent.blocks.empty? && ((has_header = parent.has_header?) ||
301
+ if parent.context == :document && parent.blocks.empty? && ((has_header = parent.header?) ||
297
302
  (attributes.delete 'invalid-header') || !(is_next_line_section? reader, attributes))
298
303
  book = (document = parent).doctype == 'book'
299
304
  if has_header || (book && attributes[1] != 'abstract')
300
- preamble = intro = (Block.new parent, :preamble, :content_model => :compound)
305
+ preamble = intro = Block.new parent, :preamble, content_model: :compound
301
306
  preamble.title = parent.attr 'preface-title' if book && (parent.attr? 'preface-title')
302
307
  parent.blocks << preamble
303
308
  end
@@ -346,16 +351,16 @@ class Parser
346
351
  if expected_next_level
347
352
  unless next_level == expected_next_level || (expected_next_level_alt && next_level == expected_next_level_alt) || expected_next_level < 0
348
353
  expected_condition = expected_next_level_alt ? %(expected levels #{expected_next_level_alt} or #{expected_next_level}) : %(expected level #{expected_next_level})
349
- logger.warn message_with_context %(section title out of sequence: #{expected_condition}, got level #{next_level}), :source_location => reader.cursor
354
+ logger.warn message_with_context %(section title out of sequence: #{expected_condition}, got level #{next_level}), source_location: reader.cursor
350
355
  end
351
356
  else
352
- logger.error message_with_context %(#{sectname} sections do not support nested sections), :source_location => reader.cursor
357
+ logger.error message_with_context %(#{sectname} sections do not support nested sections), source_location: reader.cursor
353
358
  end
354
359
  new_section, attributes = next_section reader, section, attributes
355
360
  section.assign_numeral new_section
356
361
  section.blocks << new_section
357
362
  elsif next_level == 0 && section == document
358
- logger.error message_with_context 'level 0 sections can only be used when doctype is book', :source_location => reader.cursor unless book
363
+ logger.error message_with_context 'level 0 sections can only be used when doctype is book', source_location: reader.cursor unless book
359
364
  new_section, attributes = next_section reader, section, attributes
360
365
  section.assign_numeral new_section
361
366
  section.blocks << new_section
@@ -366,7 +371,7 @@ class Parser
366
371
  else
367
372
  # just take one block or else we run the risk of overrunning section boundaries
368
373
  block_cursor = reader.cursor
369
- if (new_block = next_block reader, intro || section, attributes, :parse_metadata => false)
374
+ if (new_block = next_block reader, intro || section, attributes, parse_metadata: false)
370
375
  # REVIEW this may be doing too much
371
376
  if part
372
377
  if !section.blocks?
@@ -378,7 +383,7 @@ class Parser
378
383
  new_block.style = 'partintro'
379
384
  # emulate [partintro] open block
380
385
  else
381
- new_block.parent = (intro = Block.new section, :open, :content_model => :compound)
386
+ new_block.parent = (intro = Block.new section, :open, content_model: :compound)
382
387
  intro.style = 'partintro'
383
388
  section.blocks << intro
384
389
  end
@@ -387,10 +392,10 @@ class Parser
387
392
  first_block = section.blocks[0]
388
393
  # open the [partintro] open block for appending
389
394
  if !intro && first_block.content_model == :compound
390
- logger.error message_with_context 'illegal block content outside of partintro block', :source_location => block_cursor
395
+ logger.error message_with_context 'illegal block content outside of partintro block', source_location: block_cursor
391
396
  # rebuild [partintro] paragraph as an open block
392
397
  elsif first_block.content_model != :compound
393
- new_block.parent = (intro = Block.new section, :open, :content_model => :compound)
398
+ new_block.parent = (intro = Block.new section, :open, content_model: :compound)
394
399
  intro.style = 'partintro'
395
400
  section.blocks.shift
396
401
  if first_block.style == 'partintro'
@@ -404,7 +409,7 @@ class Parser
404
409
  end
405
410
 
406
411
  (intro || section).blocks << new_block
407
- attributes = {}
412
+ attributes.clear
408
413
  #else
409
414
  # # don't clear attributes if we don't find a block because they may
410
415
  # # be trailing attributes that didn't get associated with a block
@@ -416,7 +421,7 @@ class Parser
416
421
 
417
422
  if part
418
423
  unless section.blocks? && section.blocks[-1].context == :section
419
- logger.error message_with_context 'invalid part, must have at least one section (e.g., chapter, appendix, etc.)', :source_location => reader.cursor
424
+ logger.error message_with_context 'invalid part, must have at least one section (e.g., chapter, appendix, etc.)', source_location: reader.cursor
420
425
  end
421
426
  # NOTE we could try to avoid creating a preamble in the first place, though
422
427
  # that would require reworking assumptions in next_section since the preamble
@@ -472,7 +477,7 @@ class Parser
472
477
  # used and block content is acceptable
473
478
  if (text_only = options[:text]) && skipped > 0
474
479
  options.delete :text
475
- text_only = false
480
+ text_only = nil
476
481
  end
477
482
 
478
483
  document = parent.document
@@ -499,19 +504,21 @@ class Parser
499
504
  if (delimited_block = is_delimited_block? this_line, true)
500
505
  block_context = cloaked_context = delimited_block.context
501
506
  terminator = delimited_block.terminator
502
- if !style
503
- style = attributes['style'] = block_context.to_s
504
- elsif style != block_context.to_s
505
- if delimited_block.masq.include? style
506
- block_context = style.to_sym
507
- elsif delimited_block.masq.include?('admonition') && ADMONITION_STYLES.include?(style)
508
- block_context = :admonition
509
- elsif block_extensions && extensions.registered_for_block?(style, block_context)
510
- block_context = style.to_sym
511
- else
512
- logger.warn message_with_context %(invalid style for #{block_context} block: #{style}), :source_location => reader.cursor_at_mark
513
- style = block_context.to_s
507
+ if style
508
+ unless style == block_context.to_s
509
+ if delimited_block.masq.include? style
510
+ block_context = style.to_sym
511
+ elsif delimited_block.masq.include?('admonition') && ADMONITION_STYLES.include?(style)
512
+ block_context = :admonition
513
+ elsif block_extensions && extensions.registered_for_block?(style, block_context)
514
+ block_context = style.to_sym
515
+ else
516
+ logger.debug message_with_context %(unknown style for #{block_context} block: #{style}), source_location: reader.cursor_at_mark if logger.debug?
517
+ style = block_context.to_s
518
+ end
514
519
  end
520
+ else
521
+ style = attributes['style'] = block_context.to_s
515
522
  end
516
523
  end
517
524
 
@@ -520,7 +527,7 @@ class Parser
520
527
  # returns nil if the line should be dropped
521
528
  while true
522
529
  # process lines verbatim
523
- if style && Compliance.strict_verbatim_paragraphs && VERBATIM_STYLES.include?(style)
530
+ if style && Compliance.strict_verbatim_paragraphs && (VERBATIM_STYLES.include? style)
524
531
  block_context = style.to_sym
525
532
  reader.unshift_line this_line
526
533
  # advance to block parsing =>
@@ -540,7 +547,7 @@ class Parser
540
547
  #!(this_line.start_with? ' ') &&
541
548
  (MarkdownThematicBreakRx.match? this_line)
542
549
  # NOTE we're letting break lines (horizontal rule, page_break, etc) have attributes
543
- block = Block.new(parent, :thematic_break, :content_model => :empty)
550
+ block = Block.new(parent, :thematic_break, content_model: :empty)
544
551
  break
545
552
  end
546
553
  elsif this_line.start_with? TAB
@@ -548,17 +555,17 @@ class Parser
548
555
  else
549
556
  indented, ch0 = false, this_line.chr
550
557
  layout_break_chars = md_syntax ? HYBRID_LAYOUT_BREAK_CHARS : LAYOUT_BREAK_CHARS
551
- if (layout_break_chars.key? ch0) && (md_syntax ? (ExtLayoutBreakRx.match? this_line) :
552
- (this_line == ch0 * (ll = this_line.length) && ll > 2))
558
+ if (layout_break_chars.key? ch0) &&
559
+ (md_syntax ? (ExtLayoutBreakRx.match? this_line) : (uniform? this_line, ch0, (ll = this_line.length)) && ll > 2)
553
560
  # NOTE we're letting break lines (horizontal rule, page_break, etc) have attributes
554
- block = Block.new(parent, layout_break_chars[ch0], :content_model => :empty)
561
+ block = Block.new(parent, layout_break_chars[ch0], content_model: :empty)
555
562
  break
556
563
  # NOTE very rare that a text-only line will end in ] (e.g., inline macro), so check that first
557
564
  elsif (this_line.end_with? ']') && (this_line.include? '::')
558
565
  #if (this_line.start_with? 'image', 'video', 'audio') && BlockMediaMacroRx =~ this_line
559
566
  if (ch0 == 'i' || (this_line.start_with? 'video:', 'audio:')) && BlockMediaMacroRx =~ this_line
560
567
  blk_ctx, target, blk_attrs = $1.to_sym, $2, $3
561
- block = Block.new parent, blk_ctx, :content_model => :empty
568
+ block = Block.new parent, blk_ctx, content_model: :empty
562
569
  if blk_attrs
563
570
  case blk_ctx
564
571
  when :video
@@ -568,14 +575,14 @@ class Parser
568
575
  else # :image
569
576
  posattrs = ['alt', 'width', 'height']
570
577
  end
571
- block.parse_attributes blk_attrs, posattrs, :sub_input => true, :into => attributes
578
+ block.parse_attributes blk_attrs, posattrs, sub_input: true, into: attributes
572
579
  end
573
580
  # style doesn't have special meaning for media macros
574
581
  attributes.delete 'style' if attributes.key? 'style'
575
- if (target.include? ATTR_REF_HEAD) && (target = block.sub_attributes target, :attribute_missing => 'drop-line').empty?
582
+ if (target.include? ATTR_REF_HEAD) && (target = block.sub_attributes target, attribute_missing: 'drop-line').empty?
576
583
  # retain as unparsed if attribute-missing is skip
577
584
  if (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'skip'
578
- return Block.new(parent, :paragraph, :content_model => :simple, :source => [this_line])
585
+ return Block.new(parent, :paragraph, content_model: :simple, source: [this_line])
579
586
  # otherwise, drop the line
580
587
  else
581
588
  attributes.clear
@@ -599,32 +606,38 @@ class Parser
599
606
  break
600
607
 
601
608
  elsif ch0 == 't' && (this_line.start_with? 'toc:') && BlockTocMacroRx =~ this_line
602
- block = Block.new parent, :toc, :content_model => :empty
603
- block.parse_attributes $1, [], :into => attributes if $1
609
+ block = Block.new parent, :toc, content_model: :empty
610
+ block.parse_attributes $1, [], into: attributes if $1
604
611
  break
605
612
 
606
- elsif block_macro_extensions && CustomBlockMacroRx =~ this_line &&
607
- (extension = extensions.registered_for_block_macro? $1)
608
- target, content = $2, $3
609
- if (target.include? ATTR_REF_HEAD) && (target = parent.sub_attributes target).empty? &&
610
- (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line'
611
- attributes.clear
612
- return
613
- end
614
- if extension.config[:content_model] == :attributes
615
- document.parse_attributes content, extension.config[:pos_attrs] || [], :sub_input => true, :into => attributes if content
616
- else
617
- attributes['text'] = content || ''
618
- end
619
- if (default_attrs = extension.config[:default_attrs])
620
- attributes.update(default_attrs) {|_, old_v| old_v }
621
- end
622
- if (block = extension.process_method[parent, target, attributes])
623
- attributes.replace block.attributes
624
- break
613
+ elsif block_macro_extensions ? (CustomBlockMacroRx =~ this_line &&
614
+ (extension = extensions.registered_for_block_macro? $1) || (report_unknown_block_macro = logger.debug?)) :
615
+ (logger.debug? && (report_unknown_block_macro = CustomBlockMacroRx =~ this_line))
616
+ if report_unknown_block_macro
617
+ logger.debug message_with_context %(unknown name for block macro: #{$1}), source_location: reader.cursor_at_mark
625
618
  else
626
- attributes.clear
627
- return
619
+ target = $2
620
+ content = $3
621
+ if (target.include? ATTR_REF_HEAD) && (target = parent.sub_attributes target).empty? &&
622
+ (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line'
623
+ attributes.clear
624
+ return
625
+ end
626
+ if extension.config[:content_model] == :attributes
627
+ document.parse_attributes content, extension.config[:pos_attrs] || [], sub_input: true, into: attributes if content
628
+ else
629
+ attributes['text'] = content || ''
630
+ end
631
+ if (default_attrs = extension.config[:default_attrs])
632
+ attributes.update(default_attrs) {|_, old_v| old_v }
633
+ end
634
+ if (block = extension.process_method[parent, target, attributes])
635
+ attributes.replace block.attributes
636
+ break
637
+ else
638
+ attributes.clear
639
+ return
640
+ end
628
641
  end
629
642
  end
630
643
  end
@@ -640,19 +653,19 @@ class Parser
640
653
 
641
654
  elsif UnorderedListRx.match? this_line
642
655
  reader.unshift_line this_line
643
- attributes['style'] = (style = 'bibliography') if !style && Section === parent && parent.sectname == 'bibliography'
656
+ attributes['style'] = style = 'bibliography' if !style && Section === parent && parent.sectname == 'bibliography'
644
657
  block = parse_list(reader, :ulist, parent, style)
645
658
  break
646
659
 
647
- elsif (match = OrderedListRx.match(this_line))
660
+ elsif OrderedListRx.match? this_line
648
661
  reader.unshift_line this_line
649
662
  block = parse_list(reader, :olist, parent, style)
650
663
  attributes['style'] = block.style if block.style
651
664
  break
652
665
 
653
- elsif (match = DescriptionListRx.match(this_line))
666
+ elsif ((this_line.include? '::') || (this_line.include? ';;')) && DescriptionListRx =~ this_line
654
667
  reader.unshift_line this_line
655
- block = parse_description_list(reader, match, parent)
668
+ block = parse_description_list(reader, $~, parent)
656
669
  break
657
670
 
658
671
  elsif (style == 'float' || style == 'discrete') && (Compliance.underline_style_section_titles ?
@@ -660,7 +673,7 @@ class Parser
660
673
  reader.unshift_line this_line
661
674
  float_id, float_reftext, float_title, float_level = parse_section_title reader, document, attributes['id']
662
675
  attributes['reftext'] = float_reftext if float_reftext
663
- block = Block.new(parent, :floating_title, :content_model => :empty)
676
+ block = Block.new(parent, :floating_title, content_model: :empty)
664
677
  block.title = float_title
665
678
  attributes.delete 'title'
666
679
  block.id = float_id || ((doc_attrs.key? 'sectids') ? (Section.generate_id block.title, document) : nil)
@@ -689,7 +702,7 @@ class Parser
689
702
  # advance to block parsing =>
690
703
  break
691
704
  else
692
- logger.warn message_with_context %(invalid style for paragraph: #{style}), :source_location => reader.cursor_at_mark
705
+ logger.debug message_with_context %(unknown style for paragraph: #{style}), source_location: reader.cursor_at_mark if logger.debug?
693
706
  style = nil
694
707
  # continue to process paragraph
695
708
  end
@@ -700,32 +713,36 @@ class Parser
700
713
  # a literal paragraph: contiguous lines starting with at least one whitespace character
701
714
  # NOTE style can only be nil or "normal" at this point
702
715
  if indented && !style
703
- lines = read_paragraph_lines reader, (in_list = ListItem === parent) && skipped == 0, :skip_line_comments => text_only
716
+ lines = read_paragraph_lines reader, (list_item = options[:list_item]) && skipped == 0, skip_line_comments: text_only
704
717
  adjust_indentation! lines
705
- block = Block.new(parent, :literal, :content_model => :verbatim, :source => lines, :attributes => attributes)
706
- # a literal gets special meaning inside of a description list
707
- # TODO this feels hacky, better way to distinguish from explicit literal block?
708
- block.set_option('listparagraph') if in_list
718
+ block = Block.new(parent, :literal, content_model: :verbatim, source: lines, attributes: attributes)
719
+ if list_item
720
+ # a literal gets special meaning inside of a description list
721
+ block.set_option 'listparagraph'
722
+ block.default_subs = []
723
+ end
709
724
  # a normal paragraph: contiguous non-blank/non-continuation lines (left-indented or normal style)
710
725
  else
711
- lines = read_paragraph_lines reader, skipped == 0 && ListItem === parent, :skip_line_comments => true
726
+ lines = read_paragraph_lines reader, skipped == 0 && options[:list_item], skip_line_comments: true
712
727
  # NOTE don't check indented here since it's extremely rare
713
728
  #if text_only || indented
714
729
  if text_only
715
730
  # if [normal] is used over an indented paragraph, shift content to left margin
716
731
  # QUESTION do we even need to shift since whitespace is normalized by XML in this case?
717
732
  adjust_indentation! lines if indented && style == 'normal'
718
- block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
733
+ block = Block.new(parent, :paragraph, content_model: :simple, source: lines, attributes: attributes)
719
734
  elsif (ADMONITION_STYLE_HEADS.include? ch0) && (this_line.include? ':') && (AdmonitionParagraphRx =~ this_line)
720
735
  lines[0] = $' # string after match
721
736
  attributes['name'] = admonition_name = (attributes['style'] = $1).downcase
722
737
  attributes['textlabel'] = (attributes.delete 'caption') || doc_attrs[%(#{admonition_name}-caption)]
723
- block = Block.new(parent, :admonition, :content_model => :simple, :source => lines, :attributes => attributes)
738
+ block = Block.new(parent, :admonition, content_model: :simple, source: lines, attributes: attributes)
724
739
  elsif md_syntax && ch0 == '>' && this_line.start_with?('> ')
725
740
  lines.map! {|line| line == '>' ? (line.slice 1, line.length) : ((line.start_with? '> ') ? (line.slice 2, line.length) : line) }
726
741
  if lines[-1].start_with? '-- '
727
742
  credit_line = (credit_line = lines.pop).slice 3, credit_line.length
728
- lines.pop while lines[-1].empty?
743
+ unless lines.empty?
744
+ lines.pop while lines[-1].empty?
745
+ end
729
746
  end
730
747
  attributes['style'] = 'quote'
731
748
  # NOTE will only detect discrete (aka free-floating) headings
@@ -743,7 +760,7 @@ class Parser
743
760
  lines.pop while lines[-1].empty?
744
761
  lines << lines.pop.chop # strip trailing quote
745
762
  attributes['style'] = 'quote'
746
- block = Block.new(parent, :quote, :content_model => :simple, :source => lines, :attributes => attributes)
763
+ block = Block.new(parent, :quote, content_model: :simple, source: lines, attributes: attributes)
747
764
  attribution, citetitle = (block.apply_subs credit_line).split ', ', 2
748
765
  attributes['attribution'] = attribution if attribution
749
766
  attributes['citetitle'] = citetitle if citetitle
@@ -751,7 +768,7 @@ class Parser
751
768
  # if [normal] is used over an indented paragraph, shift content to left margin
752
769
  # QUESTION do we even need to shift since whitespace is normalized by XML in this case?
753
770
  adjust_indentation! lines if indented && style == 'normal'
754
- block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
771
+ block = Block.new(parent, :paragraph, content_model: :simple, source: lines, attributes: attributes)
755
772
  end
756
773
 
757
774
  catalog_inline_anchors((lines.join LF), block, document, reader)
@@ -786,9 +803,9 @@ class Parser
786
803
  when :source
787
804
  AttributeList.rekey attributes, [nil, 'language', 'linenums']
788
805
  if doc_attrs.key? 'source-language'
789
- attributes['language'] = doc_attrs['source-language'] || 'text'
806
+ attributes['language'] = doc_attrs['source-language']
790
807
  end unless attributes.key? 'language'
791
- if (attributes.key? 'linenums-option') || (doc_attrs.key? 'source-linenums-option')
808
+ if attributes['linenums-option'] || doc_attrs['source-linenums-option']
792
809
  attributes['linenums'] = ''
793
810
  end unless attributes.key? 'linenums'
794
811
  if doc_attrs.key? 'source-indent'
@@ -798,27 +815,24 @@ class Parser
798
815
 
799
816
  when :fenced_code
800
817
  attributes['style'] = 'source'
801
- if (ll = this_line.length) == 3
802
- language = nil
803
- elsif (comma_idx = (language = this_line.slice 3, ll).index ',')
804
- if comma_idx > 0
805
- language = (language.slice 0, comma_idx).strip
806
- attributes['linenums'] = '' if comma_idx < ll - 4
818
+ if (ll = this_line.length) > 3
819
+ if (comma_idx = (language = this_line.slice 3, ll).index ',')
820
+ if comma_idx > 0
821
+ language = (language.slice 0, comma_idx).strip
822
+ attributes['linenums'] = '' if comma_idx < ll - 4
823
+ else
824
+ attributes['linenums'] = '' if ll > 4
825
+ end
807
826
  else
808
- language = nil
809
- attributes['linenums'] = '' if ll > 4
827
+ language = language.lstrip
810
828
  end
811
- else
812
- language = language.lstrip
813
829
  end
814
830
  if language.nil_or_empty?
815
- if doc_attrs.key? 'source-language'
816
- attributes['language'] = doc_attrs['source-language'] || 'text'
817
- end
831
+ attributes['language'] = doc_attrs['source-language'] if doc_attrs.key? 'source-language'
818
832
  else
819
833
  attributes['language'] = language
820
834
  end
821
- if (attributes.key? 'linenums-option') || (doc_attrs.key? 'source-linenums-option')
835
+ if attributes['linenums-option'] || doc_attrs['source-linenums-option']
822
836
  attributes['linenums'] = ''
823
837
  end unless attributes.key? 'linenums'
824
838
  if doc_attrs.key? 'source-indent'
@@ -839,7 +853,7 @@ class Parser
839
853
 
840
854
  when :table
841
855
  block_cursor = reader.cursor
842
- block_reader = Reader.new reader.read_lines_until(:terminator => terminator, :skip_line_comments => true, :context => :table, :cursor => :at_mark), block_cursor
856
+ block_reader = Reader.new reader.read_lines_until(terminator: terminator, skip_line_comments: true, context: :table, cursor: :at_mark), block_cursor
843
857
  # NOTE it's very rare that format is set when using a format hint char, so short-circuit
844
858
  unless terminator.start_with? '|', '!'
845
859
  # NOTE infer dsv once all other format hint chars are ruled out
@@ -853,9 +867,9 @@ class Parser
853
867
 
854
868
  else
855
869
  if block_extensions && (extension = extensions.registered_for_block?(block_context, cloaked_context))
856
- if (content_model = extension.config[:content_model]) != :skip
857
- if !(pos_attrs = extension.config[:pos_attrs] || []).empty?
858
- AttributeList.rekey(attributes, [nil].concat(pos_attrs))
870
+ unless (content_model = extension.config[:content_model]) == :skip
871
+ unless (pos_attrs = extension.config[:pos_attrs] || []).empty?
872
+ AttributeList.rekey(attributes, [nil] + pos_attrs)
859
873
  end
860
874
  if (default_attrs = extension.config[:default_attrs])
861
875
  default_attrs.each {|k, v| attributes[k] ||= v }
@@ -863,7 +877,7 @@ class Parser
863
877
  # QUESTION should we clone the extension for each cloaked context and set in config?
864
878
  attributes['cloaked-context'] = cloaked_context
865
879
  end
866
- block = build_block block_context, content_model, terminator, parent, reader, attributes, :extension => extension
880
+ block = build_block block_context, content_model, terminator, parent, reader, attributes, extension: extension
867
881
  unless block
868
882
  attributes.clear
869
883
  return
@@ -882,9 +896,9 @@ class Parser
882
896
  # TODO eventually remove the style attribute from the attributes hash
883
897
  #block.style = attributes.delete 'style'
884
898
  block.style = attributes['style']
885
- if (block_id = (block.id ||= attributes['id']))
886
- unless document.register :refs, [block_id, block, attributes['reftext'] || (block.title? ? block.title : nil)]
887
- logger.warn message_with_context %(id assigned to block already in use: #{block_id}), :source_location => reader.cursor_at_mark
899
+ if (block_id = block.id || (block.id = attributes['id']))
900
+ unless document.register :refs, [block_id, block]
901
+ logger.warn message_with_context %(id assigned to block already in use: #{block_id}), source_location: reader.cursor_at_mark
888
902
  end
889
903
  end
890
904
  # FIXME remove the need for this update!
@@ -915,74 +929,49 @@ class Parser
915
929
  reader.read_lines_until opts, &break_condition
916
930
  end
917
931
 
918
- # Public: Determines whether this line is the start of any of the delimited blocks
932
+ # Public: Determines whether this line is the start of a known delimited block.
919
933
  #
920
- # returns the match data if this line is the first line of a delimited block or nil if not
921
- def self.is_delimited_block? line, return_match_data = false
934
+ # Returns the BlockMatchData (if return_match_data is true) or true (if return_match_data is false) if this line is
935
+ # the start of a delimited block, otherwise nothing.
936
+ def self.is_delimited_block? line, return_match_data = nil
922
937
  # highly optimized for best performance
923
- return unless (line_len = line.length) > 1 && DELIMITED_BLOCK_HEADS.include?(line.slice 0, 2)
924
- # catches open block
938
+ return unless (line_len = line.length) > 1 && DELIMITED_BLOCK_HEADS[line.slice 0, 2]
939
+ # open block
925
940
  if line_len == 2
926
941
  tip = line
927
- tl = 2
942
+ tip_len = 2
928
943
  else
929
- # catches all other delimited blocks, including fenced code
930
- if line_len <= 4
944
+ # all other delimited blocks, including fenced code
945
+ if line_len < 5
931
946
  tip = line
932
- tl = line_len
947
+ tip_len = line_len
933
948
  else
934
- tip = line.slice 0, 4
935
- tl = 4
949
+ tip = line.slice 0, (tip_len = 4)
936
950
  end
937
-
938
951
  # special case for fenced code blocks
939
- # REVIEW review this logic
940
- fenced_code = false
941
- if Compliance.markdown_syntax
942
- tip_3 = (tl == 4 ? tip.chop : tip)
943
- if tip_3 == '```'
944
- if tl == 4 && tip.end_with?('`')
952
+ if Compliance.markdown_syntax && (tip.start_with? '`')
953
+ if tip_len == 4
954
+ if tip == '````'
955
+ return
956
+ elsif (tip = tip.chop) == '```'
957
+ line = tip
958
+ line_len = tip_len = 3
959
+ else
945
960
  return
946
961
  end
947
- tip = tip_3
948
- tl = 3
949
- fenced_code = true
950
- end
951
- end
952
-
953
- # short circuit if not a fenced code block
954
- return if tl == 3 && !fenced_code
955
- end
956
-
957
- if DELIMITED_BLOCKS.key? tip
958
- # tip is the full line when delimiter is minimum length
959
- if tl < 4 || tl == line_len
960
- if return_match_data
961
- context, masq = DELIMITED_BLOCKS[tip]
962
- BlockMatchData.new(context, masq, tip, tip)
962
+ elsif tip == '```'
963
+ # keep it
963
964
  else
964
- true
965
+ return
965
966
  end
966
- elsif %(#{tip}#{tip.slice(-1, 1) * (line_len - tl)}) == line
967
- if return_match_data
968
- context, masq = DELIMITED_BLOCKS[tip]
969
- BlockMatchData.new(context, masq, tip, line)
970
- else
971
- true
972
- end
973
- # only enable if/when we decide to support non-congruent block delimiters
974
- #elsif (match = BlockDelimiterRx.match(line))
975
- # if return_match_data
976
- # context, masq = DELIMITED_BLOCKS[tip]
977
- # BlockMatchData.new(context, masq, tip, match[0])
978
- # else
979
- # true
980
- # end
981
- else
982
- nil
967
+ elsif tip_len == 3
968
+ return
983
969
  end
984
- else
985
- nil
970
+ end
971
+ # NOTE line matches the tip when delimiter is minimum length or fenced code
972
+ context, masq = DELIMITED_BLOCKS[tip]
973
+ if context && (line_len == tip_len || (uniform? (line.slice 1, line_len), DELIMITED_BLOCK_TAILS[tip], (line_len - 1)))
974
+ return_match_data ? (BlockMatchData.new context, masq, tip, line) : true
986
975
  end
987
976
  end
988
977
 
@@ -1000,16 +989,16 @@ class Parser
1000
989
 
1001
990
  if terminator.nil?
1002
991
  if parse_as_content_model == :verbatim
1003
- lines = reader.read_lines_until :break_on_blank_lines => true, :break_on_list_continuation => true
992
+ lines = reader.read_lines_until break_on_blank_lines: true, break_on_list_continuation: true
1004
993
  else
1005
994
  content_model = :simple if content_model == :compound
1006
995
  # TODO we could also skip processing if we're able to detect reader is a BlockReader
1007
- lines = read_paragraph_lines reader, false, :skip_line_comments => true, :skip_processing => skip_processing
996
+ lines = read_paragraph_lines reader, false, skip_line_comments: true, skip_processing: skip_processing
1008
997
  # QUESTION check for empty lines after grabbing lines for simple content model?
1009
998
  end
1010
999
  block_reader = nil
1011
1000
  elsif parse_as_content_model != :compound
1012
- lines = reader.read_lines_until :terminator => terminator, :skip_processing => skip_processing, :context => block_context, :cursor => :at_mark
1001
+ lines = reader.read_lines_until terminator: terminator, skip_processing: skip_processing, context: block_context, cursor: :at_mark
1013
1002
  block_reader = nil
1014
1003
  # terminator is false when reader has already been prepared
1015
1004
  elsif terminator == false
@@ -1018,14 +1007,15 @@ class Parser
1018
1007
  else
1019
1008
  lines = nil
1020
1009
  block_cursor = reader.cursor
1021
- block_reader = Reader.new reader.read_lines_until(:terminator => terminator, :skip_processing => skip_processing, :context => block_context, :cursor => :at_mark), block_cursor
1010
+ block_reader = Reader.new reader.read_lines_until(terminator: terminator, skip_processing: skip_processing, context: block_context, cursor: :at_mark), block_cursor
1022
1011
  end
1023
1012
 
1024
1013
  if content_model == :verbatim
1014
+ tab_size = (attributes['tabsize'] || parent.document.attributes['tabsize']).to_i
1025
1015
  if (indent = attributes['indent'])
1026
- adjust_indentation! lines, indent, (attributes['tabsize'] || parent.document.attributes['tabsize'])
1027
- elsif (tab_size = (attributes['tabsize'] || parent.document.attributes['tabsize']).to_i) > 0
1028
- adjust_indentation! lines, nil, tab_size
1016
+ adjust_indentation! lines, indent.to_i, tab_size
1017
+ elsif tab_size > 0
1018
+ adjust_indentation! lines, -1, tab_size
1029
1019
  end
1030
1020
  elsif content_model == :skip
1031
1021
  # QUESTION should we still invoke process method if extension is specified?
@@ -1040,7 +1030,7 @@ class Parser
1040
1030
  # FIXME if the content model is set to compound, but we only have simple in this context, then
1041
1031
  # forcefully set the content_model to simple to prevent parsing blocks from children
1042
1032
  # TODO document this behavior!!
1043
- if block.content_model == :compound && !(lines = block.lines).nil_or_empty?
1033
+ if block.content_model == :compound && !(lines = block.lines).empty?
1044
1034
  content_model = :compound
1045
1035
  block_reader = Reader.new lines
1046
1036
  end
@@ -1048,7 +1038,7 @@ class Parser
1048
1038
  return
1049
1039
  end
1050
1040
  else
1051
- block = Block.new(parent, block_context, :content_model => content_model, :source => lines, :attributes => attributes)
1041
+ block = Block.new(parent, block_context, content_model: content_model, source: lines, attributes: attributes)
1052
1042
  end
1053
1043
 
1054
1044
  # QUESTION should we have an explicit map or can we rely on check for *-caption attribute?
@@ -1088,10 +1078,11 @@ class Parser
1088
1078
  # style - The block style assigned to this list (optional, default: nil)
1089
1079
  #
1090
1080
  # Returns the Block encapsulating the parsed unordered or ordered list
1091
- def self.parse_list(reader, list_type, parent, style)
1092
- list_block = List.new(parent, list_type)
1081
+ def self.parse_list reader, list_type, parent, style
1082
+ list_block = List.new parent, list_type
1083
+ list_rx = ListRxMap[list_type]
1093
1084
 
1094
- while reader.has_more_lines? && (list_rx ||= ListRxMap[list_type]) =~ reader.peek_line
1085
+ while reader.has_more_lines? && list_rx =~ reader.peek_line
1095
1086
  # NOTE parse_list_item will stop at sibling item or end of list; never sees ancestor items
1096
1087
  if (list_item = parse_list_item reader, list_block, $~, $1, style)
1097
1088
  list_block.items << list_item
@@ -1112,13 +1103,11 @@ class Parser
1112
1103
  def self.catalog_callouts(text, document)
1113
1104
  found = false
1114
1105
  autonum = 0
1115
- text.scan(CalloutScanRx) {
1116
- # lead with assignments for Ruby 1.8.7 compat
1117
- captured, num = $&, $2
1118
- document.callouts.register num == '.' ? (autonum += 1).to_s : num unless captured.start_with? '\\'
1106
+ text.scan CalloutScanRx do
1107
+ document.callouts.register $2 == '.' ? (autonum += 1).to_s : $2 unless $&.start_with? '\\'
1119
1108
  # we have to mark as found even if it's escaped so it can be unescaped
1120
1109
  found = true
1121
- } if text.include? '<'
1110
+ end if text.include? '<'
1122
1111
  found
1123
1112
  end
1124
1113
 
@@ -1132,11 +1121,11 @@ class Parser
1132
1121
  #
1133
1122
  # Returns nothing
1134
1123
  def self.catalog_inline_anchor id, reftext, node, location, doc = nil
1135
- doc ||= node.document
1124
+ doc = node.document unless doc
1136
1125
  reftext = doc.sub_attributes reftext if reftext && (reftext.include? ATTR_REF_HEAD)
1137
- unless doc.register :refs, [id, (Inline.new node, :anchor, reftext, :type => :ref, :id => id), reftext]
1126
+ unless doc.register :refs, [id, (Inline.new node, :anchor, reftext, type: :ref, id: id)]
1138
1127
  location = location.cursor if Reader === location
1139
- logger.warn message_with_context %(id assigned to anchor already in use: #{id}), :source_location => location
1128
+ logger.warn message_with_context %(id assigned to anchor already in use: #{id}), source_location: location
1140
1129
  end
1141
1130
  nil
1142
1131
  end
@@ -1149,9 +1138,7 @@ class Parser
1149
1138
  #
1150
1139
  # Returns nothing
1151
1140
  def self.catalog_inline_anchors text, block, document, reader
1152
- text.scan(InlineAnchorScanRx) do
1153
- # alias match for Ruby 1.8.7 compat
1154
- m = $~
1141
+ text.scan InlineAnchorScanRx do
1155
1142
  if (id = $1)
1156
1143
  if (reftext = $2)
1157
1144
  next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
@@ -1163,12 +1150,12 @@ class Parser
1163
1150
  next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1164
1151
  end
1165
1152
  end
1166
- unless document.register :refs, [id, (Inline.new block, :anchor, reftext, :type => :ref, :id => id), reftext]
1153
+ unless document.register :refs, [id, (Inline.new block, :anchor, reftext, type: :ref, id: id)]
1167
1154
  location = reader.cursor_at_mark
1168
- if (offset = (m.pre_match.count LF) + ((m[0].start_with? LF) ? 1 : 0)) > 0
1155
+ if (offset = ($`.count LF) + (($&.start_with? LF) ? 1 : 0)) > 0
1169
1156
  (location = location.dup).advance offset
1170
1157
  end
1171
- logger.warn message_with_context %(id assigned to anchor already in use: #{id}), :source_location => location
1158
+ logger.warn message_with_context %(id assigned to anchor already in use: #{id}), source_location: location
1172
1159
  end
1173
1160
  end if (text.include? '[[') || (text.include? 'or:')
1174
1161
  nil
@@ -1184,8 +1171,8 @@ class Parser
1184
1171
  # Returns nothing
1185
1172
  def self.catalog_inline_biblio_anchor id, reftext, node, reader
1186
1173
  # QUESTION should we sub attributes in reftext (like with regular anchors)?
1187
- unless node.document.register :refs, [id, (Inline.new node, :anchor, (styled_reftext = %([#{reftext || id}])), :type => :bibref, :id => id), styled_reftext]
1188
- logger.warn message_with_context %(id assigned to bibliography anchor already in use: #{id}), :source_location => reader.cursor
1174
+ unless node.document.register :refs, [id, (Inline.new node, :anchor, reftext && %([#{reftext}]), type: :bibref, id: id)]
1175
+ logger.warn message_with_context %(id assigned to bibliography anchor already in use: #{id}), source_location: reader.cursor
1189
1176
  end
1190
1177
  nil
1191
1178
  end
@@ -1197,23 +1184,20 @@ class Parser
1197
1184
  # parent - The parent Block to which this description list belongs
1198
1185
  #
1199
1186
  # Returns the Block encapsulating the parsed description list
1200
- def self.parse_description_list(reader, match, parent)
1201
- list_block = List.new(parent, :dlist)
1202
- previous_pair = nil
1203
- # allows us to capture until we find a description item
1204
- # that uses the same delimiter (::, :::, :::: or ;;)
1187
+ def self.parse_description_list reader, match, parent
1188
+ list_block = List.new parent, :dlist
1189
+ # detects a description list item that uses the same delimiter (::, :::, :::: or ;;)
1205
1190
  sibling_pattern = DescriptionListSiblingRx[match[2]]
1191
+ list_block.items << (current_pair = parse_list_item reader, list_block, match, sibling_pattern)
1206
1192
 
1207
- # NOTE skip the match on the first time through as we've already done it (emulates begin...while)
1208
- while match || (reader.has_more_lines? && (match = sibling_pattern.match(reader.peek_line)))
1209
- term, item = parse_list_item(reader, list_block, match, sibling_pattern)
1210
- if previous_pair && !previous_pair[1]
1211
- previous_pair[0] << term
1212
- previous_pair[1] = item
1193
+ while reader.has_more_lines? && sibling_pattern =~ reader.peek_line
1194
+ next_pair = parse_list_item reader, list_block, $~, sibling_pattern
1195
+ if current_pair[1]
1196
+ list_block.items << (current_pair = next_pair)
1213
1197
  else
1214
- list_block.items << (previous_pair = [[term], item])
1198
+ current_pair[0] << next_pair[0][0]
1199
+ current_pair[1] = next_pair[1]
1215
1200
  end
1216
- match = nil
1217
1201
  end
1218
1202
 
1219
1203
  list_block
@@ -1239,12 +1223,12 @@ class Parser
1239
1223
  end
1240
1224
  # might want to move this check to a validate method
1241
1225
  unless num == next_index.to_s
1242
- logger.warn message_with_context %(callout list item index: expected #{next_index}, got #{num}), :source_location => reader.cursor_at_mark
1226
+ logger.warn message_with_context %(callout list item index: expected #{next_index}, got #{num}), source_location: reader.cursor_at_mark
1243
1227
  end
1244
1228
  if (list_item = parse_list_item reader, list_block, match, '<1>')
1245
1229
  list_block.items << list_item
1246
1230
  if (coids = callouts.callout_ids list_block.items.size).empty?
1247
- logger.warn message_with_context %(no callout found for <#{list_block.items.size}>), :source_location => reader.cursor_at_mark
1231
+ logger.warn message_with_context %(no callout found for <#{list_block.items.size}>), source_location: reader.cursor_at_mark
1248
1232
  else
1249
1233
  list_item.attributes['coids'] = coids
1250
1234
  end
@@ -1274,7 +1258,7 @@ class Parser
1274
1258
  # marker pattern.
1275
1259
  # style - The block style assigned to this list (optional, default: nil)
1276
1260
  #
1277
- # Returns the next ListItem or ListItem pair (description list) for the parent list Block.
1261
+ # Returns the next ListItem or [[ListItem], ListItem] pair (description list) for the parent list Block.
1278
1262
  def self.parse_list_item(reader, list_block, match, sibling_trait, style = nil)
1279
1263
  if (list_type = list_block.context) == :dlist
1280
1264
  dlist = true
@@ -1308,9 +1292,7 @@ class Parser
1308
1292
  catalog_inline_anchor $1, $2, list_item, reader
1309
1293
  end
1310
1294
  elsif item_text.start_with?('[ ] ', '[x] ', '[*] ')
1311
- # FIXME next_block wipes out update to options attribute
1312
- #list_block.set_option 'checklist' unless list_block.attributes['checklist-option']
1313
- list_block.attributes['checklist-option'] = ''
1295
+ list_block.set_option 'checklist'
1314
1296
  list_item.attributes['checkbox'] = ''
1315
1297
  list_item.attributes['checked'] = '' unless item_text.start_with? '[ '
1316
1298
  list_item.text = item_text.slice(4, item_text.length)
@@ -1356,12 +1338,12 @@ class Parser
1356
1338
  end
1357
1339
 
1358
1340
  # reader is confined to boundaries of list, which means only blocks will be found (no sections)
1359
- if (block = next_block(list_item_reader, list_item, {}, :text => !has_text))
1341
+ if (block = next_block(list_item_reader, list_item, {}, text: !has_text, list_item: true))
1360
1342
  list_item.blocks << block
1361
1343
  end
1362
1344
 
1363
1345
  while list_item_reader.has_more_lines?
1364
- if (block = next_block(list_item_reader, list_item))
1346
+ if (block = next_block(list_item_reader, list_item, {}, list_item: true))
1365
1347
  list_item.blocks << block
1366
1348
  end
1367
1349
  end
@@ -1369,11 +1351,7 @@ class Parser
1369
1351
  list_item.fold_first(continuation_connects_first_block, content_adjacent)
1370
1352
  end
1371
1353
 
1372
- if dlist
1373
- list_item.text? || list_item.blocks? ? [list_term, list_item] : [list_term]
1374
- else
1375
- list_item
1376
- end
1354
+ dlist ? [[list_term], (list_item.text? || list_item.blocks? ? list_item : nil)] : list_item
1377
1355
  end
1378
1356
 
1379
1357
  # Internal: Collect the lines belonging to the current list item, navigating
@@ -1407,6 +1385,8 @@ class Parser
1407
1385
  # it gets associated with the outermost block
1408
1386
  detached_continuation = nil
1409
1387
 
1388
+ dlist = list_type == :dlist
1389
+
1410
1390
  while reader.has_more_lines?
1411
1391
  this_line = reader.read_line
1412
1392
 
@@ -1443,7 +1423,7 @@ class Parser
1443
1423
  buffer << this_line
1444
1424
  # grab all the lines in the block, leaving the delimiters in place
1445
1425
  # we're being more strict here about the terminator, but I think that's a good thing
1446
- buffer.concat reader.read_lines_until(:terminator => match.terminator, :read_last_line => true, :context => nil)
1426
+ buffer.concat reader.read_lines_until(terminator: match.terminator, read_last_line: true, context: nil)
1447
1427
  continuation = :inactive
1448
1428
  else
1449
1429
  break
@@ -1451,7 +1431,7 @@ class Parser
1451
1431
  # technically BlockAttributeLineRx only breaks if ensuing line is not a list item
1452
1432
  # which really means BlockAttributeLineRx only breaks if it's acting as a block delimiter
1453
1433
  # FIXME to be AsciiDoc compliant, we shouldn't break if style in attribute line is "literal" (i.e., [literal])
1454
- elsif list_type == :dlist && continuation != :active && (BlockAttributeLineRx.match? this_line)
1434
+ elsif dlist && continuation != :active && (BlockAttributeLineRx.match? this_line)
1455
1435
  break
1456
1436
  else
1457
1437
  if continuation == :active && !this_line.empty?
@@ -1461,14 +1441,13 @@ class Parser
1461
1441
  # list item will throw off the exit from it
1462
1442
  if LiteralParagraphRx.match? this_line
1463
1443
  reader.unshift_line this_line
1464
- buffer.concat reader.read_lines_until(
1465
- :preserve_last_line => true,
1466
- :break_on_blank_lines => true,
1467
- :break_on_list_continuation => true) {|line|
1444
+ if dlist
1468
1445
  # we may be in an indented list disguised as a literal paragraph
1469
1446
  # so we need to make sure we don't slurp up a legitimate sibling
1470
- list_type == :dlist && is_sibling_list_item?(line, list_type, sibling_trait)
1471
- }
1447
+ buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true) {|line| is_sibling_list_item? line, list_type, sibling_trait }
1448
+ else
1449
+ buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true)
1450
+ end
1472
1451
  continuation = :inactive
1473
1452
  # let block metadata play out until we find the block
1474
1453
  elsif (BlockTitleRx.match? this_line) || (BlockAttributeLineRx.match? this_line) || (AttributeEntryRx.match? this_line)
@@ -1515,14 +1494,13 @@ class Parser
1515
1494
  # NOTE we have to check for indented list items first
1516
1495
  elsif LiteralParagraphRx.match? this_line
1517
1496
  reader.unshift_line this_line
1518
- buffer.concat reader.read_lines_until(
1519
- :preserve_last_line => true,
1520
- :break_on_blank_lines => true,
1521
- :break_on_list_continuation => true) {|line|
1497
+ if dlist
1522
1498
  # we may be in an indented list disguised as a literal paragraph
1523
1499
  # so we need to make sure we don't slurp up a legitimate sibling
1524
- list_type == :dlist && is_sibling_list_item?(line, list_type, sibling_trait)
1525
- }
1500
+ buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true) {|line| is_sibling_list_item? line, list_type, sibling_trait }
1501
+ else
1502
+ buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true)
1503
+ end
1526
1504
  else
1527
1505
  break
1528
1506
  end
@@ -1593,10 +1571,12 @@ class Parser
1593
1571
  if sect_style
1594
1572
  if book && sect_style == 'abstract'
1595
1573
  sect_name, sect_level = 'chapter', 1
1574
+ elsif (sect_style.start_with? 'sect') && (SectionLevelStyleRx.match? sect_style)
1575
+ sect_name = 'section'
1596
1576
  else
1597
1577
  sect_name, sect_special = sect_style, true
1598
1578
  sect_level = 1 if sect_level == 0
1599
- sect_numbered = sect_style == 'appendix'
1579
+ sect_numbered = sect_name == 'appendix'
1600
1580
  end
1601
1581
  elsif book
1602
1582
  sect_name = sect_level == 0 ? 'part' : (sect_level > 1 ? 'section' : 'chapter')
@@ -1623,9 +1603,9 @@ class Parser
1623
1603
  end
1624
1604
 
1625
1605
  # generate an ID if one was not embedded or specified as anchor above section title
1626
- if (id = section.id ||= ((document.attributes.key? 'sectids') ? (Section.generate_id section.title, document) : nil))
1627
- unless document.register :refs, [id, section, sect_reftext || section.title]
1628
- logger.warn message_with_context %(id assigned to section already in use: #{id}), :source_location => (reader.cursor_at_line reader.lineno - (sect_atx ? 1 : 2))
1606
+ if (id = section.id || (section.id = (document.attributes.key? 'sectids') ? (Section.generate_id section.title, document) : nil))
1607
+ unless document.register :refs, [id, section]
1608
+ logger.warn message_with_context %(id assigned to section already in use: #{id}), source_location: (reader.cursor_at_line reader.lineno - (sect_atx ? 1 : 2))
1629
1609
  end
1630
1610
  end
1631
1611
 
@@ -1698,9 +1678,8 @@ class Parser
1698
1678
  #
1699
1679
  # Returns the [Integer] section level if these lines are an setext section title, otherwise nothing.
1700
1680
  def self.setext_section_title? line1, line2
1701
- if (level = SETEXT_SECTION_LEVELS[line2_ch1 = line2.chr]) &&
1702
- line2_ch1 * (line2_len = line2.length) == line2 && SetextSectionTitleRx.match?(line1) &&
1703
- (line_length(line1) - line2_len).abs < 2
1681
+ if (level = SETEXT_SECTION_LEVELS[line2_ch0 = line2.chr]) && (uniform? line2, line2_ch0, (line2_len = line2.length)) &&
1682
+ (SetextSectionTitleRx.match? line1) && (line1.length - line2_len).abs < 2
1704
1683
  level
1705
1684
  end
1706
1685
  end
@@ -1760,9 +1739,8 @@ class Parser
1760
1739
  sect_title, sect_id, sect_reftext = (sect_title.slice 0, sect_title.length - $&.length), $2, $3
1761
1740
  end unless sect_id
1762
1741
  elsif Compliance.underline_style_section_titles && (line2 = reader.peek_line(true)) &&
1763
- (sect_level = SETEXT_SECTION_LEVELS[line2_ch1 = line2.chr]) &&
1764
- line2_ch1 * (line2_len = line2.length) == line2 && (sect_title = SetextSectionTitleRx =~ line1 && $1) &&
1765
- (line_length(line1) - line2_len).abs < 2
1742
+ (sect_level = SETEXT_SECTION_LEVELS[line2_ch0 = line2.chr]) && (uniform? line2, line2_ch0, (line2_len = line2.length)) &&
1743
+ (sect_title = SetextSectionTitleRx =~ line1 && $1) && (line1.length - line2_len).abs < 2
1766
1744
  atx = false
1767
1745
  if sect_title.end_with?(']]') && InlineSectionAnchorRx =~ sect_title && !$1 # escaped
1768
1746
  sect_title, sect_id, sect_reftext = (sect_title.slice 0, sect_title.length - $&.length), $2, $3
@@ -1775,21 +1753,6 @@ class Parser
1775
1753
  [sect_id, sect_reftext, sect_title, sect_level, atx]
1776
1754
  end
1777
1755
 
1778
- # Public: Calculate the number of unicode characters in the line, excluding the endline
1779
- #
1780
- # line - the String to calculate
1781
- #
1782
- # returns the number of unicode characters in the line
1783
- if FORCE_UNICODE_LINE_LENGTH
1784
- def self.line_length(line)
1785
- line.scan(UnicodeCharScanRx).size
1786
- end
1787
- else
1788
- def self.line_length(line)
1789
- line.length
1790
- end
1791
- end
1792
-
1793
1756
  # Public: Consume and parse the two header lines (line 1 = author info, line 2 = revision info).
1794
1757
  #
1795
1758
  # Returns the Hash of header metadata. If a Document object is supplied, the metadata
@@ -1801,9 +1764,9 @@ class Parser
1801
1764
  # Examples
1802
1765
  #
1803
1766
  # data = ["Author Name <author@example.org>\n", "v1.0, 2012-12-21: Coincide w/ end of world.\n"]
1804
- # parse_header_metadata(Reader.new data, nil, :normalize => true)
1805
- # # => {'author' => 'Author Name', 'firstname' => 'Author', 'lastname' => 'Name', 'email' => 'author@example.org',
1806
- # # 'revnumber' => '1.0', 'revdate' => '2012-12-21', 'revremark' => 'Coincide w/ end of world.'}
1767
+ # parse_header_metadata(Reader.new data, nil, normalize: true)
1768
+ # # => { 'author' => 'Author Name', 'firstname' => 'Author', 'lastname' => 'Name', 'email' => 'author@example.org',
1769
+ # # 'revnumber' => '1.0', 'revdate' => '2012-12-21', 'revremark' => 'Coincide w/ end of world.' }
1807
1770
  def self.parse_header_metadata(reader, document = nil)
1808
1771
  doc_attrs = document && document.attributes
1809
1772
  # NOTE this will discard any comment lines, but not skip blank lines
@@ -1943,7 +1906,7 @@ class Parser
1943
1906
  author_metadata = {}
1944
1907
  author_idx = 0
1945
1908
  keys = ['author', 'authorinitials', 'firstname', 'middlename', 'lastname', 'email']
1946
- author_entries = multiple ? (author_line.split ';').map {|it| it.strip } : Array(author_line)
1909
+ author_entries = multiple ? (author_line.split ';').map {|it| it.strip } : [*author_line]
1947
1910
  author_entries.each do |author_entry|
1948
1911
  next if author_entry.empty?
1949
1912
  author_idx += 1
@@ -2052,7 +2015,7 @@ class Parser
2052
2015
  # * :text indicates the parser is only looking for text content,
2053
2016
  # thus neither a block title or attribute entry should be captured
2054
2017
  #
2055
- # returns true if the line contains metadata, otherwise false
2018
+ # returns true if the line contains metadata, otherwise falsy
2056
2019
  def self.parse_block_metadata_line reader, document, attributes, options = {}
2057
2020
  if (next_line = reader.peek_line) &&
2058
2021
  (options[:text] ? (next_line.start_with? '[', '/') : (normal = next_line.start_with? '[', '.', '/', ':'))
@@ -2069,7 +2032,7 @@ class Parser
2069
2032
  elsif (next_line.end_with? ']') && BlockAttributeListRx =~ next_line
2070
2033
  current_style = attributes[1]
2071
2034
  # extract id, role, and options from first positional attribute and remove, if present
2072
- if (document.parse_attributes $1, [], :sub_input => true, :sub_result => true, :into => attributes)[1]
2035
+ if (document.parse_attributes $1, [], sub_input: true, sub_result: true, into: attributes)[1]
2073
2036
  attributes[1] = (parse_style_attribute attributes, reader) || current_style
2074
2037
  end
2075
2038
  return true
@@ -2084,9 +2047,9 @@ class Parser
2084
2047
  elsif !normal || (next_line.start_with? '/')
2085
2048
  if next_line == '//'
2086
2049
  return true
2087
- elsif normal && '/' * (ll = next_line.length) == next_line
2050
+ elsif normal && (uniform? next_line, '/', (ll = next_line.length))
2088
2051
  unless ll == 3
2089
- reader.read_lines_until :terminator => next_line, :skip_first_line => true, :preserve_last_line => true, :skip_processing => true, :context => :comment
2052
+ reader.read_lines_until terminator: next_line, skip_first_line: true, preserve_last_line: true, skip_processing: true, context: :comment
2090
2053
  return true
2091
2054
  end
2092
2055
  else
@@ -2113,11 +2076,11 @@ class Parser
2113
2076
  end
2114
2077
 
2115
2078
  def self.process_attribute_entry reader, document, attributes = nil, match = nil
2116
- if (match ||= (reader.has_more_lines? ? (AttributeEntryRx.match reader.peek_line) : nil))
2079
+ if match || (match = reader.has_more_lines? ? (AttributeEntryRx.match reader.peek_line) : nil)
2117
2080
  if (value = match[2]).nil_or_empty?
2118
2081
  value = ''
2119
2082
  elsif value.end_with? LINE_CONTINUATION, LINE_CONTINUATION_LEGACY
2120
- con, value = value.slice(-2, 2), (value.slice 0, value.length - 2).rstrip
2083
+ con, value = (value.slice value.length - 2, 2), (value.slice 0, value.length - 2).rstrip
2121
2084
  while reader.advance && !(next_line = reader.peek_line || '').empty?
2122
2085
  next_line = next_line.lstrip
2123
2086
  next_line = (next_line.slice 0, next_line.length - 2).rstrip if (keep_open = next_line.end_with? con)
@@ -2144,15 +2107,19 @@ class Parser
2144
2107
  # TODO move processing of attribute value to utility method
2145
2108
  if name.end_with? '!'
2146
2109
  # a nil value signals the attribute should be deleted (unset)
2147
- name, value = name.chop, nil
2110
+ name = name.chop
2111
+ value = nil
2148
2112
  elsif name.start_with? '!'
2149
2113
  # a nil value signals the attribute should be deleted (unset)
2150
- name, value = (name.slice 1, name.length), nil
2114
+ name = (name.slice 1, name.length)
2115
+ value = nil
2151
2116
  end
2152
2117
 
2153
- name = sanitize_attribute_name name
2154
- # alias numbered attribute to sectnums
2155
- name = 'sectnums' if name == 'numbered'
2118
+ if (name = sanitize_attribute_name name) == 'numbered'
2119
+ name = 'sectnums'
2120
+ elsif name == 'hardbreaks'
2121
+ name = 'hardbreaks-option'
2122
+ end
2156
2123
 
2157
2124
  if doc
2158
2125
  if value
@@ -2268,7 +2235,7 @@ class Parser
2268
2235
  end
2269
2236
 
2270
2237
  if validate && expected != actual
2271
- logger.warn message_with_context %(list item index: expected #{expected}, got #{actual}), :source_location => reader.cursor
2238
+ logger.warn message_with_context %(list item index: expected #{expected}, got #{actual}), source_location: reader.cursor
2272
2239
  end
2273
2240
 
2274
2241
  [marker, style]
@@ -2281,20 +2248,12 @@ class Parser
2281
2248
  # list_type - The context of the list (:olist, :ulist, :colist, :dlist)
2282
2249
  # sibling_trait - The String marker for the list or the Regexp to match a sibling
2283
2250
  #
2284
- # Returns a Boolean indicating whether this line is a sibling list item given
2285
- # the criteria provided
2286
- def self.is_sibling_list_item?(line, list_type, sibling_trait)
2251
+ # Returns a Boolean indicating whether this line is a sibling list item given the criteria provided
2252
+ def self.is_sibling_list_item? line, list_type, sibling_trait
2287
2253
  if ::Regexp === sibling_trait
2288
- matcher = sibling_trait
2254
+ sibling_trait.match? line
2289
2255
  else
2290
- matcher = ListRxMap[list_type]
2291
- expected_marker = sibling_trait
2292
- end
2293
-
2294
- if matcher =~ line
2295
- expected_marker ? expected_marker == resolve_list_marker(list_type, $1) : true
2296
- else
2297
- false
2256
+ ListRxMap[list_type] =~ line && sibling_trait == (resolve_list_marker list_type, $1)
2298
2257
  end
2299
2258
  end
2300
2259
 
@@ -2320,7 +2279,7 @@ class Parser
2320
2279
  skipped = table_reader.skip_blank_lines || 0
2321
2280
  parser_ctx = Table::ParserContext.new table_reader, table, attributes
2322
2281
  format, loop_idx, implicit_header_boundary = parser_ctx.format, -1, nil
2323
- implicit_header = true unless skipped > 0 || (attributes.key? 'header-option') || (attributes.key? 'noheader-option')
2282
+ implicit_header = true unless skipped > 0 || attributes['header-option'] || attributes['noheader-option']
2324
2283
 
2325
2284
  while (line = table_reader.read_line)
2326
2285
  if (beyond_first = (loop_idx += 1) > 0) && line.empty?
@@ -2432,7 +2391,6 @@ class Parser
2432
2391
  if implicit_header
2433
2392
  table.has_header_option = true
2434
2393
  attributes['header-option'] = ''
2435
- attributes['options'] = (attributes.key? 'options') ? %(#{attributes['options']},header) : 'header'
2436
2394
  end
2437
2395
 
2438
2396
  table.partition_header_footer attributes
@@ -2490,9 +2448,7 @@ class Parser
2490
2448
  end
2491
2449
 
2492
2450
  if m[1]
2493
- 1.upto(m[1].to_i) {
2494
- specs << spec.dup
2495
- }
2451
+ 1.upto(m[1].to_i) { specs << spec.dup }
2496
2452
  else
2497
2453
  specs << spec
2498
2454
  end
@@ -2517,7 +2473,7 @@ class Parser
2517
2473
 
2518
2474
  if pos == :start
2519
2475
  if line.include? delimiter
2520
- spec_part, rest = line.split delimiter, 2
2476
+ spec_part, delimiter, rest = line.partition delimiter
2521
2477
  if (m = CellSpecStartRx.match spec_part)
2522
2478
  return [{}, rest] if m[0].empty?
2523
2479
  else
@@ -2587,93 +2543,96 @@ class Parser
2587
2543
  # "role" => "lead", "options" => "fragment", "fragment-option" => '' }
2588
2544
  #
2589
2545
  # Returns the String style parsed from the first positional attribute
2590
- def self.parse_style_attribute(attributes, reader = nil)
2546
+ def self.parse_style_attribute attributes, reader = nil
2591
2547
  # NOTE spaces are not allowed in shorthand, so if we detect one, this ain't no shorthand
2592
2548
  if (raw_style = attributes[1]) && !raw_style.include?(' ') && Compliance.shorthand_property_syntax
2593
- type, collector, parsed = :style, [], {}
2594
- # QUESTION should this be a private method? (though, it's never called if shorthand isn't used)
2595
- save_current = lambda {
2596
- if collector.empty?
2597
- unless type == :style
2598
- if reader
2599
- logger.warn message_with_context %(invalid empty #{type} detected in style attribute), :source_location => reader.cursor_at_prev_line
2600
- else
2601
- logger.warn %(invalid empty #{type} detected in style attribute)
2602
- end
2603
- end
2604
- else
2605
- case type
2606
- when :role, :option
2607
- (parsed[type] ||= []) << collector.join
2608
- when :id
2609
- if parsed.key? :id
2610
- if reader
2611
- logger.warn message_with_context 'multiple ids detected in style attribute', :source_location => reader.cursor_at_prev_line
2612
- else
2613
- logger.warn 'multiple ids detected in style attribute'
2614
- end
2615
- end
2616
- parsed[type] = collector.join
2617
- else
2618
- parsed[type] = collector.join
2619
- end
2620
- collector = []
2621
- end
2622
- }
2549
+ name = nil
2550
+ accum = ''
2551
+ parsed_attrs = {}
2623
2552
 
2624
2553
  raw_style.each_char do |c|
2625
- if c == '.' || c == '#' || c == '%'
2626
- save_current.call
2627
- case c
2628
- when '.'
2629
- type = :role
2630
- when '#'
2631
- type = :id
2632
- when '%'
2633
- type = :option
2634
- end
2554
+ case c
2555
+ when '.'
2556
+ yield_buffered_attribute parsed_attrs, name, accum, reader
2557
+ accum = ''
2558
+ name = :role
2559
+ when '#'
2560
+ yield_buffered_attribute parsed_attrs, name, accum, reader
2561
+ accum = ''
2562
+ name = :id
2563
+ when '%'
2564
+ yield_buffered_attribute parsed_attrs, name, accum, reader
2565
+ accum = ''
2566
+ name = :option
2635
2567
  else
2636
- collector << c
2568
+ accum = accum + c
2637
2569
  end
2638
2570
  end
2639
2571
 
2640
2572
  # small optimization if no shorthand is found
2641
- if type == :style
2642
- attributes['style'] = raw_style
2643
- else
2644
- save_current.call
2573
+ if name
2574
+ yield_buffered_attribute parsed_attrs, name, accum, reader
2645
2575
 
2646
- parsed_style = attributes['style'] = parsed[:style] if parsed.key? :style
2576
+ if (parsed_style = parsed_attrs[:style])
2577
+ attributes['style'] = parsed_style
2578
+ end
2647
2579
 
2648
- attributes['id'] = parsed[:id] if parsed.key? :id
2580
+ attributes['id'] = parsed_attrs[:id] if parsed_attrs.key? :id
2649
2581
 
2650
- if parsed.key? :role
2651
- attributes['role'] = (existing_role = attributes['role']).nil_or_empty? ? (parsed[:role].join ' ') : %(#{existing_role} #{parsed[:role].join ' '})
2582
+ if parsed_attrs.key? :role
2583
+ attributes['role'] = (existing_role = attributes['role']).nil_or_empty? ? (parsed_attrs[:role].join ' ') : %(#{existing_role} #{parsed_attrs[:role].join ' '})
2652
2584
  end
2653
2585
 
2654
- if parsed.key? :option
2655
- (opts = parsed[:option]).each {|opt| attributes[%(#{opt}-option)] = '' }
2656
- attributes['options'] = (existing_opts = attributes['options']).nil_or_empty? ? (opts.join ',') : %(#{existing_opts},#{opts.join ','})
2586
+ if parsed_attrs.key? :option
2587
+ (opts = parsed_attrs[:option]).each {|opt| attributes[%(#{opt}-option)] = '' }
2657
2588
  end
2658
2589
 
2659
2590
  parsed_style
2591
+ else
2592
+ attributes['style'] = raw_style
2660
2593
  end
2661
2594
  else
2662
2595
  attributes['style'] = raw_style
2663
2596
  end
2664
2597
  end
2665
2598
 
2666
- # Remove the block indentation (the leading whitespace equal to the amount of
2667
- # leading whitespace of the least indented line), then replace tabs with
2668
- # spaces (using proper tab expansion logic) and, finally, indent the lines by
2669
- # the amount specified.
2599
+ # Internal: Save the collected attribute (:id, :option, :role, or nil for :style) in the attribute Hash.
2600
+ def self.yield_buffered_attribute attrs, name, value, reader
2601
+ if name
2602
+ if value.empty?
2603
+ if reader
2604
+ logger.warn message_with_context %(invalid empty #{name} detected in style attribute), source_location: reader.cursor_at_prev_line
2605
+ else
2606
+ logger.warn %(invalid empty #{name} detected in style attribute)
2607
+ end
2608
+ elsif name == :id
2609
+ if attrs.key? :id
2610
+ if reader
2611
+ logger.warn message_with_context 'multiple ids detected in style attribute', source_location: reader.cursor_at_prev_line
2612
+ else
2613
+ logger.warn 'multiple ids detected in style attribute'
2614
+ end
2615
+ end
2616
+ attrs[name] = value
2617
+ else
2618
+ (attrs[name] ||= []) << value
2619
+ end
2620
+ else
2621
+ attrs[:style] = value unless value.empty?
2622
+ end
2623
+ nil
2624
+ end
2625
+
2626
+ # Remove the block indentation (the amount of whitespace of the least indented line), replace tabs with spaces (using
2627
+ # proper tab expansion logic) and, finally, indent the lines by the margin width. Modifies the input Array directly.
2670
2628
  #
2671
- # This method preserves the relative indentation of the lines.
2629
+ # This method preserves the significant indentation (that exceeding the block indent) on each line.
2672
2630
  #
2673
- # lines - the Array of String lines to process (no trailing endlines)
2674
- # indent - the integer number of spaces to add to the beginning
2675
- # of each line; if this value is nil, the existing
2676
- # space is preserved (optional, default: 0)
2631
+ # lines - The Array of String lines to process (no trailing newlines)
2632
+ # indent_size - The Integer number of spaces to readd to the start of non-empty lines after removing the indentation.
2633
+ # If this value is < 0, the existing indentation is preserved (optional, default: 0)
2634
+ # tab_size - the Integer number of spaces to use in place of a tab. A value of <= 0 disables the replacement
2635
+ # (optional, default: 0)
2677
2636
  #
2678
2637
  # Examples
2679
2638
  #
@@ -2683,89 +2642,97 @@ class Parser
2683
2642
  # end
2684
2643
  # EOS
2685
2644
  #
2686
- # source.split "\n"
2645
+ # source.split ?\n
2687
2646
  # # => [" def names", " @names.split", " end"]
2688
2647
  #
2689
- # puts Parser.adjust_indentation!(source.split "\n").join "\n"
2648
+ # puts (Parser.adjust_indentation! source.split ?\n).join ?\n
2690
2649
  # # => def names
2691
2650
  # # => @names.split
2692
2651
  # # => end
2693
2652
  #
2694
2653
  # returns Nothing
2695
- #--
2696
- # QUESTION should indent be called margin?
2697
- def self.adjust_indentation! lines, indent = 0, tab_size = 0
2654
+ def self.adjust_indentation! lines, indent_size = 0, tab_size = 0
2698
2655
  return if lines.empty?
2699
2656
 
2700
- # expand tabs if a tab is detected unless tab_size is nil
2701
- if (tab_size = tab_size.to_i) > 0 && (lines.join.include? TAB)
2702
- #if (tab_size = tab_size.to_i) > 0 && (lines.index {|line| line.include? TAB })
2657
+ # expand tabs if a tab character is detected and tab_size > 0
2658
+ if tab_size > 0 && lines.any? {|line| line.include? TAB }
2703
2659
  full_tab_space = ' ' * tab_size
2704
2660
  lines.map! do |line|
2705
- next line if line.empty?
2706
-
2707
- # NOTE Opal has to patch this use of sub!
2708
- line.sub!(TabIndentRx) { full_tab_space * $&.length } if line.start_with? TAB
2709
-
2710
- if line.include? TAB
2661
+ if line.empty?
2662
+ line
2663
+ elsif (tab_idx = line.index TAB)
2664
+ if tab_idx == 0
2665
+ leading_tabs = 0
2666
+ line.each_byte do |b|
2667
+ break unless b == 9
2668
+ leading_tabs += 1
2669
+ end
2670
+ line = %(#{full_tab_space * leading_tabs}#{line.slice leading_tabs, line.length})
2671
+ next line unless line.include? TAB
2672
+ end
2711
2673
  # keeps track of how many spaces were added to adjust offset in match data
2712
2674
  spaces_added = 0
2713
- # NOTE Opal has to patch this use of gsub!
2714
- line.gsub!(TabRx) {
2715
- # calculate how many spaces this tab represents, then replace tab with spaces
2716
- if (offset = ($~.begin 0) + spaces_added) % tab_size == 0
2717
- spaces_added += (tab_size - 1)
2718
- full_tab_space
2719
- else
2720
- unless (spaces = tab_size - offset % tab_size) == 1
2721
- spaces_added += (spaces - 1)
2675
+ idx = 0
2676
+ result = ''
2677
+ line.each_char do |c|
2678
+ if c == TAB
2679
+ # calculate how many spaces this tab represents, then replace tab with spaces
2680
+ if (offset = idx + spaces_added) % tab_size == 0
2681
+ spaces_added += (tab_size - 1)
2682
+ result = result + full_tab_space
2683
+ else
2684
+ unless (spaces = tab_size - offset % tab_size) == 1
2685
+ spaces_added += (spaces - 1)
2686
+ end
2687
+ result = result + (' ' * spaces)
2722
2688
  end
2723
- ' ' * spaces
2689
+ else
2690
+ result = result + c
2724
2691
  end
2725
- }
2692
+ idx += 1
2693
+ end
2694
+ result
2726
2695
  else
2727
2696
  line
2728
2697
  end
2729
2698
  end
2730
2699
  end
2731
2700
 
2732
- # skip adjustment of gutter if indent is -1
2733
- return unless indent && (indent = indent.to_i) > -1
2701
+ # skip block indent adjustment if indent_size is < 0
2702
+ return if indent_size < 0
2734
2703
 
2735
- # determine width of gutter
2736
- gutter_width = nil
2704
+ # determine block indent (assumes no whitespace-only lines are present)
2705
+ block_indent = nil
2737
2706
  lines.each do |line|
2738
2707
  next if line.empty?
2739
- # NOTE this logic assumes no whitespace-only lines
2740
2708
  if (line_indent = line.length - line.lstrip.length) == 0
2741
- gutter_width = nil
2709
+ block_indent = nil
2742
2710
  break
2743
- else
2744
- unless gutter_width && line_indent > gutter_width
2745
- gutter_width = line_indent
2746
- end
2747
2711
  end
2712
+ block_indent = line_indent unless block_indent && block_indent < line_indent
2748
2713
  end
2749
2714
 
2750
- # remove gutter then apply new indent if specified
2751
- # NOTE gutter_width is > 0 if not nil
2752
- if indent == 0
2753
- if gutter_width
2754
- lines.map! {|line| line.empty? ? line : (line.slice gutter_width, line.length) }
2755
- end
2715
+ # remove block indent then apply indent_size if specified
2716
+ # NOTE block_indent is > 0 if not nil
2717
+ if indent_size == 0
2718
+ lines.map! {|line| line.empty? ? line : (line.slice block_indent, line.length) } if block_indent
2756
2719
  else
2757
- padding = ' ' * indent
2758
- if gutter_width
2759
- lines.map! {|line| line.empty? ? line : padding + (line.slice gutter_width, line.length) }
2720
+ new_block_indent = ' ' * indent_size
2721
+ if block_indent
2722
+ lines.map! {|line| line.empty? ? line : new_block_indent + (line.slice block_indent, line.length) }
2760
2723
  else
2761
- lines.map! {|line| line.empty? ? line : padding + line }
2724
+ lines.map! {|line| line.empty? ? line : new_block_indent + line }
2762
2725
  end
2763
2726
  end
2764
2727
 
2765
2728
  nil
2766
2729
  end
2767
2730
 
2768
- # Public: Convert a string to a legal attribute name.
2731
+ def self.uniform? str, chr, len
2732
+ (str.count chr) == len
2733
+ end
2734
+
2735
+ # Internal: Convert a string to a legal attribute name.
2769
2736
  #
2770
2737
  # name - the String name of the attribute
2771
2738
  #