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.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module H2o
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
class Template
|
5
|
+
attr_reader :context
|
6
|
+
|
7
|
+
def initialize (filename, env = {})
|
8
|
+
@file = Pathname.new(filename)
|
9
|
+
env[:search_path] = @file.dirname
|
10
|
+
@nodelist = Template.load(@file, env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def render (context = {})
|
14
|
+
@context = Context.new(context)
|
15
|
+
output_stream = []
|
16
|
+
@nodelist.render(@context, output_stream)
|
17
|
+
output_stream.join
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse source, env = {}
|
21
|
+
parser = Parser.new(source, false, env)
|
22
|
+
parsed = parser.parse
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load file, env = {}
|
26
|
+
file = env[:search_path] + file if file.is_a? String
|
27
|
+
parser = Parser.new(file.read, file, env)
|
28
|
+
parser.parse
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require File.dirname(__FILE__) + '/core_ext/object'
|
34
|
+
|
35
|
+
require File.dirname(__FILE__) + '/h2o/constants'
|
36
|
+
require File.dirname(__FILE__) + '/h2o/errors'
|
37
|
+
require File.dirname(__FILE__) + '/h2o/nodes'
|
38
|
+
require File.dirname(__FILE__) + '/h2o/tags'
|
39
|
+
require File.dirname(__FILE__) + '/h2o/parser'
|
40
|
+
require File.dirname(__FILE__) + '/h2o/context'
|
41
|
+
require File.dirname(__FILE__) + '/h2o/filters'
|
42
|
+
|
43
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module H2o
|
3
|
+
BLOCK_START = '{%'
|
4
|
+
BLOCK_END = '%}'
|
5
|
+
VAR_START = '{{'
|
6
|
+
VAR_END = '}}'
|
7
|
+
COMMENT_START = '{*'
|
8
|
+
COMMENT_END = '*}'
|
9
|
+
|
10
|
+
PIPE_RE = /\|/
|
11
|
+
SEPERATOR_RE = /,/
|
12
|
+
FILTER_END_RE = /;/
|
13
|
+
|
14
|
+
NIL_RE = /nil|null|none/
|
15
|
+
WHITESPACE_RE = /\s+/m
|
16
|
+
BOOLEAN_RE = /true|false/
|
17
|
+
NUMBER_RE = /\d+(\.\d*)?/
|
18
|
+
OPERATOR_RE = /(?:>=|<=|!=|==|>|<|!|and|not|or)/
|
19
|
+
|
20
|
+
STRING_RE = /
|
21
|
+
(?:
|
22
|
+
"([^"\\]*(?:\\.[^"\\]*)*)"
|
23
|
+
|
|
24
|
+
'([^'\\]*(?:\\.[^'\\]*)*)'
|
25
|
+
)
|
26
|
+
/xm
|
27
|
+
|
28
|
+
IDENTIFIER_RE = /[a-zA-Z_][a-zA-Z0-9_]*/
|
29
|
+
|
30
|
+
NAME_RE = /
|
31
|
+
#{IDENTIFIER_RE}
|
32
|
+
(?:\.[a-zA-Z0-9][a-zA-Z0-9_-]*)*
|
33
|
+
/x
|
34
|
+
|
35
|
+
NAMED_ARGS_RE = /
|
36
|
+
(#{NAME_RE})(?:#{WHITESPACE_RE})?
|
37
|
+
:
|
38
|
+
(?:#{WHITESPACE_RE})?(#{STRING_RE}|#{NUMBER_RE}|#{NAME_RE})
|
39
|
+
/x
|
40
|
+
end
|
data/lib/h2o/context.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
module H2o
|
2
|
+
class Context
|
3
|
+
|
4
|
+
def initialize(context ={})
|
5
|
+
@stack = [context]
|
6
|
+
@filter_env = Filters.build(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
# doing a reverse lookup
|
10
|
+
# FIXME: need to double check this, also changed Block#add_layer in reverse order
|
11
|
+
def [](name)
|
12
|
+
@stack.each do |layer|
|
13
|
+
value = layer[name]
|
14
|
+
return value unless value.nil?
|
15
|
+
end
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(name, value)
|
20
|
+
@stack.first[name] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def pop
|
24
|
+
@stack.shift if @stack.size > 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def push(hash = {})
|
28
|
+
@stack.unshift hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def stack
|
32
|
+
result = nil
|
33
|
+
push
|
34
|
+
begin
|
35
|
+
result = yield
|
36
|
+
ensure
|
37
|
+
pop
|
38
|
+
end
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve(name)
|
43
|
+
return name unless name.is_a? Symbol
|
44
|
+
|
45
|
+
object = self
|
46
|
+
parts = name.to_s.split('.')
|
47
|
+
part_sym = nil
|
48
|
+
|
49
|
+
parts.each do |part|
|
50
|
+
part_sym = part.to_sym
|
51
|
+
|
52
|
+
# Hashes
|
53
|
+
if object.respond_to?(:has_key?) && (object.has_key?(part_sym) || object.has_key?(part))
|
54
|
+
result = object[part_sym] || object[part]
|
55
|
+
# Proc object with extra caution
|
56
|
+
begin
|
57
|
+
result = object[part_sym] = result.call if result.is_a?(Proc) && object.respond_to?(:[]=)
|
58
|
+
rescue
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
object = result.to_h2o
|
62
|
+
|
63
|
+
# Array and Hash like objects
|
64
|
+
elsif part.match(/^-?\d+$/)
|
65
|
+
if (object.respond_to?(:has_key?) || object.respond_to?(:fetch)) && value = object[part.to_i]
|
66
|
+
object = value.to_h2o
|
67
|
+
else
|
68
|
+
return nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# H2o::DataObject Type
|
72
|
+
elsif (object.is_a?(DataObject) || object.class.h2o_safe_methods && object.class.h2o_safe_methods.include?(part_sym) )&& \
|
73
|
+
object.respond_to?(part_sym)
|
74
|
+
object = object.__send__(part_sym).to_h2o
|
75
|
+
|
76
|
+
# Sweet array shortcuts
|
77
|
+
elsif object.respond_to?(part_sym) && [:first, :length, :size, :last].include?(part_sym)
|
78
|
+
object = object.__send__(part_sym).to_h2o
|
79
|
+
else
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
object
|
85
|
+
end
|
86
|
+
|
87
|
+
def has_key?(key)
|
88
|
+
!send(:[], key).nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def apply_filters(object, filters)
|
92
|
+
filters.each do |filter|
|
93
|
+
name, *args = filter
|
94
|
+
|
95
|
+
raise FilterError, "Filter(#{name}) not found" unless @filter_env.respond_to?(name)
|
96
|
+
|
97
|
+
args.map! do |arg|
|
98
|
+
if arg.kind_of? Symbol
|
99
|
+
resolve(arg)
|
100
|
+
else
|
101
|
+
arg
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
object = @filter_env.__send__(name, object, *args)
|
106
|
+
end
|
107
|
+
object
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class DataObject
|
113
|
+
INTERNAL_METHOD = /^__/
|
114
|
+
@@required_methods = [:__send__, :__id__, :object_id, :respond_to?, :extend, :methods, :class, :nil?, :is_a?]
|
115
|
+
|
116
|
+
def respond_to?(method)
|
117
|
+
method_name = method.to_s
|
118
|
+
return false if method_name =~ INTERNAL_METHOD
|
119
|
+
return false if @@required_methods.include?(method_name)
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
123
|
+
# remove all standard methods for security
|
124
|
+
instance_methods.each do |m|
|
125
|
+
unless @@required_methods.include?(m.to_sym)
|
126
|
+
undef_method m
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class BlockContext < DataObject
|
132
|
+
|
133
|
+
def self.h2o_safe_methods
|
134
|
+
[:super]
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_h2o
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
def initialize(block, context, stream, index)
|
142
|
+
@block, @context, @stream, @index = block, context, stream, index
|
143
|
+
end
|
144
|
+
|
145
|
+
def super
|
146
|
+
@block.parent.render(@context, @stream, @index-1) if @block.parent and @block.parent.stack_size > @index.abs
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
|
150
|
+
def depth
|
151
|
+
@index.abs
|
152
|
+
end
|
153
|
+
|
154
|
+
def name
|
155
|
+
@block.name
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/lib/h2o/datatype.rb
ADDED
data/lib/h2o/errors.rb
ADDED
data/lib/h2o/filters.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module H2o
|
2
|
+
module Filters
|
3
|
+
|
4
|
+
class Base < H2o::DataObject
|
5
|
+
def initialize(context)
|
6
|
+
@context = context
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
@filters = []
|
11
|
+
|
12
|
+
# Class methods of filters
|
13
|
+
class << self
|
14
|
+
def [] name
|
15
|
+
@filters[name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def << (filter)
|
19
|
+
@filters << filter
|
20
|
+
end
|
21
|
+
|
22
|
+
def register filter
|
23
|
+
@filters << filter
|
24
|
+
end
|
25
|
+
|
26
|
+
def build(context)
|
27
|
+
@base = Base.new(context)
|
28
|
+
|
29
|
+
@filters.each do |filter|
|
30
|
+
@base.extend(filter)
|
31
|
+
end
|
32
|
+
|
33
|
+
@base
|
34
|
+
end
|
35
|
+
|
36
|
+
def create name, &block
|
37
|
+
Base.class_eval do
|
38
|
+
define_method name, &block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def all
|
43
|
+
@filters
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
require File.dirname(__FILE__) + '/filters/default'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module DefaultFilters
|
2
|
+
|
3
|
+
# String filters
|
4
|
+
def upper string
|
5
|
+
string.to_s.upcase
|
6
|
+
end
|
7
|
+
|
8
|
+
def lower string
|
9
|
+
string.to_s.downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
def capitalize string
|
13
|
+
string.to_s.capitalize
|
14
|
+
end
|
15
|
+
|
16
|
+
def escape string, attribute=false
|
17
|
+
string = string.dup.to_s
|
18
|
+
|
19
|
+
{
|
20
|
+
'&' => '&',
|
21
|
+
'>' => '>',
|
22
|
+
'<' => '<'
|
23
|
+
}.each do |v, k|
|
24
|
+
string.tr!(v, k)
|
25
|
+
end
|
26
|
+
|
27
|
+
string.gsub!(/"/, '"') if attribute
|
28
|
+
|
29
|
+
string
|
30
|
+
end
|
31
|
+
|
32
|
+
# Array Filters
|
33
|
+
def join(list, delimiter=', ')
|
34
|
+
list.join(delimiter)
|
35
|
+
end
|
36
|
+
|
37
|
+
def first(list)
|
38
|
+
list.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def last(list)
|
42
|
+
list.last
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
H2o::Filters << self
|
47
|
+
end
|
data/lib/h2o/nodes.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module H2o
|
2
|
+
class Node
|
3
|
+
def initialize(parser, position = 0)
|
4
|
+
raise "Subclass should implement initialize method"
|
5
|
+
end
|
6
|
+
|
7
|
+
def render(context, stream)
|
8
|
+
raise "Subclass should implement method method"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Nodelist
|
13
|
+
#
|
14
|
+
class Nodelist < Node
|
15
|
+
attr_reader :parser
|
16
|
+
|
17
|
+
def initialize(parser, position = 0)
|
18
|
+
@parser = parser
|
19
|
+
@stack = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(context = {}, stream = false)
|
23
|
+
stream = [] unless stream
|
24
|
+
context = Context.new(context) if context.is_a? Hash
|
25
|
+
|
26
|
+
@stack.each do |node|
|
27
|
+
node.render(context, stream)
|
28
|
+
end
|
29
|
+
|
30
|
+
stream.join
|
31
|
+
end
|
32
|
+
|
33
|
+
def <<(node)
|
34
|
+
@stack << node
|
35
|
+
end
|
36
|
+
|
37
|
+
def length
|
38
|
+
@stack.length
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class TextNode < Node
|
43
|
+
def initialize(content)
|
44
|
+
@content = content
|
45
|
+
end
|
46
|
+
|
47
|
+
def render(context, stream)
|
48
|
+
stream << @content
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class VariableNode < Node
|
53
|
+
def initialize (name, filters)
|
54
|
+
@name = name
|
55
|
+
@filters = filters.empty? ? nil : filters
|
56
|
+
end
|
57
|
+
|
58
|
+
def render(context, stream)
|
59
|
+
variable = @name.is_a?(Symbol) ? context.resolve(@name) : @name
|
60
|
+
variable = context.apply_filters(variable, @filters) if @filters
|
61
|
+
stream << variable
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class CommentNode < Node
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|