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/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, #values_at] hash used to seed new Action instance
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 Current.actions during the render operation so even in nested calls one can still access all other Action instances.
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
- binding.eval('
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
- content_type, body = send(Innate.options.action.wish[wish] || :as_html)
90
- Current.response['Content-Type'] ||= content_type
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
- body
99
+ body
100
+ end
93
101
  end
94
102
 
95
103
  # @return [Array] Content-Type and rendered action
data/lib/innate/cache.rb CHANGED
@@ -92,8 +92,8 @@ module Innate
92
92
  self.send("#{key}=", cache)
93
93
  end
94
94
 
95
- def self.add(name)
96
- register(new(name))
95
+ def self.add(*names)
96
+ names.each{|name| register(new(name)) }
97
97
  end
98
98
 
99
99
  def clear
@@ -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
- # Treat all arguments as Strings.
33
- #
34
- # +hostname+ the hostname of the machine.
35
- # +username+ user executing this process.
36
- # +appname+ identifier for the application being executed.
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
 
@@ -9,11 +9,11 @@ module Innate
9
9
 
10
10
  # Delegate the call to the current Rack::URLMap instance.
11
11
  #
12
- # NOTE: Currently Rack::URLMap will destructively modify PATH_INFO and
13
- # SCRIPT_NAME, which leads to incorrect routing as parts of the
14
- # PATH_INFO are cut out if they matched once.
15
- # Here I repair this damage and hope that my patch to rack will be
16
- # accepted.
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
- module_function
37
-
38
- # Maps the given +object+ or +block+ to +location+, +object+ must respond to
39
- # #call in order to be of any use.
40
- #
41
- # Usage with passed +object+:
42
- #
43
- # Innate.map('/', lambda{|env| [200, {}, "Hello, World"] })
44
- # Innate.at('/').call({}) # => [200, {}, "Hello, World"]
45
- #
46
- # Usage with passed +block+:
47
- #
48
- # Innate.map('/'){|env| [200, {}, ['Hello, World!']] }
49
- # Innate.at('/').call({})
50
- def map(location, object = nil, &block)
51
- DynaMap.map(location, object || block)
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
- # Answer with object at +location+.
55
- #
56
- # Usage:
57
- #
58
- # class Hello
59
- # include Innate::Node
60
- # map '/'
61
- # end
62
- #
63
- # Innate.at('/') # => Hello
64
- def at(location)
65
- DynaMap::MAP[location.to_s]
66
- end
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
- # Returns one of the paths the given +object+ is mapped to.
69
- #
70
- # Usage:
71
- #
72
- # class Hello
73
- # include Innate::Node
74
- # map '/'
75
- # end
76
- #
77
- # Innate.to(Hello) # => '/'
78
- def to(object)
79
- DynaMap::MAP.invert[object]
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
@@ -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
@@ -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
- # * Doesn't work for absolute paths, but there are no specs for that yet.
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.view = action.node.find_view(basename, ext)
71
- action.method = action.node.find_method(basename, action.params)
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
- action.variables.merge!(variables)
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 every
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
- # nodes.
18
+ # {Node}s.
19
19
  #
20
- # Upon inclusion, it will also include Innate::Trinity and Innate::Helper to
21
- # provide you with request/response objects, a session and all the standard
22
- # helper methods as well as the ability to simply add other helpers.
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
- # Usage:
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
- trait[:provide] ||= {}
131
- formats.each{|pr, as| trait[:provide][pr.to_s] = as.to_s }
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 action_not_found
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) : action_not_found(path)
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 Innate::Node
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 action_not_found(path)
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
- def action_not_found(path)
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("Action not found at: %p" % path)
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
- :node => self, :params => params, :wish => wish, :method => method,
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
- # TODO: allow layouts combined of method and view... hairy :)
294
+ # @todo allow layouts combined of method and view... hairy :)
271
295
  def find_layout(name, wish)
272
- return unless found_layout = layout
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(found_layout, wish)
299
+ if found = to_layout(layout, wish)
275
300
  [:layout, found]
276
- elsif found = find_view(found_layout, wish)
301
+ elsif found = find_view(layout, wish)
277
302
  [:view, found]
278
- elsif found = find_method(found_layout, [])
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
- # NOTE: Once 1.9 is mainstream we can use Method#parameters to do accurate
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 and set the @method_arities Hash, keys are method names,
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
- # NOTE:
326
- # * This will be executed once for every request, once we have settled
327
- # things down a bit more we can switch to update based on Reloader
328
- # hooks and update once on startup.
329
- # However, that may cause problems with dynamically created methods, so
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
- # Example:
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 = trait[:method_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
- def alias_view(to, from)
367
- (trait[:alias_view] ||= {})[to] = from
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], *provide.keys].flatten.compact.uniq.join(',')
381
- found = Dir["#{path}.{#{wish}.,#{wish},}{#{exts},}"].uniq
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
- template = found.first
388
- ancestral_trait[:alias_view][template] || template
467
+ found.first
389
468
  end
390
469
 
391
- # Set the +name+ of the layout you want, this takes only the basename
392
- # without any filename-extension or directory.
393
- def layout(name = nil)
394
- name ? trait(:layout => name) : ancestral_trait[:layout]
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
- # Samples:
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