h2o 0.2

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