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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +162 -17
- data/LICENSE +1 -1
- data/README-de.adoc +12 -13
- data/README-fr.adoc +11 -12
- data/README-jp.adoc +11 -12
- data/README-zh_CN.adoc +12 -13
- data/README.adoc +6 -7
- data/asciidoctor.gemspec +19 -24
- data/bin/asciidoctor +5 -4
- data/data/reference/syntax.adoc +283 -0
- data/data/stylesheets/asciidoctor-default.css +56 -52
- data/data/stylesheets/coderay-asciidoctor.css +7 -9
- data/lib/asciidoctor.rb +171 -232
- data/lib/asciidoctor/abstract_block.rb +96 -105
- data/lib/asciidoctor/abstract_node.rb +118 -139
- data/lib/asciidoctor/attribute_list.rb +10 -14
- data/lib/asciidoctor/block.rb +20 -19
- data/lib/asciidoctor/callouts.rb +4 -2
- data/lib/asciidoctor/cli.rb +3 -2
- data/lib/asciidoctor/cli/invoker.rb +14 -21
- data/lib/asciidoctor/cli/options.rb +64 -54
- data/lib/asciidoctor/converter.rb +357 -185
- data/lib/asciidoctor/converter/composite.rb +40 -48
- data/lib/asciidoctor/converter/docbook5.rb +604 -640
- data/lib/asciidoctor/converter/html5.rb +949 -963
- data/lib/asciidoctor/converter/manpage.rb +569 -548
- data/lib/asciidoctor/converter/template.rb +231 -272
- data/lib/asciidoctor/core_ext.rb +5 -18
- data/lib/asciidoctor/core_ext/float/truncate.rb +19 -0
- data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
- data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
- data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
- data/lib/asciidoctor/document.rb +399 -377
- data/lib/asciidoctor/extensions.rb +72 -140
- data/lib/asciidoctor/helpers.rb +122 -83
- data/lib/asciidoctor/inline.rb +5 -1
- data/lib/asciidoctor/list.rb +13 -11
- data/lib/asciidoctor/logging.rb +17 -16
- data/lib/asciidoctor/parser.rb +390 -423
- data/lib/asciidoctor/path_resolver.rb +10 -5
- data/lib/asciidoctor/reader.rb +286 -263
- data/lib/asciidoctor/rouge_ext.rb +39 -0
- data/lib/asciidoctor/section.rb +9 -8
- data/lib/asciidoctor/stylesheets.rb +19 -37
- data/lib/asciidoctor/substitutors.rb +364 -509
- data/lib/asciidoctor/syntax_highlighter.rb +238 -0
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +87 -0
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +26 -0
- data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
- data/lib/asciidoctor/syntax_highlighter/prettify.rb +27 -0
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +149 -0
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +129 -0
- data/lib/asciidoctor/table.rb +73 -66
- data/lib/asciidoctor/timings.rb +4 -2
- data/lib/asciidoctor/version.rb +2 -1
- data/lib/asciidoctor/writer.rb +30 -0
- data/man/asciidoctor.1 +19 -15
- data/man/asciidoctor.adoc +14 -12
- metadata +69 -216
- data/CONTRIBUTING.adoc +0 -185
- data/Gemfile +0 -60
- data/Rakefile +0 -129
- data/bin/asciidoctor-safe +0 -15
- data/features/open_block.feature +0 -92
- data/features/pass_block.feature +0 -66
- data/features/step_definitions.rb +0 -49
- data/features/text_formatting.feature +0 -57
- data/features/xref.feature +0 -1039
- data/lib/asciidoctor/converter/base.rb +0 -59
- data/lib/asciidoctor/converter/docbook45.rb +0 -93
- data/lib/asciidoctor/converter/factory.rb +0 -226
- data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
- data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
- data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
- data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
- data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
- data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
- data/test/api_test.rb +0 -1240
- data/test/attribute_list_test.rb +0 -242
- data/test/attributes_test.rb +0 -1623
- data/test/blocks_test.rb +0 -3870
- data/test/converter_test.rb +0 -470
- data/test/document_test.rb +0 -1853
- data/test/extensions_test.rb +0 -1560
- data/test/fixtures/asciidoc_index.txt +0 -521
- data/test/fixtures/basic-docinfo-footer.html +0 -6
- data/test/fixtures/basic-docinfo-footer.xml +0 -8
- data/test/fixtures/basic-docinfo.html +0 -1
- data/test/fixtures/basic-docinfo.xml +0 -4
- data/test/fixtures/basic.asciidoc +0 -5
- data/test/fixtures/chapter-a.adoc +0 -3
- data/test/fixtures/child-include.adoc +0 -5
- data/test/fixtures/circle.svg +0 -9
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
- data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
- data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
- data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
- data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
- data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
- data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
- data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
- data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
- data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
- data/test/fixtures/docinfo-footer.html +0 -1
- data/test/fixtures/docinfo-footer.xml +0 -9
- data/test/fixtures/docinfo.html +0 -1
- data/test/fixtures/docinfo.xml +0 -3
- data/test/fixtures/doctime-localtime.adoc +0 -2
- data/test/fixtures/dot.gif +0 -0
- data/test/fixtures/encoding.asciidoc +0 -13
- data/test/fixtures/file-with-missing-include.adoc +0 -1
- data/test/fixtures/grandchild-include.adoc +0 -3
- data/test/fixtures/hello-asciidoctor.pdf +0 -69
- data/test/fixtures/include-file.asciidoc +0 -24
- data/test/fixtures/include-file.jsx +0 -8
- data/test/fixtures/include-file.ml +0 -3
- data/test/fixtures/include-file.xml +0 -5
- data/test/fixtures/lists.adoc +0 -96
- data/test/fixtures/master.adoc +0 -5
- data/test/fixtures/mismatched-end-tag.adoc +0 -7
- data/test/fixtures/other-chapters.adoc +0 -11
- data/test/fixtures/outer-include.adoc +0 -5
- data/test/fixtures/parent-include-restricted.adoc +0 -5
- data/test/fixtures/parent-include.adoc +0 -5
- data/test/fixtures/sample.asciidoc +0 -30
- data/test/fixtures/section-a.adoc +0 -4
- data/test/fixtures/stylesheets/custom.css +0 -3
- data/test/fixtures/subdir/index.adoc +0 -3
- data/test/fixtures/subdir/inner-include.adoc +0 -3
- data/test/fixtures/subdir/middle-include.adoc +0 -5
- data/test/fixtures/subs-docinfo.html +0 -2
- data/test/fixtures/subs.adoc +0 -6
- data/test/fixtures/tagged-class-enclosed.rb +0 -25
- data/test/fixtures/tagged-class.rb +0 -23
- data/test/fixtures/tip.gif +0 -0
- data/test/fixtures/unclosed-tag.adoc +0 -3
- data/test/fixtures/unexpected-end-tag.adoc +0 -4
- data/test/invoker_test.rb +0 -745
- data/test/links_test.rb +0 -855
- data/test/lists_test.rb +0 -5151
- data/test/logger_test.rb +0 -211
- data/test/manpage_test.rb +0 -660
- data/test/options_test.rb +0 -262
- data/test/paragraphs_test.rb +0 -562
- data/test/parser_test.rb +0 -742
- data/test/paths_test.rb +0 -395
- data/test/preamble_test.rb +0 -173
- data/test/reader_test.rb +0 -2161
- data/test/sections_test.rb +0 -3575
- data/test/substitutions_test.rb +0 -2066
- data/test/tables_test.rb +0 -2036
- data/test/test_helper.rb +0 -447
- data/test/text_test.rb +0 -309
data/lib/asciidoctor/inline.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
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
|
data/lib/asciidoctor/list.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
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
|
-
#
|
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])
|
116
|
-
|
117
|
-
(
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
data/lib/asciidoctor/logging.rb
CHANGED
@@ -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}
|
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
|
-
|
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 << { :
|
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=
|
91
|
-
@logger =
|
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
|
-
|
98
|
-
|
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
|
-
|
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
|
-
({ :
|
120
|
+
({ text: text }.merge context).extend Logger::AutoFormattingMessage
|
120
121
|
end
|
121
122
|
end
|
122
123
|
end
|
data/lib/asciidoctor/parser.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
module Asciidoctor
|
3
|
-
#
|
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
|
-
#
|
31
|
-
|
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 =
|
36
|
+
StartOfBlockProc = proc {|l| ((l.start_with? '[') && (BlockAttributeLineRx.match? l)) || (is_delimited_block? l) }
|
37
37
|
|
38
|
-
StartOfListProc =
|
38
|
+
StartOfListProc = proc {|l| AnyListRx.match? l }
|
39
39
|
|
40
|
-
StartOfBlockOrListProc =
|
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
|
-
#
|
72
|
+
# Hide the default constructor to make sure this class doesn't get instantiated.
|
74
73
|
#
|
75
|
-
# Raises
|
76
|
-
|
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
|
-
|
131
|
+
|
135
132
|
unless (val = doc_attrs['doctitle']).nil_or_empty?
|
136
|
-
document.title =
|
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, _,
|
143
|
-
|
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
|
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 (
|
157
|
-
doc_attrs['
|
158
|
+
if (role = block_attrs['role'])
|
159
|
+
doc_attrs['role'] = role
|
158
160
|
end
|
159
|
-
if (
|
160
|
-
doc_attrs['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
|
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', :
|
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 :
|
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, :
|
240
|
-
doc_attrs['manname'] =
|
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, :
|
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.
|
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 =
|
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}), :
|
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), :
|
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', :
|
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, :
|
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, :
|
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', :
|
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, :
|
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.)', :
|
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 =
|
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
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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?
|
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, :
|
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) &&
|
552
|
-
(this_line
|
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], :
|
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, :
|
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, :
|
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, :
|
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, :
|
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, :
|
603
|
-
block.parse_attributes $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
|
607
|
-
(extension = extensions.registered_for_block_macro? $1)
|
608
|
-
|
609
|
-
if
|
610
|
-
(
|
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
|
-
|
627
|
-
|
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'] =
|
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
|
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 (
|
666
|
+
elsif ((this_line.include? '::') || (this_line.include? ';;')) && DescriptionListRx =~ this_line
|
654
667
|
reader.unshift_line this_line
|
655
|
-
block = parse_description_list(reader,
|
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, :
|
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.
|
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, (
|
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, :
|
706
|
-
|
707
|
-
|
708
|
-
|
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 &&
|
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, :
|
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, :
|
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
|
-
|
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, :
|
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, :
|
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']
|
806
|
+
attributes['language'] = doc_attrs['source-language']
|
790
807
|
end unless attributes.key? 'language'
|
791
|
-
if
|
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)
|
802
|
-
language =
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
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 =
|
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
|
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(:
|
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
|
-
|
857
|
-
|
858
|
-
AttributeList.rekey(attributes, [nil]
|
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, :
|
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
|
886
|
-
unless document.register :refs, [block_id, block
|
887
|
-
logger.warn message_with_context %(id assigned to block already in use: #{block_id}), :
|
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
|
932
|
+
# Public: Determines whether this line is the start of a known delimited block.
|
919
933
|
#
|
920
|
-
#
|
921
|
-
|
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
|
924
|
-
#
|
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
|
-
|
942
|
+
tip_len = 2
|
928
943
|
else
|
929
|
-
#
|
930
|
-
if line_len
|
944
|
+
# all other delimited blocks, including fenced code
|
945
|
+
if line_len < 5
|
931
946
|
tip = line
|
932
|
-
|
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
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
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
|
-
|
948
|
-
|
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
|
-
|
965
|
+
return
|
965
966
|
end
|
966
|
-
elsif
|
967
|
-
|
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
|
-
|
985
|
-
|
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 :
|
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, :
|
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 :
|
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(:
|
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,
|
1027
|
-
elsif
|
1028
|
-
adjust_indentation! lines,
|
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).
|
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, :
|
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
|
1092
|
-
list_block = List.new
|
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? &&
|
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
|
1116
|
-
|
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
|
-
|
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
|
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, :
|
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}), :
|
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
|
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, :
|
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 = (
|
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}), :
|
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,
|
1188
|
-
logger.warn message_with_context %(id assigned to bibliography anchor already in use: #{id}), :
|
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
|
1201
|
-
list_block = List.new
|
1202
|
-
|
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
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
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
|
-
|
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}), :
|
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}>), :
|
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
|
-
|
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, {}, :
|
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
|
-
|
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(:
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
1627
|
-
unless document.register :refs, [id, section
|
1628
|
-
logger.warn message_with_context %(id assigned to section already in use: #{id}), :
|
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[
|
1702
|
-
|
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[
|
1764
|
-
|
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, :
|
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 } :
|
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
|
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, [], :
|
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 && '/'
|
2050
|
+
elsif normal && (uniform? next_line, '/', (ll = next_line.length))
|
2088
2051
|
unless ll == 3
|
2089
|
-
reader.read_lines_until :
|
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
|
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
|
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
|
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
|
2114
|
+
name = (name.slice 1, name.length)
|
2115
|
+
value = nil
|
2151
2116
|
end
|
2152
2117
|
|
2153
|
-
name = sanitize_attribute_name name
|
2154
|
-
|
2155
|
-
|
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}), :
|
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
|
-
|
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
|
-
|
2254
|
+
sibling_trait.match? line
|
2289
2255
|
else
|
2290
|
-
|
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 ||
|
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.
|
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
|
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
|
-
|
2594
|
-
|
2595
|
-
|
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
|
-
|
2626
|
-
|
2627
|
-
|
2628
|
-
|
2629
|
-
|
2630
|
-
|
2631
|
-
|
2632
|
-
|
2633
|
-
|
2634
|
-
|
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
|
-
|
2568
|
+
accum = accum + c
|
2637
2569
|
end
|
2638
2570
|
end
|
2639
2571
|
|
2640
2572
|
# small optimization if no shorthand is found
|
2641
|
-
if
|
2642
|
-
|
2643
|
-
else
|
2644
|
-
save_current.call
|
2573
|
+
if name
|
2574
|
+
yield_buffered_attribute parsed_attrs, name, accum, reader
|
2645
2575
|
|
2646
|
-
parsed_style =
|
2576
|
+
if (parsed_style = parsed_attrs[:style])
|
2577
|
+
attributes['style'] = parsed_style
|
2578
|
+
end
|
2647
2579
|
|
2648
|
-
attributes['id'] =
|
2580
|
+
attributes['id'] = parsed_attrs[:id] if parsed_attrs.key? :id
|
2649
2581
|
|
2650
|
-
if
|
2651
|
-
attributes['role'] = (existing_role = attributes['role']).nil_or_empty? ? (
|
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
|
2655
|
-
(opts =
|
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
|
-
#
|
2667
|
-
|
2668
|
-
|
2669
|
-
|
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
|
2629
|
+
# This method preserves the significant indentation (that exceeding the block indent) on each line.
|
2672
2630
|
#
|
2673
|
-
# lines
|
2674
|
-
#
|
2675
|
-
#
|
2676
|
-
#
|
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
|
2645
|
+
# source.split ?\n
|
2687
2646
|
# # => [" def names", " @names.split", " end"]
|
2688
2647
|
#
|
2689
|
-
# puts Parser.adjust_indentation!
|
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
|
2701
|
-
if
|
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
|
-
|
2706
|
-
|
2707
|
-
|
2708
|
-
|
2709
|
-
|
2710
|
-
|
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
|
-
|
2714
|
-
|
2715
|
-
|
2716
|
-
if
|
2717
|
-
|
2718
|
-
|
2719
|
-
|
2720
|
-
|
2721
|
-
|
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
|
-
|
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
|
2733
|
-
return
|
2701
|
+
# skip block indent adjustment if indent_size is < 0
|
2702
|
+
return if indent_size < 0
|
2734
2703
|
|
2735
|
-
# determine
|
2736
|
-
|
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
|
-
|
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
|
2751
|
-
# NOTE
|
2752
|
-
if
|
2753
|
-
if
|
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
|
-
|
2758
|
-
if
|
2759
|
-
lines.map! {|line| line.empty? ? line :
|
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 :
|
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
|
-
|
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
|
#
|