manveru-innate 2009.02.06

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.
Files changed (101) hide show
  1. data/CHANGELOG +1409 -0
  2. data/COPYING +18 -0
  3. data/MANIFEST +100 -0
  4. data/README.md +485 -0
  5. data/Rakefile +139 -0
  6. data/example/app/retro_games.rb +57 -0
  7. data/example/app/whywiki_erb/layout/wiki.html.erb +15 -0
  8. data/example/app/whywiki_erb/spec/wiki.rb +19 -0
  9. data/example/app/whywiki_erb/start.rb +45 -0
  10. data/example/app/whywiki_erb/view/edit.html.erb +6 -0
  11. data/example/app/whywiki_erb/view/index.html.erb +10 -0
  12. data/example/custom_middleware.rb +43 -0
  13. data/example/error_handling.rb +31 -0
  14. data/example/hello.rb +12 -0
  15. data/example/howto_spec.rb +60 -0
  16. data/example/link.rb +35 -0
  17. data/example/providing_hash.rb +46 -0
  18. data/example/session.rb +42 -0
  19. data/innate.gemspec +118 -0
  20. data/lib/innate.rb +191 -0
  21. data/lib/innate/action.rb +156 -0
  22. data/lib/innate/adapter.rb +89 -0
  23. data/lib/innate/cache.rb +117 -0
  24. data/lib/innate/cache/api.rb +106 -0
  25. data/lib/innate/cache/drb.rb +58 -0
  26. data/lib/innate/cache/file_based.rb +39 -0
  27. data/lib/innate/cache/marshal.rb +17 -0
  28. data/lib/innate/cache/memory.rb +22 -0
  29. data/lib/innate/cache/yaml.rb +17 -0
  30. data/lib/innate/core_compatibility/basic_object.rb +9 -0
  31. data/lib/innate/core_compatibility/string.rb +3 -0
  32. data/lib/innate/current.rb +37 -0
  33. data/lib/innate/dynamap.rb +81 -0
  34. data/lib/innate/helper.rb +195 -0
  35. data/lib/innate/helper/aspect.rb +62 -0
  36. data/lib/innate/helper/cgi.rb +39 -0
  37. data/lib/innate/helper/flash.rb +36 -0
  38. data/lib/innate/helper/link.rb +55 -0
  39. data/lib/innate/helper/partial.rb +90 -0
  40. data/lib/innate/helper/redirect.rb +85 -0
  41. data/lib/innate/helper/send_file.rb +18 -0
  42. data/lib/innate/log.rb +23 -0
  43. data/lib/innate/log/color_formatter.rb +43 -0
  44. data/lib/innate/log/hub.rb +72 -0
  45. data/lib/innate/mock.rb +49 -0
  46. data/lib/innate/node.rb +471 -0
  47. data/lib/innate/options.rb +91 -0
  48. data/lib/innate/options/dsl.rb +155 -0
  49. data/lib/innate/request.rb +165 -0
  50. data/lib/innate/response.rb +18 -0
  51. data/lib/innate/route.rb +109 -0
  52. data/lib/innate/session.rb +104 -0
  53. data/lib/innate/session/flash.rb +94 -0
  54. data/lib/innate/setup.rb +23 -0
  55. data/lib/innate/spec.rb +42 -0
  56. data/lib/innate/state.rb +22 -0
  57. data/lib/innate/state/accessor.rb +130 -0
  58. data/lib/innate/state/fiber.rb +68 -0
  59. data/lib/innate/state/thread.rb +39 -0
  60. data/lib/innate/traited.rb +20 -0
  61. data/lib/innate/trinity.rb +22 -0
  62. data/lib/innate/version.rb +3 -0
  63. data/lib/innate/view.rb +67 -0
  64. data/lib/innate/view/erb.rb +17 -0
  65. data/lib/innate/view/none.rb +9 -0
  66. data/lib/rack/middleware_compiler.rb +62 -0
  67. data/lib/rack/reloader.rb +192 -0
  68. data/spec/example/hello.rb +14 -0
  69. data/spec/example/link.rb +29 -0
  70. data/spec/helper.rb +2 -0
  71. data/spec/innate/cache/common.rb +45 -0
  72. data/spec/innate/cache/marshal.rb +5 -0
  73. data/spec/innate/cache/memory.rb +5 -0
  74. data/spec/innate/cache/yaml.rb +5 -0
  75. data/spec/innate/dynamap.rb +22 -0
  76. data/spec/innate/helper.rb +66 -0
  77. data/spec/innate/helper/aspect.rb +80 -0
  78. data/spec/innate/helper/cgi.rb +37 -0
  79. data/spec/innate/helper/flash.rb +148 -0
  80. data/spec/innate/helper/link.rb +82 -0
  81. data/spec/innate/helper/partial.rb +66 -0
  82. data/spec/innate/helper/redirect.rb +148 -0
  83. data/spec/innate/helper/send_file.rb +21 -0
  84. data/spec/innate/helper/view/aspect_hello.erb +1 -0
  85. data/spec/innate/helper/view/locals.erb +1 -0
  86. data/spec/innate/helper/view/loop.erb +4 -0
  87. data/spec/innate/helper/view/num.erb +1 -0
  88. data/spec/innate/helper/view/partial.erb +1 -0
  89. data/spec/innate/helper/view/recursive.erb +8 -0
  90. data/spec/innate/mock.rb +84 -0
  91. data/spec/innate/node.rb +180 -0
  92. data/spec/innate/node/bar.html +1 -0
  93. data/spec/innate/node/foo.html.erb +1 -0
  94. data/spec/innate/node/with_layout.erb +3 -0
  95. data/spec/innate/options.rb +90 -0
  96. data/spec/innate/parameter.rb +154 -0
  97. data/spec/innate/request.rb +73 -0
  98. data/spec/innate/route.rb +129 -0
  99. data/spec/innate/session.rb +59 -0
  100. data/spec/innate/traited.rb +55 -0
  101. metadata +160 -0
@@ -0,0 +1,471 @@
1
+ module Innate
2
+
3
+ # The nervous system of Innate, so you can relax.
4
+ #
5
+ # Node may be included into any class to make it a valid responder to
6
+ # requests.
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.
10
+ #
11
+ # What's also an important difference is the fact that Node is a module, so
12
+ # we don't have to spend a lot of time designing the perfect subclassing
13
+ # scheme.
14
+ #
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
17
+ # exactly the way you want without worrying about side-effects to other
18
+ # nodes.
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.
23
+ #
24
+ # NOTE:
25
+ # * Although I tried to minimize the amount of code in here there is still
26
+ # quite a number of methods left in order to do ramaze-style lookups.
27
+ # 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
+ # ignored.
30
+ # * This also means that method_missing will not see any of the requests
31
+ # coming in.
32
+ # * If you want an action to act as a catch-all, use `def index(*args)`.
33
+
34
+ module Node
35
+ include Traited
36
+
37
+ HELPERS = [:aspect, :cgi, :flash, :link, :partial, :redirect, :send_file]
38
+ LIST = Set.new
39
+
40
+ trait(:layout => nil, :alias_view => {}, :provide => {},
41
+ :method_arities => {}, :wrap => [:aspect_wrap])
42
+
43
+ # Upon inclusion we make ourselves comfortable.
44
+ def self.included(obj)
45
+ obj.__send__(:include, Helper)
46
+ obj.helper(*HELPERS)
47
+
48
+ obj.extend(Trinity, self)
49
+
50
+ LIST << obj
51
+
52
+ return if obj.provide.any?
53
+ # provide .html with no interpolation
54
+ obj.provide(:html => :erb, :yaml => :yaml, :json => :json)
55
+ end
56
+
57
+ def self.setup
58
+ LIST.each{|node| Innate.map(node.mapping, node) }
59
+ Log.debug("Mapped Nodes: %p" % DynaMap::MAP)
60
+ end
61
+
62
+ def mapping
63
+ mapped = Innate.to(self)
64
+ return mapped if mapped
65
+ return '/' if Innate::Node::LIST.size == 1
66
+ "/" << self.name.gsub(/\B[A-Z][^A-Z]/, '_\&').downcase
67
+ end
68
+
69
+ # Shortcut to map or remap this Node
70
+
71
+ def map(location)
72
+ Innate.map(location, self)
73
+ end
74
+
75
+ # This little piece of nasty looking code enables you to provide different
76
+ # content from a single action.
77
+ #
78
+ # Usage:
79
+ #
80
+ # class Feeds
81
+ # include Innate::Node
82
+ # map '/feed'
83
+ #
84
+ # provide :html => :erb, :rss => :erb, :atom => :erb
85
+ #
86
+ # def index
87
+ # @feed = build_some_feed
88
+ # end
89
+ # end
90
+ #
91
+ # This will do following to these requests:
92
+ #
93
+ # /feed # => call Feeds#index with template /view/feed/index.erb
94
+ # /feed.atom # => call Feeds#index with template /view/feed/index.atom.erb
95
+ # /feed.rss # => call Feeds#index with template /view/feed/index.rss.erb
96
+ #
97
+ # If index.atom.erb isn't available we fall back to /view/feed/index.erb
98
+ #
99
+ # So it's really easy to add your own content representation.
100
+ #
101
+ # If no matching provider is found for the given extension it will fall
102
+ # back to the one specified for html.
103
+ #
104
+ # The correct templating engine is selected by matching the last extension
105
+ # of the template itself to the one set in Innate::View.
106
+ #
107
+ # If you don't want that your response is passed through a templating
108
+ # engine, use :none like:
109
+ #
110
+ # provide :txt => :none
111
+ #
112
+ # So a request to
113
+ #
114
+ # /feed.txt # => call Feeds#index with template /view/feed/index.txt.erb
115
+ #
116
+ # NOTE: provides also have effect on the chosen layout for the action.
117
+ #
118
+ # Given a Node at '/' with `layout('default')`:
119
+ # /layout/default.erb
120
+ # /layout/default.rss.erb
121
+ # /view/index.erb
122
+ # /view/feed.rss.erb
123
+ #
124
+ # /feed.rss will wrap /view/feed.rss.erb in /layout/default.rss.erb
125
+ # /index will wrap /view/index.erb in /layout/default.erb
126
+
127
+ def provide(formats = {})
128
+ return ancestral_trait[:provide] if formats.empty?
129
+
130
+ trait[:provide] ||= {}
131
+ formats.each{|pr, as| trait[:provide][pr.to_s] = as.to_s }
132
+
133
+ ancestral_trait[:provide]
134
+ end
135
+
136
+ # This makes the Node a valid application for Rack.
137
+ # +env+ is the environment hash passed from the Rack::Handler
138
+ #
139
+ # We rely on correct PATH_INFO.
140
+ #
141
+ # As defined by the Rack spec, PATH_INFO may be empty if it wants the root
142
+ # of the application, so we insert '/' to make our dispatcher simple.
143
+ #
144
+ # Innate will not rescue any errors for you or do any error handling, this
145
+ # should be done by an underlying middleware.
146
+ #
147
+ # We do however log errors at some vital points in order to provide you
148
+ # with feedback in your logs.
149
+ #
150
+ # NOTE:
151
+ # * A lot of functionality in here relies on the fact that call is
152
+ # executed within Innate::STATE.wrap which populates the variables used
153
+ # by Trinity.
154
+ # * If you use the Node directly as a middleware make sure that you #use
155
+ # Innate::Current as a middleware before it.
156
+
157
+ def call(env)
158
+ path = env['PATH_INFO']
159
+ path << '/' if path.empty?
160
+
161
+ response.reset
162
+ response = try_resolve(path)
163
+ response['Content-Type'] ||= 'text/html'
164
+
165
+ Current.session.flush(response)
166
+
167
+ response.finish
168
+ end
169
+
170
+ # Let's try to find some valid action for given +path+.
171
+ # Otherwise we dispatch to action_not_found
172
+
173
+ def try_resolve(path)
174
+ action = resolve(path)
175
+ action ? action_found(action) : action_not_found(path)
176
+ end
177
+
178
+ # 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
+
182
+ def action_found(action)
183
+ result = catch(:respond){ catch(:redirect){ action.call }}
184
+
185
+ if result.respond_to?(:finish)
186
+ return result
187
+ else
188
+ Current.response.write(result)
189
+ return Current.response
190
+ end
191
+ end
192
+
193
+ # The default handler in case no action was found, kind of method_missing.
194
+ # Must modify the response in order to have any lasting effect.
195
+ #
196
+ # Reasoning:
197
+ # * We are doing this is in order to avoid tons of special error handling
198
+ # code that would impact runtime and make the overall API more
199
+ # complicated.
200
+ # * This cannot be a normal action is that methods defined in Innate::Node
201
+ # will never be considered for actions.
202
+ #
203
+ # To use a normal action with template do following:
204
+ #
205
+ # class Hi
206
+ # include Innate::Node
207
+ # map '/'
208
+ #
209
+ # def action_not_found(path)
210
+ # return if path == '/not_found'
211
+ # # No normal action, runs on bare metal
212
+ # try_resolve('/not_found')
213
+ # end
214
+ #
215
+ # def not_found
216
+ # # Normal action
217
+ # "Sorry, I do not exist"
218
+ # end
219
+ # end
220
+
221
+ def action_not_found(path)
222
+ response.status = 404
223
+ response['Content-Type'] = 'text/plain'
224
+ response.write("Action not found at: %p" % path)
225
+
226
+ response
227
+ end
228
+
229
+ # Let's get down to business, first check if we got any wishes regarding
230
+ # the representation from the client, otherwise we will assume he wants
231
+ # html.
232
+
233
+ def resolve(path)
234
+ name, wish = find_provide(path)
235
+ update_method_arities
236
+ find_action(name, wish)
237
+ end
238
+
239
+ def find_provide(path)
240
+ name, wish = path, 'html'
241
+
242
+ provide.find do |key, value|
243
+ next unless path =~ /^(.+)\.#{key}$/i
244
+ name, wish = $1, key
245
+ end
246
+
247
+ return name, wish
248
+ end
249
+
250
+ # Now we're talking Action, we try to find a matching template and method,
251
+ # if we can't find either we go to the next pattern, otherwise we answer
252
+ # with an Action with everything we know so far about the demands of the
253
+ # client.
254
+
255
+ def find_action(given_name, wish)
256
+ patterns_for(given_name) do |name, params|
257
+ view = find_view(name, wish)
258
+ method = find_method(name, params)
259
+
260
+ next unless view or method
261
+
262
+ layout = find_layout(name, wish)
263
+
264
+ Action.create(
265
+ :node => self, :params => params, :wish => wish, :method => method,
266
+ :view => view, :options => {}, :variables => {}, :layout => layout)
267
+ end
268
+ end
269
+
270
+ # TODO: allow layouts combined of method and view... hairy :)
271
+ def find_layout(name, wish)
272
+ return unless found_layout = layout
273
+
274
+ if found = to_layout(found_layout, wish)
275
+ [:layout, found]
276
+ elsif found = find_view(found_layout, wish)
277
+ [:view, found]
278
+ elsif found = find_method(found_layout, [])
279
+ [:method, found]
280
+ end
281
+ end
282
+
283
+ # I hope this method talks for itself, we check arity if possible, but will
284
+ # happily dispatch to any method that has default parameters.
285
+ # If you don't want your method to be responsible for messing up a request
286
+ # you should think twice about the arguments you specify due to limitations
287
+ # in Ruby.
288
+ #
289
+ # So if you want your method to take only one parameter which may have a
290
+ # default value following will work fine:
291
+ #
292
+ # def index(foo = "bar", *rest)
293
+ #
294
+ # But following will respond to /arg1/arg2 and then fail due to ArgumentError:
295
+ #
296
+ # def index(foo = "bar")
297
+ #
298
+ # Here a glance at how parameters are expressed in arity:
299
+ #
300
+ # def index(a) # => 1
301
+ # def index(a = :a) # => -1
302
+ # def index(a, *r) # => -2
303
+ # def index(a = :a, *r) # => -1
304
+ #
305
+ # def index(a, b) # => 2
306
+ # def index(a, b, *r) # => -3
307
+ # def index(a, b = :b) # => -2
308
+ # def index(a, b = :b, *r) # => -2
309
+ #
310
+ # def index(a = :a, b = :b) # => -1
311
+ # def index(a = :a, b = :b, *r) # => -1
312
+ #
313
+ # NOTE: Once 1.9 is mainstream we can use Method#parameters to do accurate
314
+ # prediction
315
+ def find_method(name, params)
316
+ return unless arity = trait[:method_arities][name]
317
+ name if arity == params.size or arity < 0
318
+ end
319
+
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
324
+ #
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.
331
+ #
332
+ # Example:
333
+ #
334
+ # Hi.update_method_arities
335
+ # # => {'index' => 0, 'foo' => -1, 'bar => 2}
336
+ def update_method_arities
337
+ arities = trait[:method_arities] = {}
338
+
339
+ exposed = ancestors & Helper::EXPOSE.to_a
340
+ higher = ancestors.select{|a| a < Innate::Node }
341
+
342
+ (higher + exposed).reverse_each do |ancestor|
343
+ ancestor.public_instance_methods(false).each do |im|
344
+ arities[im.to_s] = ancestor.instance_method(im).arity
345
+ end
346
+ end
347
+
348
+ arities
349
+ end
350
+
351
+ # Try to find the best template for the given basename and wish.
352
+ # Also, having extraordinarily much fun with globs.
353
+ def find_view(file, wish)
354
+ path = [Innate.options.app.root, Innate.options.app.view, view_root, file]
355
+ to_template(path, wish)
356
+ end
357
+
358
+ # This is done to make you feel more at home, pass an absolute path or a
359
+ # path relative to your application root to set it, otherwise you'll get
360
+ # the current mapping.
361
+ def view_root(location = nil)
362
+ return @view_root = location if location
363
+ @view_root ||= Innate.to(self)
364
+ end
365
+
366
+ def alias_view(to, from)
367
+ (trait[:alias_view] ||= {})[to] = from
368
+ end
369
+
370
+ # Find the best matching file for the layout, if any.
371
+ def to_layout(file, wish)
372
+ path = [Innate.options.app.root, Innate.options.app.layout, file]
373
+ to_template(path, wish)
374
+ end
375
+
376
+ def to_template(path, wish)
377
+ return unless path.all?
378
+
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
382
+
383
+ if found.size > 1
384
+ Log.warn("%d views found for %p | %p" % [found.size, path, wish])
385
+ end
386
+
387
+ template = found.first
388
+ ancestral_trait[:alias_view][template] || template
389
+ end
390
+
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]
395
+ end
396
+
397
+ # The innate beauty in Nitro, Ramaze, and Innate.
398
+ #
399
+ # Will yield the name of the action and parameter for the action method in
400
+ # order of significance.
401
+ #
402
+ # def foo__bar # responds to /foo/bar
403
+ # def foo(bar) # also responds to /foo/bar
404
+ #
405
+ # But foo__bar takes precedence because it's more explicit.
406
+ #
407
+ # The last fallback will always be the index action with all of the path
408
+ # turned into parameters.
409
+ #
410
+ # Samples:
411
+ #
412
+ # class Foo; include Innate::Node; map '/'; end
413
+ #
414
+ # Foo.patterns_for('/'){|action, params| p action => params }
415
+ # {"index"=>[]}
416
+ #
417
+ # Foo.patterns_for('/foo/bar'){|action, params| p action => params }
418
+ # {"foo__bar"=>[]}
419
+ # {"foo"=>["bar"]}
420
+ # {"index"=>["foo", "bar"]}
421
+ #
422
+ # 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"]}
427
+ def patterns_for(path)
428
+ atoms = path.split('/')
429
+ atoms.delete('')
430
+ result = nil
431
+
432
+ atoms.size.downto(0) do |len|
433
+ action = atoms[0...len].join('__')
434
+ params = atoms[len..-1]
435
+ action = 'index' if action.empty?
436
+
437
+ return result if result = yield(action, params)
438
+ end
439
+
440
+ return nil
441
+ end
442
+
443
+ # This awesome piece of hackery implements action AOP, methods may register
444
+ # themself in the trait[:wrap] and will be called in left-to-right order,
445
+ # each being passed the action instance and a block that they have to yield
446
+ # to continue the chain.
447
+ #
448
+ # This enables things like action logging, caching, aspects,
449
+ # authentication, etc...
450
+ #
451
+ # @param [Action] action instance that is being passed to every registered method
452
+ # @param [Proc] block contains the instructions to call the action method if any
453
+ # @see Action#render
454
+ # @author manveru
455
+
456
+ def wrap_action_call(action, &block)
457
+ wrap = ancestral_trait[:wrap]
458
+
459
+ head, tail = wrap[0], wrap[1..-1].reverse
460
+ combined = tail.inject(block){|s,v| lambda{ __send__(v, action, &s) } }
461
+ __send__(head, action, &combined)
462
+ end
463
+
464
+ # For compatibility with new Kernel#binding behaviour in 1.9
465
+ #
466
+ # @return [Binding] binding of the instance being rendered.
467
+ # @see Action#binding
468
+ # @author manveru
469
+ def binding; super; end
470
+ end
471
+ end