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.
- data/README.md +52 -0
- data/example/erb/base.html +56 -0
- data/example/h2o/base.html +85 -0
- data/example/h2o/inherit.html +7 -0
- data/example/liquid/base.html +57 -0
- data/example/request.html +0 -0
- data/example/run.rb +103 -0
- data/example/server +3 -0
- data/example/server.bat +1 -0
- data/example/server.rb +37 -0
- data/h2o.gemspec +43 -0
- data/lib/core_ext/object.rb +13 -0
- data/lib/h2o.rb +43 -0
- data/lib/h2o/constants.rb +40 -0
- data/lib/h2o/context.rb +158 -0
- data/lib/h2o/datatype.rb +11 -0
- data/lib/h2o/errors.rb +7 -0
- data/lib/h2o/filters.rb +49 -0
- data/lib/h2o/filters/default.rb +47 -0
- data/lib/h2o/nodes.rb +68 -0
- data/lib/h2o/parser.rb +225 -0
- data/lib/h2o/tags.rb +22 -0
- data/lib/h2o/tags/block.rb +64 -0
- data/lib/h2o/tags/for.rb +76 -0
- data/lib/h2o/tags/if.rb +77 -0
- data/lib/h2o/tags/recurse.rb +55 -0
- data/lib/h2o/tags/with.rb +28 -0
- metadata +82 -0
data/lib/h2o/parser.rb
ADDED
@@ -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
|
data/lib/h2o/tags.rb
ADDED
@@ -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
|
data/lib/h2o/tags/for.rb
ADDED
@@ -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
|
data/lib/h2o/tags/if.rb
ADDED
@@ -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
|