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