hamdown_core 0.5.0
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 +7 -0
- data/.gitignore +513 -0
- data/.rspec +2 -0
- data/.rubocop.yml +59 -0
- data/.rubocop_todo.yml +261 -0
- data/.ruby-version +1 -0
- data/.travis.yml +0 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +109 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +28 -0
- data/bin/console +15 -0
- data/bin/hamdown_compiler +13 -0
- data/bin/hamdown_parser +10 -0
- data/bin/hamdown_transformer +12 -0
- data/bin/setup +7 -0
- data/exe/hamdown_core +7 -0
- data/hamdown_core.gemspec +33 -0
- data/input.hd +30 -0
- data/lib/hamdown_core/ast.rb +179 -0
- data/lib/hamdown_core/cli.rb +23 -0
- data/lib/hamdown_core/compiler.rb +95 -0
- data/lib/hamdown_core/element_parser.rb +243 -0
- data/lib/hamdown_core/engine.rb +14 -0
- data/lib/hamdown_core/error.rb +11 -0
- data/lib/hamdown_core/filter_parser.rb +57 -0
- data/lib/hamdown_core/indent_tracker.rb +117 -0
- data/lib/hamdown_core/line_parser.rb +68 -0
- data/lib/hamdown_core/parser.rb +298 -0
- data/lib/hamdown_core/ruby_multiline.rb +24 -0
- data/lib/hamdown_core/script_parser.rb +106 -0
- data/lib/hamdown_core/transformer.rb +52 -0
- data/lib/hamdown_core/utils.rb +18 -0
- data/lib/hamdown_core/version.rb +4 -0
- data/lib/hamdown_core.rb +8 -0
- metadata +222 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamdownCore
|
4
|
+
module Ast
|
5
|
+
module HasChildren
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
self.children ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(ast)
|
12
|
+
self.children << ast
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
super.merge(children: children.map(&:to_h))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Root = Struct.new(:children) do
|
21
|
+
include HasChildren
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
super.merge(type: 'root')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Doctype = Struct.new(:doctype, :filename, :lineno) do
|
29
|
+
def to_h
|
30
|
+
super.merge(type: 'doctype')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Element = Struct.new(
|
35
|
+
:children,
|
36
|
+
:tag_name,
|
37
|
+
:static_class,
|
38
|
+
:static_id,
|
39
|
+
:old_attributes,
|
40
|
+
:new_attributes,
|
41
|
+
:oneline_child,
|
42
|
+
:self_closing,
|
43
|
+
:nuke_inner_whitespace,
|
44
|
+
:nuke_outer_whitespace,
|
45
|
+
:object_ref,
|
46
|
+
:filename,
|
47
|
+
:lineno,
|
48
|
+
) do
|
49
|
+
include HasChildren
|
50
|
+
|
51
|
+
def initialize(*)
|
52
|
+
super
|
53
|
+
self.static_class ||= ''
|
54
|
+
self.static_id ||= ''
|
55
|
+
self.self_closing ||= false
|
56
|
+
self.nuke_inner_whitespace ||= false
|
57
|
+
self.nuke_outer_whitespace ||= false
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_h
|
61
|
+
super.merge(
|
62
|
+
type: 'element',
|
63
|
+
oneline_child: oneline_child && oneline_child.to_h,
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
# XXX: For compatibility
|
68
|
+
def attributes
|
69
|
+
attrs = old_attributes || ''
|
70
|
+
if new_attributes
|
71
|
+
if attrs.empty?
|
72
|
+
attrs = new_attributes
|
73
|
+
else
|
74
|
+
attrs += ", #{new_attributes}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
attrs
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
Script = Struct.new(
|
82
|
+
:children,
|
83
|
+
:script,
|
84
|
+
:keyword,
|
85
|
+
:escape_html,
|
86
|
+
:preserve,
|
87
|
+
:filename,
|
88
|
+
:lineno,
|
89
|
+
) do
|
90
|
+
include HasChildren
|
91
|
+
|
92
|
+
def initialize(*)
|
93
|
+
super
|
94
|
+
if escape_html.nil?
|
95
|
+
self.escape_html = true
|
96
|
+
end
|
97
|
+
if preserve.nil?
|
98
|
+
self.preserve = false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_h
|
103
|
+
super.merge(type: 'script')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
SilentScript = Struct.new(:children, :script, :keyword, :filename, :lineno) do
|
108
|
+
include HasChildren
|
109
|
+
|
110
|
+
def to_h
|
111
|
+
super.merge(type: 'silent_script')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
HtmlComment = Struct.new(:children, :comment, :conditional, :filename, :lineno) do
|
116
|
+
include HasChildren
|
117
|
+
|
118
|
+
def initialize(*)
|
119
|
+
super
|
120
|
+
self.comment ||= ''
|
121
|
+
self.conditional ||= ''
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_h
|
125
|
+
super.merge(type: 'html_comment')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
HamlComment = Struct.new(:children, :filename, :lineno) do
|
130
|
+
include HasChildren
|
131
|
+
|
132
|
+
def to_h
|
133
|
+
super.merge(type: 'haml_comment')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
Text = Struct.new(:text, :escape_html, :filename, :lineno) do
|
138
|
+
def initialize(*)
|
139
|
+
super
|
140
|
+
if escape_html.nil?
|
141
|
+
self.escape_html = true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_h
|
146
|
+
super.merge(type: 'text')
|
147
|
+
end
|
148
|
+
|
149
|
+
def markdownable?
|
150
|
+
true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class MdHeader < Text; end
|
155
|
+
class MdList < Text; end
|
156
|
+
class MdQuote < Text; end
|
157
|
+
class MdImageTitle < Text; end
|
158
|
+
class MdImage < Text; end
|
159
|
+
class MdLinkTitle < Text; end
|
160
|
+
class MdLink < Text; end
|
161
|
+
|
162
|
+
Filter = Struct.new(:name, :texts, :filename, :lineno) do
|
163
|
+
def initialize(*)
|
164
|
+
super
|
165
|
+
self.texts ||= []
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_h
|
169
|
+
super.merge(type: 'filter')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
Empty = Struct.new(:filename, :lineno) do
|
174
|
+
def to_h
|
175
|
+
super.merge(type: 'empty')
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'optparse'
|
3
|
+
require_relative 'version'
|
4
|
+
|
5
|
+
module HamdownCore
|
6
|
+
module Cli
|
7
|
+
def self.call(argv)
|
8
|
+
file_name = OptionParser.new.tap do |parser|
|
9
|
+
parser.version = VERSION
|
10
|
+
end.parse!(argv).first
|
11
|
+
|
12
|
+
if file_name.nil? || file_name.size == 0
|
13
|
+
puts 'Error: No file.'
|
14
|
+
puts 'Use it like: "exe/hamdown_core path_to/file.hd > output.html"'
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
content = File.open(file_name, 'r').read
|
19
|
+
output = Engine.call(content)
|
20
|
+
puts output
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamdownCore
|
4
|
+
module Compiler
|
5
|
+
class << self
|
6
|
+
def call(ast_root)
|
7
|
+
strings = []
|
8
|
+
strings = render_strings(ast_root.children, 0)
|
9
|
+
strings.join("\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# TODO: refactoring
|
15
|
+
def render_strings(ast_nodes, space_deep = 0)
|
16
|
+
strings = []
|
17
|
+
ast_nodes.each do |node|
|
18
|
+
case node
|
19
|
+
when HamdownCore::Ast::Filter
|
20
|
+
str = (' ' * space_deep)
|
21
|
+
str += ":#{node.name}"
|
22
|
+
strings << str
|
23
|
+
node.texts.each do |row|
|
24
|
+
row = (' ' * (space_deep + 2)) + row
|
25
|
+
strings << row
|
26
|
+
end
|
27
|
+
when HamdownCore::Ast::SilentScript
|
28
|
+
str = (' ' * space_deep)
|
29
|
+
str += "- #{node.script}"
|
30
|
+
strings << str
|
31
|
+
when HamdownCore::Ast::Script
|
32
|
+
str = (' ' * space_deep)
|
33
|
+
if node.escape_html == true
|
34
|
+
str += "= #{node.script}"
|
35
|
+
else
|
36
|
+
str += "!= #{node.script}"
|
37
|
+
end
|
38
|
+
strings << str
|
39
|
+
when HamdownCore::Ast::Text
|
40
|
+
strings << (' ' * space_deep + "#{node.text}")
|
41
|
+
when HamdownCore::Ast::HtmlComment
|
42
|
+
strings << (' ' * space_deep + "/ #{node.comment}")
|
43
|
+
when HamdownCore::Ast::Empty
|
44
|
+
# NODE: or add spaces?
|
45
|
+
strings << ''
|
46
|
+
when HamdownCore::Ast::Element
|
47
|
+
str = (' ' * space_deep)
|
48
|
+
|
49
|
+
str += "%#{node.tag_name}"
|
50
|
+
|
51
|
+
if node.static_id.size > 0
|
52
|
+
str += "##{node.static_id}"
|
53
|
+
end
|
54
|
+
|
55
|
+
if node.static_class.size > 0
|
56
|
+
str += ".#{node.static_class.gsub(' ', '.')}"
|
57
|
+
end
|
58
|
+
|
59
|
+
if !node.new_attributes.nil?
|
60
|
+
props = []
|
61
|
+
node.new_attributes.split(',').each do |str|
|
62
|
+
l,r = str.split(' => ')
|
63
|
+
l.gsub!("\"", '')
|
64
|
+
r.gsub!("\"", "'")
|
65
|
+
props << "#{l}=#{r}"
|
66
|
+
end
|
67
|
+
str += "(#{props.join(' ')})"
|
68
|
+
end
|
69
|
+
|
70
|
+
if !node.old_attributes.nil?
|
71
|
+
str += "{#{node.old_attributes}}"
|
72
|
+
end
|
73
|
+
|
74
|
+
if !node.oneline_child.nil?
|
75
|
+
substr = render_strings([node.oneline_child]).first
|
76
|
+
unless substr.start_with?('=')
|
77
|
+
substr = " #{substr}"
|
78
|
+
end
|
79
|
+
str += substr
|
80
|
+
end
|
81
|
+
|
82
|
+
strings << str
|
83
|
+
end
|
84
|
+
|
85
|
+
if node.respond_to?(:children) && node.children.size > 0
|
86
|
+
render_strings(node.children, space_deep + 2).each do |str|
|
87
|
+
strings << str
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
strings
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'strscan'
|
3
|
+
require_relative 'ast'
|
4
|
+
require_relative 'error'
|
5
|
+
require_relative 'ruby_multiline'
|
6
|
+
require_relative 'script_parser'
|
7
|
+
require_relative 'utils'
|
8
|
+
|
9
|
+
module HamdownCore
|
10
|
+
class ElementParser
|
11
|
+
def initialize(line_parser)
|
12
|
+
@line_parser = line_parser
|
13
|
+
end
|
14
|
+
|
15
|
+
ELEMENT_REGEXP = /\A%([-:\w]+)([-:\w.#]*)(.+)?\z/o
|
16
|
+
|
17
|
+
def parse(text)
|
18
|
+
m = text.match(ELEMENT_REGEXP)
|
19
|
+
unless m
|
20
|
+
syntax_error!('Invalid element declaration')
|
21
|
+
end
|
22
|
+
|
23
|
+
element = Ast::Element.new
|
24
|
+
element.filename = @line_parser.filename
|
25
|
+
element.lineno = @line_parser.lineno
|
26
|
+
element.tag_name = m[1]
|
27
|
+
element.static_class, element.static_id = parse_class_and_id(m[2])
|
28
|
+
rest = m[3] || ''
|
29
|
+
|
30
|
+
element.old_attributes, element.new_attributes, element.object_ref, rest = parse_attributes(rest)
|
31
|
+
element.nuke_inner_whitespace, element.nuke_outer_whitespace, rest = parse_nuke_whitespace(rest)
|
32
|
+
element.self_closing, rest = parse_self_closing(rest)
|
33
|
+
element.oneline_child = ScriptParser.new(@line_parser).parse(rest)
|
34
|
+
|
35
|
+
element
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def parse_class_and_id(class_and_id)
|
41
|
+
classes = []
|
42
|
+
id = ''
|
43
|
+
scanner = StringScanner.new(class_and_id)
|
44
|
+
until scanner.eos?
|
45
|
+
unless scanner.scan(/([#.])([-:_a-zA-Z0-9]+)/)
|
46
|
+
syntax_error!('Illegal element: classes and ids must have values.')
|
47
|
+
end
|
48
|
+
case scanner[1]
|
49
|
+
when '.'
|
50
|
+
classes << scanner[2]
|
51
|
+
when '#'
|
52
|
+
id = scanner[2]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
[classes.join(' '), id]
|
57
|
+
end
|
58
|
+
|
59
|
+
OLD_ATTRIBUTE_BEGIN = '{'
|
60
|
+
NEW_ATTRIBUTE_BEGIN = '('
|
61
|
+
OBJECT_REF_BEGIN = '['
|
62
|
+
|
63
|
+
def parse_attributes(rest)
|
64
|
+
old_attributes = nil
|
65
|
+
new_attributes = nil
|
66
|
+
object_ref = nil
|
67
|
+
|
68
|
+
loop do
|
69
|
+
case rest[0]
|
70
|
+
when OLD_ATTRIBUTE_BEGIN
|
71
|
+
if old_attributes
|
72
|
+
break
|
73
|
+
end
|
74
|
+
old_attributes, rest = parse_old_attributes(rest)
|
75
|
+
when NEW_ATTRIBUTE_BEGIN
|
76
|
+
if new_attributes
|
77
|
+
break
|
78
|
+
end
|
79
|
+
new_attributes, rest = parse_new_attributes(rest)
|
80
|
+
when OBJECT_REF_BEGIN
|
81
|
+
if object_ref
|
82
|
+
break
|
83
|
+
end
|
84
|
+
object_ref, rest = parse_object_ref(rest)
|
85
|
+
else
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
[old_attributes, new_attributes, object_ref, rest]
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_old_attributes(text)
|
94
|
+
text = text.dup
|
95
|
+
s = StringScanner.new(text)
|
96
|
+
s.pos = 1
|
97
|
+
depth = 1
|
98
|
+
loop do
|
99
|
+
depth = Utils.balance(s, '{', '}', depth)
|
100
|
+
if depth == 0
|
101
|
+
attr = s.pre_match + s.matched
|
102
|
+
return [attr[1, attr.size - 2], s.rest]
|
103
|
+
elsif /,\s*\z/ === text && @line_parser.has_next?
|
104
|
+
text << "\n" << @line_parser.next_line
|
105
|
+
else
|
106
|
+
syntax_error!('Unmatched brace')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_new_attributes(text)
|
112
|
+
text = text.dup
|
113
|
+
s = StringScanner.new(text)
|
114
|
+
s.pos = 1
|
115
|
+
depth = 1
|
116
|
+
loop do
|
117
|
+
pre_pos = s.pos
|
118
|
+
depth = Utils.balance(s, '(', ')', depth)
|
119
|
+
if depth == 0
|
120
|
+
t = s.string.byteslice(pre_pos...s.pos - 1)
|
121
|
+
return [parse_new_attribute_list(t), s.rest]
|
122
|
+
elsif @line_parser.has_next?
|
123
|
+
text << "\n" << @line_parser.next_line
|
124
|
+
else
|
125
|
+
syntax_error!('Unmatched paren')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def parse_new_attribute_list(text)
|
131
|
+
s = StringScanner.new(text)
|
132
|
+
attributes = []
|
133
|
+
until s.eos?
|
134
|
+
name = scan_key(s)
|
135
|
+
s.skip(/\s*/)
|
136
|
+
|
137
|
+
if scan_operator(s)
|
138
|
+
s.skip(/\s*/)
|
139
|
+
value = scan_value(s)
|
140
|
+
else
|
141
|
+
value = 'true'
|
142
|
+
end
|
143
|
+
spaces = s.scan(/\s*/)
|
144
|
+
line_count = spaces.count("\n")
|
145
|
+
|
146
|
+
attributes << "#{name.inspect} => #{value},#{"\n" * line_count}"
|
147
|
+
end
|
148
|
+
attributes.join
|
149
|
+
end
|
150
|
+
|
151
|
+
def scan_key(scanner)
|
152
|
+
scanner.scan(/[-:\w]+/).tap do |name|
|
153
|
+
unless name
|
154
|
+
syntax_error!('Invalid attribute list (missing attribute name)')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def scan_operator(scanner)
|
160
|
+
scanner.skip(/=/)
|
161
|
+
end
|
162
|
+
|
163
|
+
def scan_value(scanner)
|
164
|
+
quote = scanner.scan(/["']/)
|
165
|
+
if quote
|
166
|
+
scan_quoted_value(scanner, quote)
|
167
|
+
else
|
168
|
+
scan_variable_value(scanner)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def scan_quoted_value(scanner, quote)
|
173
|
+
re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
|
174
|
+
pos = scanner.pos
|
175
|
+
loop do
|
176
|
+
unless scanner.scan(re)
|
177
|
+
syntax_error!('Invalid attribute list (mismatched quotation)')
|
178
|
+
end
|
179
|
+
if scanner[2] == quote
|
180
|
+
break
|
181
|
+
end
|
182
|
+
depth = Utils.balance(scanner, '{', '}')
|
183
|
+
if depth != 0
|
184
|
+
syntax_error!('Invalid attribute list (mismatched interpolation)')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
str = scanner.string.byteslice(pos - 1..scanner.pos - 1)
|
188
|
+
|
189
|
+
# Even if the quote is single, string interpolation is performed in Haml.
|
190
|
+
str[0] = '"'
|
191
|
+
str[-1] = '"'
|
192
|
+
str
|
193
|
+
end
|
194
|
+
|
195
|
+
def scan_variable_value(scanner)
|
196
|
+
scanner.scan(/(@@?|\$)?\w+/).tap do |var|
|
197
|
+
unless var
|
198
|
+
syntax_error!('Invalid attribute list (invalid variable name)')
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def parse_object_ref(text)
|
204
|
+
s = StringScanner.new(text)
|
205
|
+
s.pos = 1
|
206
|
+
depth = Utils.balance(s, '[', ']')
|
207
|
+
if depth == 0
|
208
|
+
[s.pre_match[1, s.pre_match.size - 1], s.rest]
|
209
|
+
else
|
210
|
+
syntax_error!('Unmatched brackets for object reference')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def parse_nuke_whitespace(rest)
|
215
|
+
m = rest.match(/\A(><|<>|[><])(.*)\z/)
|
216
|
+
if m
|
217
|
+
nuke_whitespace = m[1]
|
218
|
+
[
|
219
|
+
nuke_whitespace.include?('<'),
|
220
|
+
nuke_whitespace.include?('>'),
|
221
|
+
m[2],
|
222
|
+
]
|
223
|
+
else
|
224
|
+
[false, false, rest]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def parse_self_closing(rest)
|
229
|
+
if rest[0] == '/'
|
230
|
+
if rest.size > 1
|
231
|
+
syntax_error!("Self-closing tags can't have content")
|
232
|
+
end
|
233
|
+
[true, '']
|
234
|
+
else
|
235
|
+
[false, rest]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def syntax_error!(message)
|
240
|
+
raise Error.new(message, @line_parser.lineno)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'parser'
|
3
|
+
require_relative 'transformer'
|
4
|
+
require_relative 'compiler'
|
5
|
+
|
6
|
+
module HamdownCore
|
7
|
+
module Engine
|
8
|
+
def self.call(content)
|
9
|
+
ast = HamdownCore::Parser.new.call(content)
|
10
|
+
transformed_ast = HamdownCore::Transformer.call(ast)
|
11
|
+
puts HamdownCore::Compiler.call(transformed_ast)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'ast'
|
3
|
+
|
4
|
+
module HamdownCore
|
5
|
+
class FilterParser
|
6
|
+
def initialize(indent_tracker)
|
7
|
+
@ast = nil
|
8
|
+
@indent_level = nil
|
9
|
+
@indent_tracker = indent_tracker
|
10
|
+
end
|
11
|
+
|
12
|
+
def enabled?
|
13
|
+
!@ast.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def start(name, filename, lineno)
|
17
|
+
@ast = Ast::Filter.new
|
18
|
+
@ast.name = name
|
19
|
+
@ast.filename = filename
|
20
|
+
@ast.lineno = lineno
|
21
|
+
end
|
22
|
+
|
23
|
+
def append(line)
|
24
|
+
indent, text = @indent_tracker.split(line)
|
25
|
+
if text.empty?
|
26
|
+
@ast.texts << ''
|
27
|
+
return
|
28
|
+
end
|
29
|
+
indent_level = indent.size
|
30
|
+
|
31
|
+
if @indent_level
|
32
|
+
if indent_level < @indent_level
|
33
|
+
# Finish filter
|
34
|
+
@indent_level = nil
|
35
|
+
ast = @ast
|
36
|
+
@ast = nil
|
37
|
+
return ast
|
38
|
+
end
|
39
|
+
elsif indent_level > @indent_tracker.current_level
|
40
|
+
# Start filter
|
41
|
+
@indent_level = indent_level
|
42
|
+
else
|
43
|
+
# Empty filter
|
44
|
+
@ast = nil
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
text = line[@indent_level..-1]
|
49
|
+
@ast.texts << text
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish
|
54
|
+
@ast
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|