manveru-innate 2009.02.06 → 2009.02.21
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +715 -0
- data/MANIFEST +3 -0
- data/Rakefile +120 -10
- data/example/app/retro_games.rb +29 -26
- data/innate.gemspec +4 -1
- data/lib/innate.rb +183 -131
- data/lib/innate/action.rb +21 -13
- data/lib/innate/cache.rb +2 -2
- data/lib/innate/cache/api.rb +28 -6
- data/lib/innate/dynamap.rb +47 -47
- data/lib/innate/helper/aspect.rb +4 -2
- data/lib/innate/helper/link.rb +8 -1
- data/lib/innate/helper/partial.rb +17 -15
- data/lib/innate/node.rb +203 -74
- data/lib/innate/options.rb +27 -2
- data/lib/innate/options/dsl.rb +16 -9
- data/lib/innate/request.rb +40 -5
- data/lib/innate/route.rb +7 -5
- data/lib/innate/session.rb +2 -1
- data/lib/innate/spec.rb +1 -1
- data/lib/innate/state/fiber.rb +3 -4
- data/lib/innate/traited.rb +7 -2
- data/lib/innate/version.rb +1 -1
- data/lib/innate/view.rb +1 -1
- data/lib/rack/middleware_compiler.rb +13 -3
- data/spec/innate/action/layout.rb +104 -0
- data/spec/innate/action/layout/file_layout.erb +1 -0
- data/spec/innate/cache/common.rb +4 -2
- data/spec/innate/helper.rb +20 -0
- data/spec/innate/helper/aspect.rb +4 -7
- data/spec/innate/helper/flash.rb +0 -11
- data/spec/innate/helper/link.rb +6 -0
- data/spec/innate/helper/partial.rb +18 -7
- data/spec/innate/node.rb +54 -22
- data/spec/innate/node/another_layout/another_layout.erb +3 -0
- data/spec/innate/options.rb +11 -8
- data/spec/innate/route.rb +8 -0
- data/spec/innate/session.rb +12 -16
- metadata +6 -2
data/lib/innate/action.rb
CHANGED
@@ -3,16 +3,20 @@ module Innate
|
|
3
3
|
:wish, :options, :variables, :value, :view_value, :name ]
|
4
4
|
|
5
5
|
class Action < Struct.new(*ACTION_MEMBERS)
|
6
|
+
DEFAULT = {:options => {}, :variables => {}, :params => []}
|
7
|
+
|
6
8
|
# Create a new Action instance.
|
7
9
|
#
|
8
|
-
# @param [Hash, #
|
10
|
+
# @param [Hash, #to_hash] hash used to seed new Action instance
|
9
11
|
# @return [Action] action with the given defaults from hash
|
10
12
|
# @author manveru
|
11
13
|
def self.create(hash = {})
|
12
|
-
new(*hash.values_at(*ACTION_MEMBERS))
|
14
|
+
new(*DEFAULT.merge(hash.to_hash).values_at(*ACTION_MEMBERS))
|
13
15
|
end
|
14
16
|
|
15
|
-
# Call the Action instance, will insert itself temporarily into
|
17
|
+
# Call the Action instance, will insert itself temporarily into
|
18
|
+
# Current.actions during the render operation so even in nested calls one
|
19
|
+
# can still access all other Action instances.
|
16
20
|
# Will initialize the assigned node and call Action#render
|
17
21
|
#
|
18
22
|
# @return [String] The rendition of all nested calls
|
@@ -20,8 +24,6 @@ module Innate
|
|
20
24
|
# @author manveru
|
21
25
|
def call
|
22
26
|
Current.actions << self
|
23
|
-
self.instance = node.new
|
24
|
-
self.variables[:content] ||= nil
|
25
27
|
render
|
26
28
|
ensure
|
27
29
|
Current.actions.delete(self)
|
@@ -54,6 +56,11 @@ module Innate
|
|
54
56
|
from_action
|
55
57
|
end
|
56
58
|
|
59
|
+
COPY_VARIABLES = '
|
60
|
+
STATE[:action_variables].each do |iv, value|
|
61
|
+
instance_variable_set("@#{iv}", value)
|
62
|
+
end'.strip.freeze
|
63
|
+
|
57
64
|
# Copy Action#variables as instance variables into the given binding.
|
58
65
|
#
|
59
66
|
# This relies on Innate::STATE, so should be thread-safe and doesn't depend
|
@@ -71,25 +78,26 @@ module Innate
|
|
71
78
|
STATE.sync do
|
72
79
|
STATE[:action_variables] = self.variables
|
73
80
|
|
74
|
-
|
75
|
-
STATE[:action_variables].each do |iv, value|
|
76
|
-
instance_variable_set("@#{iv}", value)
|
77
|
-
end')
|
81
|
+
eval(COPY_VARIABLES, binding)
|
78
82
|
|
79
83
|
STATE[:action_variables] = nil
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
83
87
|
def render
|
88
|
+
self.instance = node.new
|
89
|
+
self.variables[:content] ||= nil
|
90
|
+
|
84
91
|
instance.wrap_action_call(self) do
|
85
92
|
self.value = instance.__send__(method, *params) if method
|
86
93
|
self.view_value = File.read(view) if view
|
87
|
-
end
|
88
94
|
|
89
|
-
|
90
|
-
|
95
|
+
content_type, body = send(Innate.options.action.wish[wish] || :as_html)
|
96
|
+
response = Current.response
|
97
|
+
response['Content-Type'] ||= content_type if response
|
91
98
|
|
92
|
-
|
99
|
+
body
|
100
|
+
end
|
93
101
|
end
|
94
102
|
|
95
103
|
# @return [Array] Content-Type and rendered action
|
data/lib/innate/cache.rb
CHANGED
data/lib/innate/cache/api.rb
CHANGED
@@ -29,17 +29,18 @@ module Innate
|
|
29
29
|
# Some parameters identifying the current process will be passed so
|
30
30
|
# caches that act in one global name-space can use them as a prefix.
|
31
31
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# +cachename+ name-space of the cache, like 'session' or 'action'
|
32
|
+
# @param [String #to_s] hostname the hostname of the machine
|
33
|
+
# @param [String #to_s] username user executing the process
|
34
|
+
# @param [String #to_s] appname identifier for the application
|
35
|
+
# @param [String #to_s] cachename namespace, like 'session' or 'action'
|
36
|
+
# @author manveru
|
38
37
|
def cache_setup(hostname, username, appname, cachename)
|
39
38
|
end
|
40
39
|
|
41
40
|
# Remove all key/value pairs from the cache.
|
42
41
|
# Should behave as if #delete had been called with all +keys+ as argument.
|
42
|
+
#
|
43
|
+
# @author manveru
|
43
44
|
def cache_clear
|
44
45
|
clear
|
45
46
|
end
|
@@ -49,6 +50,16 @@ module Innate
|
|
49
50
|
#
|
50
51
|
# If only one key was deleted, answer with the corresponding value.
|
51
52
|
# If multiple keys were deleted, answer with an Array containing the values.
|
53
|
+
#
|
54
|
+
# NOTE: Due to differences in the underlying implementation in the
|
55
|
+
# caches, some may not return the deleted value as it would mean
|
56
|
+
# another lookup before deletion. This is the case for caches on
|
57
|
+
# memcached or any database system.
|
58
|
+
#
|
59
|
+
# @param [Object] key the key for the value to delete
|
60
|
+
# @param [Object] keys any other keys to delete as well
|
61
|
+
# @return [Object Array nil]
|
62
|
+
# @author manveru
|
52
63
|
def cache_delete(key, *keys)
|
53
64
|
if keys.empty?
|
54
65
|
if value = yield(key)
|
@@ -61,6 +72,12 @@ module Innate
|
|
61
72
|
|
62
73
|
# Answer with the value associated with the +key+, +nil+ if not found or
|
63
74
|
# expired.
|
75
|
+
#
|
76
|
+
# @param [Object] key the key for which to fetch the value
|
77
|
+
# @param [Object] default will return this if no value was found
|
78
|
+
# @return [Object]
|
79
|
+
# @see Innate::Cache#fetch Innate::Cache#[]
|
80
|
+
# @author manveru
|
64
81
|
def cache_fetch(key, default = nil)
|
65
82
|
value = default
|
66
83
|
|
@@ -91,6 +108,11 @@ module Innate
|
|
91
108
|
# sleep 21
|
92
109
|
# Cache.value.fetch(:num) # => nil
|
93
110
|
#
|
111
|
+
# @param [Object] key the value is stored with this key
|
112
|
+
# @param [Object] value the key points to this value
|
113
|
+
# @param [Hash] options for now, only :ttl => Fixnum is used.
|
114
|
+
# @see Innate::Cache#store Innate::Cache#[]=
|
115
|
+
# @author manveru
|
94
116
|
def cache_store(key, value, options = {})
|
95
117
|
ttl = options[:ttl]
|
96
118
|
|
data/lib/innate/dynamap.rb
CHANGED
@@ -9,11 +9,11 @@ module Innate
|
|
9
9
|
|
10
10
|
# Delegate the call to the current Rack::URLMap instance.
|
11
11
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# @note Currently Rack::URLMap will destructively modify PATH_INFO and
|
13
|
+
# SCRIPT_NAME, which leads to incorrect routing as parts of the PATH_INFO
|
14
|
+
# are cut out if they matched once. Here I repair this damage and hope
|
15
|
+
# that my patch to rack will be accepted.
|
16
|
+
# Update: patch was accepted, will remove it on next rack release
|
17
17
|
def self.call(env)
|
18
18
|
if app = CACHE[:map]
|
19
19
|
script_name, path_info = env['SCRIPT_NAME'], env['PATH_INFO']
|
@@ -33,49 +33,49 @@ module Innate
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
36
|
+
module SingletonMethods
|
37
|
+
# Maps the given +object+ or +block+ to +location+, +object+ must respond to
|
38
|
+
# #call in order to be of any use.
|
39
|
+
#
|
40
|
+
# @example with passed +object+
|
41
|
+
#
|
42
|
+
# Innate.map('/', lambda{|env| [200, {}, "Hello, World"] })
|
43
|
+
# Innate.at('/').call({}) # => [200, {}, "Hello, World"]
|
44
|
+
#
|
45
|
+
# @example with passed +block+
|
46
|
+
#
|
47
|
+
# Innate.map('/'){|env| [200, {}, ['Hello, World!']] }
|
48
|
+
# Innate.at('/').call({})
|
49
|
+
def map(location, object = nil, &block)
|
50
|
+
DynaMap.map(location, object || block)
|
51
|
+
end
|
53
52
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
53
|
+
# Answer with object at +location+.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
#
|
57
|
+
# class Hello
|
58
|
+
# include Innate::Node
|
59
|
+
# map '/'
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Innate.at('/') # => Hello
|
63
|
+
def at(location)
|
64
|
+
DynaMap::MAP[location.to_s]
|
65
|
+
end
|
67
66
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
67
|
+
# Returns one of the paths the given +object+ is mapped to.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
#
|
71
|
+
# class Hello
|
72
|
+
# include Innate::Node
|
73
|
+
# map '/'
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# Innate.to(Hello) # => '/'
|
77
|
+
def to(object)
|
78
|
+
DynaMap::MAP.invert[object]
|
79
|
+
end
|
80
80
|
end
|
81
81
|
end
|
data/lib/innate/helper/aspect.rb
CHANGED
@@ -27,12 +27,14 @@ module Innate
|
|
27
27
|
|
28
28
|
def aspect_wrap(action)
|
29
29
|
return yield unless method = action.name
|
30
|
-
|
30
|
+
|
31
31
|
aspect_call(:before_all, method)
|
32
32
|
aspect_call(:before, method)
|
33
|
-
yield
|
33
|
+
result = yield
|
34
34
|
aspect_call(:after, method)
|
35
35
|
aspect_call(:after_all, method)
|
36
|
+
|
37
|
+
result
|
36
38
|
end
|
37
39
|
|
38
40
|
module SingletonMethods
|
data/lib/innate/helper/link.rb
CHANGED
@@ -21,13 +21,16 @@ module Innate
|
|
21
21
|
# Users.r(:foo, :bar) # => URI('/users/foo/bar')
|
22
22
|
# Users.r(:foo, :a => :b) # => URI('/users/foo?a=b')
|
23
23
|
# Users.r(:foo, :bar, :a => :b) # => URI('/users/foo/bar?a=b')
|
24
|
+
#
|
25
|
+
# @return [URI] to the location
|
24
26
|
def route(name = '/', *args)
|
25
27
|
hash = {}
|
26
28
|
hashes, names = args.partition{|arg| arg.respond_to?(:merge!) }
|
27
29
|
hashes.each{|to_merge| hash.merge!(to_merge) }
|
28
30
|
|
31
|
+
prefix = Innate.options.app.prefix
|
29
32
|
location = Innate.to(self) || Innate.to(self.class)
|
30
|
-
front = Array[location, name, *names].join('/').squeeze('/')
|
33
|
+
front = Array[prefix, location, name, *names].join('/').squeeze('/')
|
31
34
|
|
32
35
|
if hash.empty?
|
33
36
|
URI(front)
|
@@ -39,11 +42,15 @@ module Innate
|
|
39
42
|
end
|
40
43
|
alias r route
|
41
44
|
|
45
|
+
# Create a link tag
|
46
|
+
#
|
42
47
|
# Usage, given Wiki is mapped to `/wiki`:
|
43
48
|
# Wiki.a(:home) # => '<a href="/wiki/home">home</a>'
|
44
49
|
# Wiki.a('home', :home) # => '<a href="/wiki/home">home</a>'
|
45
50
|
# Wiki.a('home', :/) # => '<a href="/wiki/">home</a>'
|
46
51
|
# Wiki.a('foo', :/, :foo => :bar) # => '<a href="/wiki/?foo=bar">foo</a>'
|
52
|
+
#
|
53
|
+
# @return [String]
|
47
54
|
def anchor(text, *args)
|
48
55
|
href = args.empty? ? r(text) : r(*args)
|
49
56
|
text = Rack::Utils.escape_html(text)
|
@@ -55,30 +55,32 @@ module Innate
|
|
55
55
|
# current controller.
|
56
56
|
#
|
57
57
|
# TODO:
|
58
|
+
# * Doesn't work for absolute paths, but there are no specs for that yet.
|
58
59
|
# * the local variable hack isn't working because innate allocates a new
|
59
|
-
# binding
|
60
|
+
# binding.
|
60
61
|
# For now one can simply use instance variables, which I prefer anyway.
|
61
|
-
#
|
62
|
+
#
|
63
|
+
# the local binding hack:
|
64
|
+
#
|
65
|
+
# variables.each do |key, value|
|
66
|
+
# value = "ObjectSpace._id2ref(#{value.object_id})"
|
67
|
+
# eval "#{key} = #{value}", action.binding
|
68
|
+
# end
|
69
|
+
|
62
70
|
def render_template(path, variables = {})
|
63
71
|
path = path.to_s
|
64
72
|
|
65
73
|
ext = File.extname(path)
|
66
74
|
basename = File.basename(path, ext)
|
67
|
-
ext = ext[1..-1]
|
68
|
-
|
75
|
+
ext = ext[1..-1] || action.node.provide[action.wish].to_s
|
76
|
+
|
69
77
|
action = Innate::Current.action.dup
|
70
|
-
action.
|
71
|
-
action.
|
78
|
+
action.layout = nil
|
79
|
+
action.view = action.node.find_view(basename, ext)
|
80
|
+
action.method = action.node.find_method(basename, action.params)
|
81
|
+
action.variables = action.variables.merge(variables)
|
72
82
|
action.sync_variables(action)
|
73
|
-
|
74
|
-
|
75
|
-
=begin
|
76
|
-
variables.each do |key, value|
|
77
|
-
value = "ObjectSpace._id2ref(#{value.object_id})"
|
78
|
-
eval "#{key} = #{value}", action.binding
|
79
|
-
end
|
80
|
-
=end
|
81
|
-
|
83
|
+
|
82
84
|
action.call
|
83
85
|
end
|
84
86
|
|
data/lib/innate/node.rb
CHANGED
@@ -1,33 +1,34 @@
|
|
1
1
|
module Innate
|
2
2
|
|
3
|
-
# The nervous system of Innate, so you can relax.
|
3
|
+
# The nervous system of {Innate}, so you can relax.
|
4
4
|
#
|
5
5
|
# Node may be included into any class to make it a valid responder to
|
6
6
|
# requests.
|
7
7
|
#
|
8
|
-
# The major difference between this and the Ramaze controller is that
|
9
|
-
# Node acts as a standalone application with its own dispatcher.
|
8
|
+
# The major difference between this and the old Ramaze controller is that
|
9
|
+
# every Node acts as a standalone application with its own dispatcher.
|
10
10
|
#
|
11
|
-
# What's also an important difference is the fact that Node is a module, so
|
11
|
+
# What's also an important difference is the fact that {Node} is a module, so
|
12
12
|
# we don't have to spend a lot of time designing the perfect subclassing
|
13
13
|
# scheme.
|
14
14
|
#
|
15
15
|
# This makes dispatching more fun, avoids a lot of processing that is done by
|
16
|
-
# Rack anyway and lets you tailor your application down to the last action
|
16
|
+
# {Rack} anyway and lets you tailor your application down to the last action
|
17
17
|
# exactly the way you want without worrying about side-effects to other
|
18
|
-
#
|
18
|
+
# {Node}s.
|
19
19
|
#
|
20
|
-
# Upon inclusion, it will also include Innate::Trinity and Innate::Helper
|
21
|
-
# provide you with
|
22
|
-
#
|
20
|
+
# Upon inclusion, it will also include {Innate::Trinity} and {Innate::Helper}
|
21
|
+
# to provide you with {Innate::Request}, {Innate::Response},
|
22
|
+
# {Innate::Session} instances, and all the standard helper methods as well as
|
23
|
+
# the ability to simply add other helpers.
|
23
24
|
#
|
24
25
|
# NOTE:
|
25
26
|
# * Although I tried to minimize the amount of code in here there is still
|
26
27
|
# quite a number of methods left in order to do ramaze-style lookups.
|
27
28
|
# Those methods, and all other methods occurring in the ancestors after
|
28
|
-
# Innate::Node will not be considered valid action methods and will be
|
29
|
+
# {Innate::Node} will not be considered valid action methods and will be
|
29
30
|
# ignored.
|
30
|
-
# * This also means that method_missing will not see any of the requests
|
31
|
+
# * This also means that {method_missing} will not see any of the requests
|
31
32
|
# coming in.
|
32
33
|
# * If you want an action to act as a catch-all, use `def index(*args)`.
|
33
34
|
|
@@ -59,6 +60,7 @@ module Innate
|
|
59
60
|
Log.debug("Mapped Nodes: %p" % DynaMap::MAP)
|
60
61
|
end
|
61
62
|
|
63
|
+
# @return [String] the relative path to the node
|
62
64
|
def mapping
|
63
65
|
mapped = Innate.to(self)
|
64
66
|
return mapped if mapped
|
@@ -67,7 +69,7 @@ module Innate
|
|
67
69
|
end
|
68
70
|
|
69
71
|
# Shortcut to map or remap this Node
|
70
|
-
|
72
|
+
# @param [#to_s] location
|
71
73
|
def map(location)
|
72
74
|
Innate.map(location, self)
|
73
75
|
end
|
@@ -75,7 +77,7 @@ module Innate
|
|
75
77
|
# This little piece of nasty looking code enables you to provide different
|
76
78
|
# content from a single action.
|
77
79
|
#
|
78
|
-
#
|
80
|
+
# @example
|
79
81
|
#
|
80
82
|
# class Feeds
|
81
83
|
# include Innate::Node
|
@@ -127,8 +129,9 @@ module Innate
|
|
127
129
|
def provide(formats = {})
|
128
130
|
return ancestral_trait[:provide] if formats.empty?
|
129
131
|
|
130
|
-
|
131
|
-
formats.each{|pr, as|
|
132
|
+
hash = {}
|
133
|
+
formats.each{|pr, as| hash[pr.to_s] = Array[*as].map{|a| a.to_s } }
|
134
|
+
trait(:provide => hash)
|
132
135
|
|
133
136
|
ancestral_trait[:provide]
|
134
137
|
end
|
@@ -168,17 +171,20 @@ module Innate
|
|
168
171
|
end
|
169
172
|
|
170
173
|
# Let's try to find some valid action for given +path+.
|
171
|
-
# Otherwise we dispatch to
|
172
|
-
|
174
|
+
# Otherwise we dispatch to action_missing
|
175
|
+
#
|
176
|
+
# @param [String] path from request['REQUEST_PATH']
|
173
177
|
def try_resolve(path)
|
174
178
|
action = resolve(path)
|
175
|
-
action ? action_found(action) :
|
179
|
+
action ? action_found(action) : action_missing(path)
|
176
180
|
end
|
177
181
|
|
178
182
|
# Executed once an Action has been found.
|
179
|
-
# Reset the Response instance, catch :respond and :redirect.
|
180
|
-
# Action#call has to return a String.
|
181
|
-
|
183
|
+
# Reset the {Innate::Response} instance, catch :respond and :redirect.
|
184
|
+
# {Action#call} has to return a String.
|
185
|
+
#
|
186
|
+
# @param [Innate::Action] action
|
187
|
+
# @return [Innate::Response]
|
182
188
|
def action_found(action)
|
183
189
|
result = catch(:respond){ catch(:redirect){ action.call }}
|
184
190
|
|
@@ -197,16 +203,18 @@ module Innate
|
|
197
203
|
# * We are doing this is in order to avoid tons of special error handling
|
198
204
|
# code that would impact runtime and make the overall API more
|
199
205
|
# complicated.
|
200
|
-
# * This cannot be a normal action is that methods defined in
|
201
|
-
# will never be considered for actions.
|
206
|
+
# * This cannot be a normal action is that methods defined in
|
207
|
+
# {Innate::Node} will never be considered for actions.
|
202
208
|
#
|
203
209
|
# To use a normal action with template do following:
|
204
210
|
#
|
211
|
+
# @example
|
212
|
+
#
|
205
213
|
# class Hi
|
206
214
|
# include Innate::Node
|
207
215
|
# map '/'
|
208
216
|
#
|
209
|
-
# def
|
217
|
+
# def self.action_missing(path)
|
210
218
|
# return if path == '/not_found'
|
211
219
|
# # No normal action, runs on bare metal
|
212
220
|
# try_resolve('/not_found')
|
@@ -217,11 +225,13 @@ module Innate
|
|
217
225
|
# "Sorry, I do not exist"
|
218
226
|
# end
|
219
227
|
# end
|
220
|
-
|
221
|
-
|
228
|
+
#
|
229
|
+
# @param [String] path
|
230
|
+
# @see Innate::Response Node::try_resolve
|
231
|
+
def action_missing(path)
|
222
232
|
response.status = 404
|
223
233
|
response['Content-Type'] = 'text/plain'
|
224
|
-
response.write("
|
234
|
+
response.write("No action found at: %p" % path)
|
225
235
|
|
226
236
|
response
|
227
237
|
end
|
@@ -229,13 +239,21 @@ module Innate
|
|
229
239
|
# Let's get down to business, first check if we got any wishes regarding
|
230
240
|
# the representation from the client, otherwise we will assume he wants
|
231
241
|
# html.
|
232
|
-
|
242
|
+
#
|
243
|
+
# @param [String] path
|
244
|
+
# @return [nil Action]
|
245
|
+
# @see Node::find_provide Node::update_method_arities Node::find_action
|
246
|
+
# @author manveru
|
233
247
|
def resolve(path)
|
234
248
|
name, wish = find_provide(path)
|
235
249
|
update_method_arities
|
236
250
|
find_action(name, wish)
|
237
251
|
end
|
238
252
|
|
253
|
+
# @param [String] path
|
254
|
+
# @return [Array] with name and wish
|
255
|
+
# @see Node::provide
|
256
|
+
# @author manveru
|
239
257
|
def find_provide(path)
|
240
258
|
name, wish = path, 'html'
|
241
259
|
|
@@ -251,31 +269,38 @@ module Innate
|
|
251
269
|
# if we can't find either we go to the next pattern, otherwise we answer
|
252
270
|
# with an Action with everything we know so far about the demands of the
|
253
271
|
# client.
|
254
|
-
|
272
|
+
#
|
273
|
+
# @param [String] given_name the name extracted from REQUEST_PATH
|
274
|
+
# @param [String] wish
|
275
|
+
# @author manveru
|
255
276
|
def find_action(given_name, wish)
|
277
|
+
needs_method = Innate.options.action.needs_method
|
278
|
+
|
256
279
|
patterns_for(given_name) do |name, params|
|
257
|
-
view = find_view(name, wish)
|
258
280
|
method = find_method(name, params)
|
281
|
+
view = find_view(name, wish)
|
259
282
|
|
260
283
|
next unless view or method
|
284
|
+
next unless method if needs_method
|
261
285
|
|
262
286
|
layout = find_layout(name, wish)
|
287
|
+
params ||= []
|
263
288
|
|
264
|
-
Action.create(
|
265
|
-
|
266
|
-
:view => view, :options => {}, :variables => {}, :layout => layout)
|
289
|
+
Action.create(:method => method, :params => params, :layout => layout,
|
290
|
+
:node => self, :view => view, :wish => wish)
|
267
291
|
end
|
268
292
|
end
|
269
293
|
|
270
|
-
#
|
294
|
+
# @todo allow layouts combined of method and view... hairy :)
|
271
295
|
def find_layout(name, wish)
|
272
|
-
return unless
|
296
|
+
return unless layout = ancestral_trait[:layout]
|
297
|
+
return unless layout = layout.call(name, wish) if layout.respond_to?(:call)
|
273
298
|
|
274
|
-
if found = to_layout(
|
299
|
+
if found = to_layout(layout, wish)
|
275
300
|
[:layout, found]
|
276
|
-
elsif found = find_view(
|
301
|
+
elsif found = find_view(layout, wish)
|
277
302
|
[:view, found]
|
278
|
-
elsif found = find_method(
|
303
|
+
elsif found = find_method(layout, [])
|
279
304
|
[:method, found]
|
280
305
|
end
|
281
306
|
end
|
@@ -310,31 +335,31 @@ module Innate
|
|
310
335
|
# def index(a = :a, b = :b) # => -1
|
311
336
|
# def index(a = :a, b = :b, *r) # => -1
|
312
337
|
#
|
313
|
-
#
|
338
|
+
# @todo Once 1.9 is mainstream we can use Method#parameters to do accurate
|
314
339
|
# prediction
|
315
340
|
def find_method(name, params)
|
316
341
|
return unless arity = trait[:method_arities][name]
|
317
342
|
name if arity == params.size or arity < 0
|
318
343
|
end
|
319
344
|
|
320
|
-
# Answer with
|
321
|
-
# values are method arities.
|
322
|
-
#
|
323
|
-
# Usually called from Node::resolve
|
345
|
+
# Answer with a hash, keys are method names, values are method arities.
|
324
346
|
#
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
# let's play it safe for now.
|
347
|
+
# Note that this will be executed once for every request, once we have
|
348
|
+
# settled things down a bit more we can switch to update based on Reloader
|
349
|
+
# hooks and update once on startup.
|
350
|
+
# However, that may cause problems with dynamically created methods, so
|
351
|
+
# let's play it safe for now.
|
331
352
|
#
|
332
|
-
#
|
353
|
+
# @example
|
333
354
|
#
|
334
355
|
# Hi.update_method_arities
|
335
356
|
# # => {'index' => 0, 'foo' => -1, 'bar => 2}
|
357
|
+
#
|
358
|
+
# @see Node::resolve
|
359
|
+
# @return [Hash] mapping the name of the methods to their arity
|
336
360
|
def update_method_arities
|
337
|
-
arities =
|
361
|
+
arities = {}
|
362
|
+
trait(:method_arities => arities)
|
338
363
|
|
339
364
|
exposed = ancestors & Helper::EXPOSE.to_a
|
340
365
|
higher = ancestors.select{|a| a < Innate::Node }
|
@@ -351,6 +376,9 @@ module Innate
|
|
351
376
|
# Try to find the best template for the given basename and wish.
|
352
377
|
# Also, having extraordinarily much fun with globs.
|
353
378
|
def find_view(file, wish)
|
379
|
+
aliased = find_aliased_view(file, wish)
|
380
|
+
return aliased if aliased
|
381
|
+
|
354
382
|
path = [Innate.options.app.root, Innate.options.app.view, view_root, file]
|
355
383
|
to_template(path, wish)
|
356
384
|
end
|
@@ -363,38 +391,105 @@ module Innate
|
|
363
391
|
@view_root ||= Innate.to(self)
|
364
392
|
end
|
365
393
|
|
366
|
-
|
367
|
-
|
394
|
+
# Aliasing one view from another.
|
395
|
+
# The aliases are inherited, and the optional third +node+ parameter
|
396
|
+
# indicates the Node to take the view from.
|
397
|
+
#
|
398
|
+
# The argument order is identical with `alias` and `alias_method`, which
|
399
|
+
# quite honestly confuses me, but at least we stay consistent.
|
400
|
+
#
|
401
|
+
# @example
|
402
|
+
# class Foo
|
403
|
+
# include Innate::Node
|
404
|
+
#
|
405
|
+
# # Use the 'foo' view when calling 'bar'
|
406
|
+
# alias_view 'bar', 'foo'
|
407
|
+
#
|
408
|
+
# # Use the 'foo' view from FooBar node when calling 'bar'
|
409
|
+
# alias_view 'bar', 'foo', FooBar
|
410
|
+
# end
|
411
|
+
#
|
412
|
+
# Note that the parameters have been simplified in comparision with
|
413
|
+
# Ramaze::Controller::template where the second parameter may be a
|
414
|
+
# Controller or the name of the template. We take that now as an optional
|
415
|
+
# third parameter.
|
416
|
+
#
|
417
|
+
# @param [#to_s] to view that should be replaced
|
418
|
+
# @param [#to_s] from view to use or Node.
|
419
|
+
# @param [#nil? Node] node optionally obtain view from this Node
|
420
|
+
# @see Node::find_aliased_view
|
421
|
+
# @author manveru
|
422
|
+
def alias_view(to, from, node = nil)
|
423
|
+
trait[:alias_view] || trait(:alias_view => {})
|
424
|
+
trait[:alias_view][to.to_s] = node ? [from.to_s, node] : from.to_s
|
425
|
+
end
|
426
|
+
|
427
|
+
# @param [String] file
|
428
|
+
# @param [String] wish
|
429
|
+
# @return [nil String] the absolute path to the aliased template or nil
|
430
|
+
# @see Node::alias_view Node::find_view
|
431
|
+
# @author manveru
|
432
|
+
def find_aliased_view(file, wish)
|
433
|
+
aliased_file, aliased_node = ancestral_trait[:alias_view][file]
|
434
|
+
aliased_node ||= self
|
435
|
+
aliased_node.find_view(aliased_file, wish) if aliased_file
|
368
436
|
end
|
369
437
|
|
370
438
|
# Find the best matching file for the layout, if any.
|
439
|
+
#
|
440
|
+
# @param [String] file
|
441
|
+
# @param [String] wish
|
442
|
+
# @return [nil String] the absolute path to the template or nil
|
443
|
+
# @see Node::to_template
|
444
|
+
# @author manveru
|
371
445
|
def to_layout(file, wish)
|
372
446
|
path = [Innate.options.app.root, Innate.options.app.layout, file]
|
373
447
|
to_template(path, wish)
|
374
448
|
end
|
375
449
|
|
450
|
+
# @param [String] file
|
451
|
+
# @param [String] wish
|
452
|
+
# @return [nil String] the absolute path to the template or nil
|
453
|
+
# @see Node::find_view Node::to_layout Node::find_aliased_view
|
454
|
+
# @author manveru
|
376
455
|
def to_template(path, wish)
|
377
456
|
return unless path.all?
|
378
457
|
|
379
|
-
path = File.join(*path.map{|pa| pa.to_s })
|
380
|
-
exts = [provide[wish]
|
381
|
-
|
458
|
+
path = File.join(*path.map{|pa| pa.to_s.split('__') }.flatten)
|
459
|
+
exts = (Array[provide[wish]] + provide.keys).flatten.compact.uniq.join(',')
|
460
|
+
glob = "#{path}.{#{wish}.,#{wish},}{#{exts},}"
|
461
|
+
found = Dir[glob].uniq
|
382
462
|
|
383
463
|
if found.size > 1
|
384
464
|
Log.warn("%d views found for %p | %p" % [found.size, path, wish])
|
385
465
|
end
|
386
466
|
|
387
|
-
|
388
|
-
ancestral_trait[:alias_view][template] || template
|
467
|
+
found.first
|
389
468
|
end
|
390
469
|
|
391
|
-
#
|
392
|
-
#
|
393
|
-
|
394
|
-
|
470
|
+
# Define a layout to use on this Node.
|
471
|
+
#
|
472
|
+
# @param [String #to_s] name basename without extension of the layout to use
|
473
|
+
# @param [Proc #call] block called on every dispatch if no name given
|
474
|
+
# @return [Proc String] The assigned name or block
|
475
|
+
#
|
476
|
+
# @note The behaviour of Node#layout changed significantly from Ramaze,
|
477
|
+
# instead of multitudes of obscure options and methods like deny_layout
|
478
|
+
# we simply take a block and use the returned value as the name for the
|
479
|
+
# layout. No layout will be used if the block returns nil.
|
480
|
+
def layout(name = nil, &block)
|
481
|
+
if name and block
|
482
|
+
trait(:layout => lambda{|n, w| name if block.call(n, w) })
|
483
|
+
elsif name
|
484
|
+
trait(:layout => name.to_s)
|
485
|
+
elsif block
|
486
|
+
trait(:layout => block)
|
487
|
+
end
|
488
|
+
|
489
|
+
return ancestral_trait[:layout]
|
395
490
|
end
|
396
491
|
|
397
|
-
# The innate beauty in Nitro, Ramaze, and Innate.
|
492
|
+
# The innate beauty in Nitro, Ramaze, and {Innate}.
|
398
493
|
#
|
399
494
|
# Will yield the name of the action and parameter for the action method in
|
400
495
|
# order of significance.
|
@@ -407,23 +502,22 @@ module Innate
|
|
407
502
|
# The last fallback will always be the index action with all of the path
|
408
503
|
# turned into parameters.
|
409
504
|
#
|
410
|
-
#
|
411
|
-
#
|
505
|
+
# @usage
|
412
506
|
# class Foo; include Innate::Node; map '/'; end
|
413
507
|
#
|
414
508
|
# Foo.patterns_for('/'){|action, params| p action => params }
|
415
|
-
# {"index"=>[]}
|
509
|
+
# # => {"index"=>[]}
|
416
510
|
#
|
417
511
|
# Foo.patterns_for('/foo/bar'){|action, params| p action => params }
|
418
|
-
# {"foo__bar"=>[]}
|
419
|
-
# {"foo"=>["bar"]}
|
420
|
-
# {"index"=>["foo", "bar"]}
|
512
|
+
# # => {"foo__bar"=>[]}
|
513
|
+
# # => {"foo"=>["bar"]}
|
514
|
+
# # => {"index"=>["foo", "bar"]}
|
421
515
|
#
|
422
516
|
# Foo.patterns_for('/foo/bar/baz'){|action, params| p action => params }
|
423
|
-
# {"foo__bar__baz"=>[]}
|
424
|
-
# {"foo__bar"=>["baz"]}
|
425
|
-
# {"foo"=>["bar", "baz"]}
|
426
|
-
# {"index"=>["foo", "bar", "baz"]}
|
517
|
+
# # => {"foo__bar__baz"=>[]}
|
518
|
+
# # => {"foo__bar"=>["baz"]}
|
519
|
+
# # => {"foo"=>["bar", "baz"]}
|
520
|
+
# # => {"index"=>["foo", "bar", "baz"]}
|
427
521
|
def patterns_for(path)
|
428
522
|
atoms = path.split('/')
|
429
523
|
atoms.delete('')
|
@@ -452,7 +546,6 @@ module Innate
|
|
452
546
|
# @param [Proc] block contains the instructions to call the action method if any
|
453
547
|
# @see Action#render
|
454
548
|
# @author manveru
|
455
|
-
|
456
549
|
def wrap_action_call(action, &block)
|
457
550
|
wrap = ancestral_trait[:wrap]
|
458
551
|
|
@@ -468,4 +561,40 @@ module Innate
|
|
468
561
|
# @author manveru
|
469
562
|
def binding; super; end
|
470
563
|
end
|
564
|
+
|
565
|
+
module SingletonMethods
|
566
|
+
# Convenience method to include the Node module into +node+ and map to a
|
567
|
+
# +location+.
|
568
|
+
#
|
569
|
+
# @param [#to_s] location where the node is mapped to
|
570
|
+
# @param [Node nil] node the class that will be a node, will try to look it
|
571
|
+
# up if not given
|
572
|
+
# @return [Class] the node argument or detected class will be returned
|
573
|
+
# @see Innate::node_from_backtrace
|
574
|
+
# @author manveru
|
575
|
+
def node(location, node = nil)
|
576
|
+
node ||= node_from_backtrace(caller)
|
577
|
+
node.__send__(:include, Node)
|
578
|
+
node.map(location)
|
579
|
+
node
|
580
|
+
end
|
581
|
+
|
582
|
+
# Cheap hack that works reasonably well to avoid passing self all the time
|
583
|
+
# to Innate::node
|
584
|
+
# We simply search the file that Innate::node was called in for the first
|
585
|
+
# class definition above the line that Innate::node was called and look up
|
586
|
+
# the constant.
|
587
|
+
# If there are any problems with this (filenames containing ':' or
|
588
|
+
# metaprogramming) just pass the node parameter explicitly to Innate::node
|
589
|
+
#
|
590
|
+
# @param [Array #[]] backtrace
|
591
|
+
# @see Innate::node
|
592
|
+
# @author manveru
|
593
|
+
def node_from_backtrace(backtrace)
|
594
|
+
file, line = backtrace[0].split(':', 2)
|
595
|
+
line = line.to_i
|
596
|
+
File.readlines(file)[0..line].reverse.find{|line| line =~ /^\s*class\s+(\S+)/ }
|
597
|
+
const_get($1)
|
598
|
+
end
|
599
|
+
end
|
471
600
|
end
|