action_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/TODO ADDED
@@ -0,0 +1,32 @@
1
+
2
+
3
+
4
+
5
+ dialect mixins
6
+ templating
7
+
8
+ rewrite tests
9
+ tests for:
10
+ variable scope copy
11
+ after hooks
12
+ action namespaces
13
+ running different action layers
14
+ get, put, post, delete
15
+ postprocessors
16
+ mounting
17
+ layers & application
18
+
19
+
20
+ documentation for:
21
+ postprocessors
22
+ action namespaces
23
+ new mounting
24
+ layer application
25
+ ActionTree.layer
26
+ Node.apply(layer)
27
+ mounting versus layer application
28
+ get, put, post, and update helpers
29
+ DSL shorthands/aliases
30
+ applying plugins
31
+
32
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,132 @@
1
+
2
+
3
+
4
+
5
+ class ActionTree::Basic::Match
6
+
7
+ include ::ActionTree::DialectHelper
8
+
9
+ DEFAULT_HELPERS = []
10
+
11
+ attr_reader :node, :selected_scope, :parent, :fragment
12
+
13
+ def initialize(node, parent, fragment)
14
+ @node, @parent, @fragment = node, parent, fragment
15
+ end
16
+
17
+ def found?; @node; end
18
+
19
+ def captures
20
+ @captures ||= read_captures
21
+ end
22
+
23
+ def match_chain
24
+ @match_chain ||= @parent ? @parent.match_chain << self : [self]
25
+ end
26
+
27
+ def valid_match_chain
28
+ @valid_match_chain ||= match_chain.select(&:node)
29
+ end
30
+
31
+ def node_chain
32
+ @node_chain ||= valid_match_chain.map(&:node)
33
+ end
34
+
35
+ def path
36
+ match_chain.map(&:fragment).join('/')
37
+ end
38
+
39
+ # Run the action; first the before filters within the match chain, then the actions, then the postprocessors, then the after filters, returning the return value of the last evaluated action.
40
+ def run(namespace=:default, *scope_sources)
41
+
42
+ eval_scope = ::ActionTree::EvalScope.new(
43
+ *helper_mixins, *scope_sources, captures,
44
+ *DEFAULT_HELPERS,
45
+ {:__match => self}
46
+ )
47
+
48
+ hooks(:before_hooks).each {|prc| eval_scope.instance_eval(&prc) }
49
+ result = eval_scope.instance_eval(&main_action(namespace))
50
+ hooks(:after_hooks).each {|prc| eval_scope.instance_eval(&prc) }
51
+
52
+ postprocessors.inject(result) do |result, postprocessor|
53
+ eval_scope.instance_exec(result, &postprocessor)
54
+ end
55
+ end
56
+
57
+
58
+ # Return @self@ or a descendant matching @path@, or a {NotFound} if not found.
59
+ def match(path)
60
+ path = parse_path(path)
61
+ return self if path.empty?
62
+ return self unless found?
63
+
64
+ fragment = path.shift
65
+ @node.children.each do |child|
66
+ if child.match?(fragment)
67
+ return self.class.new(
68
+ child, self, fragment).match(path)
69
+ end
70
+ end
71
+
72
+ self.class.new(nil, self, fragment) # not found
73
+ end
74
+
75
+ private
76
+ def parse_path(path)
77
+ case path
78
+ when Array then path
79
+ else path.to_s.gsub(/(^\/)|(\/$)/, '').split('/')
80
+ end
81
+ end
82
+
83
+ def not_found_handler
84
+ node_chain.reverse.each do |node|
85
+ return node.not_found_handler if node.not_found_handler
86
+ end; nil
87
+ end
88
+
89
+ def read_captures
90
+ hsh = ::ActionTree::CaptureHash.new
91
+
92
+ match_chain[1..-1].each do |m|
93
+ if m.found? && m.node.capture_names.any?
94
+ raw_captures = m.fragment.match(m.node.regexp).captures
95
+ m.node.capture_names.each_with_index do |name, i|
96
+ hsh.add(name, raw_captures[i])
97
+ end
98
+ end
99
+ end
100
+ hsh
101
+ end
102
+
103
+ def main_action(namespace)
104
+ (found? ?
105
+ @node.action_namespaces[namespace] :
106
+ not_found_handler
107
+ ) || Proc.new { nil }
108
+ end
109
+
110
+ def helper_mixins
111
+ node_chain.map(&:helper_scope)
112
+ end
113
+
114
+ def hooks(type)
115
+ node_chain.map {|n| n.send(type) }.flatten
116
+ end
117
+
118
+ def postprocessors
119
+ @node ? @node.postprocessors : []
120
+ end
121
+
122
+ def inherit_via_node_chain(&blk)
123
+ n = node_chain.reverse.detect(&blk)
124
+ n ? blk.call(n) : nil
125
+ end
126
+
127
+ end
128
+
129
+
130
+
131
+
132
+
@@ -0,0 +1,180 @@
1
+
2
+ class ActionTree::Basic::Node
3
+
4
+ include ::ActionTree::DialectHelper
5
+
6
+ attr_reader :token, :children, :helper_scope,
7
+ :action_namespaces, :before_hooks,
8
+ :after_hooks, :postprocessors
9
+ attr_accessor :not_found_handler
10
+
11
+
12
+ def initialize(path=[], &blk)
13
+ path = parse_path(path)
14
+ @token = path.shift
15
+ @children = Set.new
16
+ @helper_scope = Module.new
17
+ @before_hooks = []
18
+ @after_hooks = []
19
+ @postprocessors = []
20
+ @action_namespaces = {}
21
+ @not_found_handler = nil
22
+ route(path, &blk) if block_given?
23
+ end
24
+
25
+ # INSPECTION
26
+
27
+ def inspect
28
+ "#<#{self.class}:#{self.object_id.to_s(16)} #{display_name} >"
29
+ end
30
+
31
+ def display_name
32
+ case @token
33
+ when String then @token
34
+ when Symbol then @token.inspect
35
+ when Regexp then @token.source
36
+ when nil then '(nil)'
37
+ end + ' ' + [
38
+ @actions.size.to_s,
39
+ @before_hooks.size,
40
+ @after_hooks.size
41
+ ].join(', ')
42
+ end
43
+
44
+ def printout(stack='')
45
+ stack + display_name + "\n" +
46
+ @children.map {|c| c.printout(stack + ' ') }.join
47
+ end
48
+
49
+ # MATCHING
50
+
51
+ def regexp
52
+ @regexp ||=
53
+ Regexp.new('^' + case @token
54
+ when Regexp then "(#{@token.source})"
55
+ when Symbol then '(.+)'
56
+ when String then @token.gsub(/:\w+/, '(.+)')
57
+ #when nil then '\/*'
58
+ end + '$')
59
+ end
60
+
61
+ def match?(path_fragment)
62
+ !!path_fragment.match(regexp)
63
+ end
64
+
65
+ def capture_names
66
+ case @token
67
+ when Regexp then ['match']
68
+ when Symbol then [@token.to_s]
69
+ when String
70
+ @token.scan(/:\w+/).map {|m| m[1..-1] }
71
+ end
72
+ end
73
+
74
+
75
+ # DSL
76
+
77
+ def route(location=nil, &blk)
78
+ descend(location).instance_eval(&blk) if blk
79
+ self
80
+ end
81
+ alias :with :route
82
+ alias :w :route
83
+ alias :r :route
84
+ alias :_ :route
85
+ def apply(layer); route(&layer); end
86
+
87
+ def action(location=nil, namespace=:default, &blk)
88
+ descend(location).action_namespaces[namespace] = blk
89
+ self
90
+ end
91
+ alias :a :action
92
+ alias :o :action
93
+ def get( loc=nil, &blk); action(loc, :get, &blk); end
94
+ def put( loc=nil, &blk); action(loc, :put, &blk); end
95
+ def post( loc=nil, &blk); action(loc, :post, &blk); end
96
+ def delete(loc=nil, &blk); action(loc, :delete, &blk); end
97
+
98
+ def helpers(location=nil, &blk)
99
+ descend(location).helper_scope.module_eval(&blk)
100
+ end
101
+
102
+ def before(location=nil, &blk)
103
+ descend(location).before_hooks << blk if blk
104
+ end
105
+ alias :b :before
106
+
107
+ def after(location=nil, &blk)
108
+ descend(location).after_hooks << blk if blk
109
+ end
110
+
111
+ def not_found(location=nil, &blk)
112
+ descend(location).not_found_handler = blk if blk
113
+ end
114
+ alias :x :not_found
115
+
116
+ def postprocessor(location=nil, &blk)
117
+ descend(location).postprocessors << blk if blk
118
+ end
119
+ alias :p :postprocessor
120
+
121
+ def mount(node, location=nil)
122
+ if node.token
123
+ descend(location).children < node
124
+ else
125
+ raise 'root nodes can not be mounted. apply a layer instead.'
126
+ end
127
+ end
128
+
129
+
130
+ # LOOKUPS
131
+
132
+ def match(path=[])
133
+ dialect::Match.new(self, nil, nil).match(path)
134
+ end
135
+
136
+ def descend(path)
137
+ loc = parse_path(path)
138
+ if loc.empty? then self else
139
+ token = loc.shift
140
+ (get_child(token) || make_child(token)).descend(loc)
141
+ end
142
+ end
143
+
144
+
145
+ private
146
+
147
+ def get_child(token)
148
+ @children.each do |child|
149
+ return child if child.token == token
150
+ end; nil
151
+ end
152
+
153
+ def make_child(token)
154
+ child = self.class.new(token)
155
+ @children << child
156
+ child
157
+ end
158
+
159
+ # convert any valid path syntax to array syntax
160
+ def parse_path(path)
161
+ case path
162
+ when nil then []
163
+ when Array then path
164
+ when Regexp, Symbol then [path]
165
+ when String then parse_string_path(path)
166
+ else
167
+ raise "invalid path #{path}"
168
+ end
169
+ end
170
+
171
+ # convert a string to array path syntax
172
+ def parse_string_path(path)
173
+ path.gsub(/(^\/)|(\/$)/, ''). # remove trailing slashes
174
+ split('/').map do |str| # split tokens
175
+ str.match(/^:\w+$/) ? # replace ':c' with :c
176
+ str[1..-1].to_sym : str
177
+ end
178
+ end
179
+ end
180
+
@@ -0,0 +1,19 @@
1
+
2
+
3
+ class ActionTree::CaptureHash < Hash
4
+ def merge!(hsh)
5
+ hsh.each {|k, v| add(k,v) }
6
+ end
7
+
8
+ def merge(hsh)
9
+ dup.merge!(hsh)
10
+ end
11
+
12
+ def add(key, value)
13
+ case self[key]
14
+ when nil then self[key] = value
15
+ when Array then self[key] << value
16
+ else self[key] = [self[key], value]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module ActionTree::DialectHelper
3
+
4
+ # access to the dialect namespace
5
+ def dialect
6
+ modules = self.class.name.split('::')
7
+ modules.pop
8
+ modules.inject(Kernel) do |current, next_const|
9
+ current.const_get(next_const)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+
2
+ # The scope in which matched actions are run. Used to copy instance variables, define instance variables from captures, and extend helper method modules.
3
+
4
+ class ActionTree::EvalScope
5
+ def initialize(*ingredients)
6
+ ingredients.each do |ing|
7
+ case ing
8
+ when Hash
9
+ ing.each do |name, value|
10
+ instance_variable_set(:"@#{name}", value)
11
+ end
12
+ when Module
13
+ extend ing
14
+ else
15
+ ing.instance_variables.each do |name|
16
+ self.instance_variable_set(name,
17
+ ing.instance_variable_get(name)
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+
2
+
3
+
4
+ require 'tilt'
5
+
6
+ module ActionTree::Plugins::Tilt
7
+
8
+ CACHE = Tilt::Cache.new
9
+
10
+ module NodeMixin
11
+ # sets a default, optionally namespaced, layout for this and
12
+ # descending nodes
13
+ def layout(path, namespace=nil)
14
+ layout_namespaces[namespace] = path
15
+ end
16
+
17
+ def layout_namespaces
18
+ @layout_namespaces ||= {}
19
+ end
20
+
21
+ # sets a default view folder path for this and descending nodes.
22
+ def view_path(path=nil)
23
+ path ? @view_path = path : @view_path
24
+ end
25
+ end
26
+
27
+ module MatchMixin
28
+ def view_path
29
+ inherit_via_node_chain(&:view_path) || '.'
30
+ end
31
+
32
+ def layout(namespace=nil)
33
+ inherit_via_node_chain do |node|
34
+ node.layout_namespaces[namespace]
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ module Helpers
41
+
42
+ def render(path, layout_namespace=nil, &blk)
43
+ path = ::File.join(@__match.view_path, path)
44
+
45
+ template = ::ActionTree::Plugins::Tilt::CACHE.fetch(
46
+ path) { Tilt.new(path, nil, {}) }
47
+
48
+ if respond_to?(:content_type)
49
+ content_type(template.class.default_mime_type)
50
+ end
51
+
52
+ result = template.render(self, &blk)
53
+
54
+ layout = @__match.layout(layout_namespace)
55
+ if layout && !%w{sass scss less coffee}.include?(File.extname(path))
56
+ render(layout, :AbSoLuTeLy_No_LaYoUt) { result }
57
+ else
58
+ result
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+