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,13 @@
1
+ class Object
2
+ def self.h2o_expose(*attrs)
3
+ @h2o_safe = attrs
4
+ end
5
+
6
+ def self.h2o_safe_methods
7
+ @h2o_safe
8
+ end
9
+
10
+ def to_h2o
11
+ self
12
+ end
13
+ end
@@ -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
@@ -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
@@ -0,0 +1,11 @@
1
+ module H2o
2
+ class Stream < Array
3
+ def << (item)
4
+ unshift item.to_s
5
+ end
6
+
7
+ def close
8
+ reverse!
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module H2o
2
+ class RuntimeError < Exception ;end
3
+ class SyntaxError < Exception ;end
4
+ class TemplateNotFound < Exception ;end
5
+ class ParserError < Exception ;end
6
+ class FilterError < Exception ;end
7
+ end
@@ -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
+ '&' => '&amp;',
21
+ '>' => '&gt;',
22
+ '<' => '&lt;'
23
+ }.each do |v, k|
24
+ string.tr!(v, k)
25
+ end
26
+
27
+ string.gsub!(/"/, '&quot;') 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
@@ -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