h2o 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,225 @@
1
+ module H2o
2
+ class Parser
3
+ attr_reader :token, :env
4
+ attr_accessor :storage
5
+
6
+ ParseRegex = /\G
7
+ (.*?)(?:
8
+ #{Regexp.escape(BLOCK_START)} (.*?)
9
+ #{Regexp.escape(BLOCK_END)} |
10
+ #{Regexp.escape(VAR_START)} (.*?)
11
+ #{Regexp.escape(VAR_END)} |
12
+ #{Regexp.escape(COMMENT_START)} (.*?)
13
+ #{Regexp.escape(COMMENT_END)}
14
+ )(?:\r?\n)?
15
+ /xm
16
+
17
+ def initialize (source, filename, env = {})
18
+ @env = env
19
+ @storage = {}
20
+ @source = source
21
+ @filename = filename
22
+ @tokenstream = tokenize
23
+ @first = true
24
+ end
25
+
26
+ def tokenize
27
+ result = []
28
+ lstrip = false
29
+ rstrip = false
30
+
31
+ @source.scan(ParseRegex).each do |match|
32
+ result << [:text, match[0]] if match[0] and !match[0].empty?
33
+ # strip next left white spaces
34
+ result.last[1].lstrip! if lstrip
35
+
36
+ if data = match[1]
37
+ data = data.strip
38
+
39
+ # strip right whitespaces on previous text node
40
+ if rstrip = data[0].chr == "-"
41
+ data.slice!(0)
42
+ result.last[1].rstrip! if rstrip
43
+ end
44
+
45
+ if lstrip = data[data.length-1].chr == "-"
46
+ data.chop!
47
+ end
48
+
49
+ result << [:block, data.strip]
50
+ elsif data = match[2]
51
+ result << [:variable, data.strip]
52
+ elsif data = match[3]
53
+ result << [:comment, data.strip]
54
+ end
55
+ end
56
+
57
+ rest = $~.nil? ? @source : @source[$~.end(0) .. -1]
58
+ unless rest.empty?
59
+ result << [:text, rest]
60
+ # strip next left white spaces
61
+ result.last[1].lstrip! if lstrip
62
+ end
63
+ result.reverse
64
+ end
65
+
66
+ def first?
67
+ @first
68
+ end
69
+
70
+ def parse(*untils)
71
+ nodelist = Nodelist.new(self)
72
+
73
+ while @token = @tokenstream.pop
74
+ token, content = @token
75
+ case token
76
+ when :text
77
+ nodelist << TextNode.new(content) unless content.empty?
78
+ when :variable
79
+ names = []
80
+ filters = []
81
+ Parser.parse_arguments(content).each do |argument|
82
+ if argument.is_a? Array
83
+ filters << argument
84
+ else
85
+ names << argument
86
+ end
87
+ end
88
+ nodelist << VariableNode.new(names.first, filters)
89
+ when :block
90
+ name, args = content.split(/\s+/, 2)
91
+ name = name.to_sym
92
+
93
+ if untils.include?(name)
94
+ return nodelist
95
+ end
96
+
97
+ tag = Tags[name]
98
+ raise "Unknow tag #{name}" if tag.nil?
99
+
100
+ nodelist << tag.new(self, args) if tag
101
+ when :comment
102
+ nodelist << CommentNode.new(content)
103
+ end
104
+ @first = false
105
+ end
106
+ nodelist
107
+ end
108
+
109
+ def self.parse_arguments (argument)
110
+ result = current_buffer = []
111
+ filter_buffer = []
112
+ data = nil
113
+ ArgumentLexer.lex(argument).each do |token|
114
+ token, data = token
115
+ case token
116
+ when :filter_start
117
+ current_buffer = filter_buffer.clear
118
+ when :filter_end
119
+ result << filter_buffer.dup unless filter_buffer.empty?
120
+ current_buffer = result
121
+ when :nil
122
+ current_buffer << nil
123
+ when :boolean
124
+ current_buffer << (data == 'true'? true : false)
125
+ when :name
126
+ current_buffer << data.to_sym
127
+ when :number
128
+ current_buffer << (data.include?('.') ? data.to_f : data.to_i)
129
+ when :string
130
+ data.match(STRING_RE)
131
+ current_buffer << ($1 || $2)
132
+ when :named_argument
133
+ current_buffer << {} unless current_buffer.last.is_a?(Hash)
134
+
135
+ named_args = current_buffer.last
136
+ name, value = data.split(':', 2).map{|m| m.strip}
137
+ named_args[name.to_sym] = parse_arguments(value).first
138
+ when :operator
139
+ # replace exclaimation mark '!' into not
140
+ data = 'not' if data == '!'
141
+ current_buffer << {:operator => data.to_sym}
142
+ end
143
+ end
144
+ result
145
+ end
146
+
147
+ def pretty_print pp
148
+ nil
149
+ end
150
+ end
151
+
152
+ class ArgumentLexer
153
+ require 'strscan'
154
+
155
+ def initialize(argstring, pos = 0)
156
+ @argument = argstring
157
+ end
158
+
159
+ def self.lex(argstring)
160
+ new(argstring).lexer()
161
+ end
162
+
163
+ def lexer
164
+ s = StringScanner.new(@argument)
165
+ state = :initial
166
+ result = []
167
+ while ! s.eos?
168
+ next if s.scan(WHITESPACE_RE)
169
+
170
+ if state == :initial
171
+ if match = s.scan(OPERATOR_RE)
172
+ result << [:operator, match]
173
+ elsif match = s.scan(BOOLEAN_RE)
174
+ result << [:boolean, match]
175
+ elsif match = s.scan(NIL_RE)
176
+ result << [:nil, match]
177
+ elsif match = s.scan(NAMED_ARGS_RE)
178
+ result << [:named_argument, match]
179
+ elsif match = s.scan(NAME_RE)
180
+ result << [:name, match]
181
+ elsif match = s.scan(PIPE_RE)
182
+ state = :filter
183
+ result << [:filter_start, nil]
184
+ elsif match = s.scan(SEPERATOR_RE)
185
+ result << [:seperator, nil]
186
+ elsif match = s.scan(STRING_RE)
187
+ result << [:string, match]
188
+ elsif match = s.scan(NUMBER_RE)
189
+ result << [:number, match]
190
+ else
191
+ raise SyntaxError, "unexpected character #{s.getch} in tag"
192
+ end
193
+ elsif state == :filter
194
+ if match = s.scan(PIPE_RE)
195
+ result << [:filter_end, nil]
196
+ result << [:filter_start, nil]
197
+ elsif match = s.scan(SEPERATOR_RE)
198
+ result << [:seperator, nil]
199
+ elsif match = s.scan(FILTER_END_RE)
200
+ result << [:filter_end, nil]
201
+ state = :initial
202
+ elsif match = s.scan(BOOLEAN_RE)
203
+ result << [:boolean, match]
204
+ elsif match = s.scan(NIL_RE)
205
+ result << [:nil, match]
206
+ elsif match = s.scan(NAMED_ARGS_RE)
207
+ result << [:named_argument, match]
208
+ elsif match = s.scan(NAME_RE)
209
+ result << [:name, match]
210
+ elsif match = s.scan(STRING_RE)
211
+ result << [:string, match]
212
+ elsif match = s.scan(NUMBER_RE)
213
+ result << [:number, match]
214
+ else
215
+ raise SyntaxError, "unexpected character #{s.getch} in filter"
216
+ end
217
+ end
218
+ end
219
+ result << [:filter_end, nil] if state == :filter
220
+
221
+ result
222
+ end
223
+
224
+ end
225
+ end
@@ -0,0 +1,22 @@
1
+ module H2o
2
+ module Tags
3
+ @tags = {}
4
+ class << self
5
+ def [] name
6
+ @tags[name]
7
+ end
8
+
9
+ def register(tag, name)
10
+ @tags[name] = tag
11
+ end
12
+ end
13
+
14
+ class Tag < Node; end
15
+ end
16
+ end
17
+
18
+ require File.dirname(__FILE__) + '/tags/if'
19
+ require File.dirname(__FILE__) + '/tags/for'
20
+ require File.dirname(__FILE__) + '/tags/block'
21
+ require File.dirname(__FILE__) + '/tags/with'
22
+ require File.dirname(__FILE__) + '/tags/recurse'
@@ -0,0 +1,64 @@
1
+ module H2o
2
+ module Tags
3
+ # Block tag allows to divide document into reusable blocks
4
+ #
5
+ class Block < Tag
6
+ attr_reader :name
7
+ attr_accessor :parent
8
+
9
+ def initialize parser, argstring
10
+ @name = argstring.to_sym
11
+ @stack = [ parser.parse(:endblock) ]
12
+ blocks = parser.storage[:blocks] ||= {}
13
+ raise SyntaxError, "block name needs to be unique" if blocks.include? @name
14
+ blocks[@name] = self
15
+ end
16
+
17
+ def stack_size
18
+ @stack.size
19
+ end
20
+
21
+ def add_layer nodelist
22
+ @stack << nodelist
23
+ end
24
+
25
+ def render context, stream, index=-1
26
+ context.stack do
27
+ context[:block] = BlockContext.new(self, context, stream, index)
28
+ @stack[index].render(context, stream)
29
+ end
30
+ end
31
+ Tags.register(self, :block)
32
+ end
33
+
34
+ class Extends < Tag
35
+ Syntax = /\"(.*?)\"|\'(.*?)\'/
36
+
37
+ def initialize parser, argstring
38
+ unless parser.first?
39
+ raise SyntaxError, "extend tag needs to be at the beginning of the document"
40
+ end
41
+ # parser the entire subtemplate
42
+ parser.parse()
43
+
44
+ # load the parent template into nodelist
45
+ @nodelist = Template.load(argstring[1...-1], parser.env)
46
+
47
+ blocks = @nodelist.parser.storage[:blocks] || {}
48
+
49
+ (parser.storage[:blocks] || []).each do |name, block|
50
+ if blocks.include? name
51
+ blocks[name].add_layer(block)
52
+ block.parent = blocks[name]
53
+ end
54
+ end
55
+ end
56
+
57
+ def render context, stream
58
+ @nodelist.render(context, stream)
59
+ end
60
+
61
+ Tags.register(self, :extends)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,76 @@
1
+ module H2o
2
+ module Tags
3
+ class For < Tag
4
+ Syntax = /
5
+ (?:(#{H2o::IDENTIFIER_RE}),\s?)?
6
+ (#{H2o::IDENTIFIER_RE})\s+in\s+(#{H2o::NAME_RE})\s*
7
+ (reversed)?
8
+ /x
9
+
10
+ def initialize(parser, argstring)
11
+ @key = false
12
+ @else = false
13
+ @body = parser.parse(:else, :endfor)
14
+ @else = parser.parse(:endfor) if parser.token && parser.token.include?('else')
15
+
16
+ if matches = Syntax.match(argstring)
17
+ @key = $1.to_sym unless $1.nil?
18
+ @item = $2.to_sym
19
+ @iteratable = $3.to_sym
20
+ @reverse = !$4.nil?
21
+ else
22
+ raise SyntaxError, "Invalid for loop syntax "
23
+ end
24
+ end
25
+
26
+ def render(context, stream)
27
+ iteratable = context.resolve(@iteratable)
28
+ iteratable.reverse! if @reverse
29
+ length = 0
30
+
31
+ if iteratable.respond_to?(:each)
32
+ length = iteratable.size || iterabe.length
33
+ end
34
+
35
+ if length > 0
36
+ parent = context[:loop]
37
+ # Main iteration
38
+ context.stack do
39
+ iteratable.each_with_index do |*args|
40
+ value, index = args
41
+
42
+ if args.first.is_a? Array
43
+ key, value = value
44
+ else
45
+ key = index
46
+ end
47
+
48
+ is_even = index % 2 != 0
49
+ rev_count = length - index
50
+ context[@item] = value
51
+ context[@key] = key
52
+ context[:loop] = {
53
+ :parent => parent,
54
+ :first => index == 0,
55
+ :last => rev_count == 1,
56
+ :counter => index + 1,
57
+ :counter0 => index,
58
+ :revcounter => rev_count,
59
+ :revcounter0 => rev_count - 1,
60
+ :even => is_even,
61
+ :odd => !is_even
62
+ }
63
+ @body.render(context, stream)
64
+ end
65
+ end
66
+ else
67
+ # Else statement
68
+ @else.render(context, stream) if @else
69
+ end
70
+ nil
71
+ end
72
+ Tags.register(self, :for)
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,77 @@
1
+ module H2o
2
+ module Tags
3
+ class If < Tag
4
+ @else = false
5
+ @negated = false
6
+
7
+ def initialize(parser, argstring)
8
+ raise SyntaxError, "If tag doesn't support Keywords(and or)" if argstring =~ / and|or /
9
+
10
+ @body = parser.parse(:else, :endif)
11
+ @else = parser.parse(:endif) if parser.token.include? 'else'
12
+ @args = Parser.parse_arguments(argstring)
13
+
14
+ # Negated condition
15
+ first = @args.first
16
+ if first.is_a?(Hash) && first[:operator] && [:"!", :not].include?(first[:operator])
17
+ @negated = true
18
+ @args.shift
19
+ end
20
+ end
21
+
22
+ def render(context, stream)
23
+ if self.evaluate(context)
24
+ @body.render(context, stream)
25
+ else
26
+ @else.render(context, stream) if @else
27
+ end
28
+ end
29
+
30
+ def evaluate(context)
31
+ # Implicity evaluation
32
+ if @args.size == 1
33
+ object = context.resolve(@args.first)
34
+ if object == false
35
+ result = false
36
+ elsif object == true
37
+ result = true
38
+ elsif object.respond_to? :length
39
+ result = object.length != 0
40
+ elsif object.respond_to? :size
41
+ result = object.size != 0
42
+ else
43
+ result = !object.nil?
44
+ end
45
+ # Comparisons
46
+ elsif @args.size == 3
47
+ left, op, right = @args
48
+ right = context.resolve(right)
49
+ left = context.resolve(left)
50
+
51
+ result = comparision(op[:operator], left, right)
52
+ end
53
+
54
+ return @negated ? !result : result
55
+ end
56
+
57
+ def comparision(operator, left, right)
58
+ case operator
59
+ when :>
60
+ left > right
61
+ when :>=
62
+ left >= right
63
+ when :==
64
+ left == right
65
+ when :<
66
+ left < right
67
+ when :<=
68
+ left <= right
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ Tags.register(self, :if)
75
+ end
76
+ end
77
+ end