asciidoctor 1.5.8 → 2.0.0.rc.1

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 (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
  #