manveru-innate 2009.02.25 → 2009.03.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG +383 -0
  2. data/MANIFEST +17 -8
  3. data/README.md +222 -136
  4. data/Rakefile +7 -2
  5. data/example/provides.rb +28 -0
  6. data/innate.gemspec +19 -10
  7. data/lib/innate/action.rb +21 -47
  8. data/lib/innate/adapter.rb +65 -56
  9. data/lib/innate/cache.rb +16 -8
  10. data/lib/innate/dynamap.rb +39 -29
  11. data/lib/innate/helper/aspect.rb +60 -0
  12. data/lib/innate/helper/cgi.rb +2 -0
  13. data/lib/innate/helper/link.rb +43 -11
  14. data/lib/innate/helper/partial.rb +6 -5
  15. data/lib/innate/helper.rb +10 -13
  16. data/lib/innate/log/hub.rb +1 -0
  17. data/lib/innate/log.rb +3 -6
  18. data/lib/{rack → innate}/middleware_compiler.rb +19 -12
  19. data/lib/innate/mock.rb +3 -2
  20. data/lib/innate/node.rb +573 -179
  21. data/lib/innate/options/dsl.rb +46 -6
  22. data/lib/innate/options/stub.rb +7 -0
  23. data/lib/innate/options.rb +14 -93
  24. data/lib/innate/request.rb +21 -7
  25. data/lib/innate/response.rb +12 -0
  26. data/lib/innate/route.rb +2 -3
  27. data/lib/innate/session.rb +37 -20
  28. data/lib/innate/spec.rb +4 -0
  29. data/lib/innate/state/fiber.rb +14 -7
  30. data/lib/innate/state/thread.rb +10 -2
  31. data/lib/innate/state.rb +8 -11
  32. data/lib/innate/traited.rb +14 -6
  33. data/lib/innate/trinity.rb +0 -4
  34. data/lib/innate/version.rb +1 -1
  35. data/lib/innate/view/erb.rb +4 -2
  36. data/lib/innate/view/none.rb +2 -2
  37. data/lib/innate/view.rb +14 -21
  38. data/lib/innate.rb +49 -30
  39. data/spec/helper.rb +8 -0
  40. data/spec/innate/action/layout.rb +9 -6
  41. data/spec/innate/cache/common.rb +3 -3
  42. data/spec/innate/helper/aspect.rb +3 -5
  43. data/spec/innate/helper/flash.rb +1 -1
  44. data/spec/innate/helper/link.rb +45 -2
  45. data/spec/innate/helper/partial.rb +34 -10
  46. data/spec/innate/helper/view/loop.erb +1 -1
  47. data/spec/innate/helper/view/recursive.erb +1 -1
  48. data/spec/innate/node/mapping.rb +37 -0
  49. data/spec/innate/node/node.rb +142 -0
  50. data/spec/innate/node/resolve.rb +82 -0
  51. data/spec/innate/node/{another_layout → view/another_layout}/another_layout.erb +0 -0
  52. data/spec/innate/node/{bar.html → view/bar.erb} +0 -0
  53. data/spec/innate/node/{foo.html.erb → view/foo.html.erb} +0 -0
  54. data/spec/innate/node/{only_view.html → view/only_view.erb} +0 -0
  55. data/spec/innate/node/view/with_layout.erb +1 -0
  56. data/spec/innate/node/wrap_action_call.rb +83 -0
  57. data/spec/innate/options.rb +28 -6
  58. data/spec/innate/provides/list.html.erb +1 -0
  59. data/spec/innate/provides/list.txt.erb +1 -0
  60. data/spec/innate/provides.rb +99 -0
  61. data/spec/innate/request.rb +23 -10
  62. data/spec/innate/route.rb +2 -4
  63. data/spec/innate/session.rb +1 -1
  64. data/spec/innate/state/fiber.rb +57 -0
  65. data/spec/innate/state/thread.rb +40 -0
  66. metadata +20 -11
  67. data/lib/rack/reloader.rb +0 -192
  68. data/spec/innate/node/with_layout.erb +0 -3
  69. data/spec/innate/node.rb +0 -224
data/innate.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "innate"
3
- s.version = "2009.02.25"
3
+ s.version = "2009.03.24"
4
4
 
5
5
  s.summary = "Powerful web-framework wrapper for Rack."
6
6
  s.description = "Simple, straight-forward, base for web-frameworks."
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.homepage = "http://github.com/manveru/innate"
12
12
  s.require_path = "lib"
13
13
 
14
- s.add_dependency('rack', '>= 0.4.0')
14
+ s.add_dependency('rack', '>= 0.9.1')
15
15
 
16
16
  s.files = [
17
17
  "CHANGELOG",
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  "example/hello.rb",
34
34
  "example/howto_spec.rb",
35
35
  "example/link.rb",
36
+ "example/provides.rb",
36
37
  "example/session.rb",
37
38
  "innate.gemspec",
38
39
  "lib/innate.rb",
@@ -60,10 +61,12 @@ Gem::Specification.new do |s|
60
61
  "lib/innate/log.rb",
61
62
  "lib/innate/log/color_formatter.rb",
62
63
  "lib/innate/log/hub.rb",
64
+ "lib/innate/middleware_compiler.rb",
63
65
  "lib/innate/mock.rb",
64
66
  "lib/innate/node.rb",
65
67
  "lib/innate/options.rb",
66
68
  "lib/innate/options/dsl.rb",
69
+ "lib/innate/options/stub.rb",
67
70
  "lib/innate/request.rb",
68
71
  "lib/innate/response.rb",
69
72
  "lib/innate/route.rb",
@@ -80,8 +83,6 @@ Gem::Specification.new do |s|
80
83
  "lib/innate/view.rb",
81
84
  "lib/innate/view/erb.rb",
82
85
  "lib/innate/view/none.rb",
83
- "lib/rack/middleware_compiler.rb",
84
- "lib/rack/reloader.rb",
85
86
  "spec/example/hello.rb",
86
87
  "spec/example/link.rb",
87
88
  "spec/example/session.rb",
@@ -108,17 +109,25 @@ Gem::Specification.new do |s|
108
109
  "spec/innate/helper/view/partial.erb",
109
110
  "spec/innate/helper/view/recursive.erb",
110
111
  "spec/innate/mock.rb",
111
- "spec/innate/node.rb",
112
- "spec/innate/node/another_layout/another_layout.erb",
113
- "spec/innate/node/bar.html",
114
- "spec/innate/node/foo.html.erb",
115
- "spec/innate/node/only_view.html",
116
- "spec/innate/node/with_layout.erb",
112
+ "spec/innate/node/mapping.rb",
113
+ "spec/innate/node/node.rb",
114
+ "spec/innate/node/resolve.rb",
115
+ "spec/innate/node/view/another_layout/another_layout.erb",
116
+ "spec/innate/node/view/bar.erb",
117
+ "spec/innate/node/view/foo.html.erb",
118
+ "spec/innate/node/view/only_view.erb",
119
+ "spec/innate/node/view/with_layout.erb",
120
+ "spec/innate/node/wrap_action_call.rb",
117
121
  "spec/innate/options.rb",
118
122
  "spec/innate/parameter.rb",
123
+ "spec/innate/provides.rb",
124
+ "spec/innate/provides/list.html.erb",
125
+ "spec/innate/provides/list.txt.erb",
119
126
  "spec/innate/request.rb",
120
127
  "spec/innate/route.rb",
121
128
  "spec/innate/session.rb",
129
+ "spec/innate/state/fiber.rb",
130
+ "spec/innate/state/thread.rb",
122
131
  "spec/innate/traited.rb"
123
132
  ]
124
133
  end
data/lib/innate/action.rb CHANGED
@@ -1,19 +1,25 @@
1
1
  module Innate
2
2
  ACTION_MEMBERS = [ :node, :method, :params, :view, :layout, :instance, :exts,
3
- :wish, :options, :variables, :value, :view_value, :name ]
3
+ :wish, :options, :variables, :value, :view_value, :engine ]
4
4
 
5
5
  class Action < Struct.new(*ACTION_MEMBERS)
6
- # Holds the default values for merging in {Action::create}
7
- DEFAULT = {:options => {}, :variables => {}, :params => []}
8
-
9
6
  # Create a new Action instance.
7
+ # Note that the default cannot be a constant as assigning the value objects
8
+ # to the struct would modify them and might lead to bugs due to persisting
9
+ # action contents.
10
10
  #
11
11
  # @param [Hash, #to_hash] hash used to seed new Action instance
12
12
  # @return [Action] action with the given defaults from hash
13
13
  # @api stable
14
14
  # @author manveru
15
15
  def self.create(hash = {})
16
- new(*DEFAULT.merge(hash.to_hash).values_at(*ACTION_MEMBERS))
16
+ default = {:options => {}, :variables => {}, :params => []}
17
+ new(*default.merge(hash.to_hash).values_at(*ACTION_MEMBERS))
18
+ end
19
+
20
+ def merge!(hash)
21
+ hash.each_pair{|key, value| send("#{key}=", value) }
22
+ self
17
23
  end
18
24
 
19
25
  # Call the Action instance, will insert itself temporarily into
@@ -96,51 +102,14 @@ module Innate
96
102
  instance.wrap_action_call(self) do
97
103
  self.value = instance.__send__(method, *params) if method
98
104
  self.view_value = File.read(view) if view
105
+ copy_variables
99
106
 
100
- content_type, body = send(Innate.options.action.wish[wish] || :as_html)
101
- response = Current.response
102
- response['Content-Type'] ||= content_type if response
103
-
107
+ body, content_type = wrap_in_layout{ engine.call(self, view_value || value) }
108
+ options[:content_type] ||= content_type if content_type
104
109
  body
105
110
  end
106
111
  end
107
112
 
108
- # @return [Array] Content-Type and rendered action
109
- # @see Action#render Action#wrap_in_layout
110
- # @author manveru
111
- def as_html
112
- return 'text/html', wrap_in_layout{ fulfill_wish(view_value || value) }
113
- end
114
-
115
- # @return [Array] Content-Type and rendered action
116
- # @see Action#render Action#wrap_in_layout
117
- # @author manveru
118
- def as_yaml
119
- require 'yaml'
120
- return 'text/yaml', (value || view_value).to_yaml
121
- end
122
-
123
- # @return [Array] Content-Type and rendered action
124
- # @see Action#render Action#wrap_in_layout
125
- # @author manveru
126
- def as_json
127
- require 'json'
128
- return 'application/json', (value || view_value).to_json
129
- end
130
-
131
- # @param [String, #to_str] string to be rendered
132
- # @return [String] The rendered result of the templating engine
133
- # @raise [RuntimeError] if no suitable templating engine was found
134
- # @see Action#as_html
135
- # @author manveru
136
- def fulfill_wish(string)
137
- way = File.basename(view).gsub!(/.*?#{wish}\./, '') if view
138
- way ||= node.provide[wish] || node.provide['html']
139
-
140
- return View.render(way, self, string || view) if way
141
- raise("No templating engine was found for %p" % way)
142
- end
143
-
144
113
  def wrap_in_layout
145
114
  return yield unless layout
146
115
 
@@ -148,8 +117,9 @@ module Innate
148
117
  action.view, action.method = layout_view_or_method(*layout)
149
118
  action.layout = nil
150
119
  action.sync_variables(self)
151
- action.variables[:content] = yield
152
- action.call
120
+ body, content_type = yield
121
+ action.variables[:content] = body
122
+ return action.call, content_type
153
123
  end
154
124
 
155
125
  def layout_view_or_method(name, arg)
@@ -161,5 +131,9 @@ module Innate
161
131
  def name
162
132
  File.basename((method || view).to_s).split('.').first
163
133
  end
134
+
135
+ def valid?
136
+ method || view
137
+ end
164
138
  end
165
139
  end
@@ -17,73 +17,82 @@ module Innate
17
17
  # where Rack doesn't want to take a stand.
18
18
 
19
19
  module Adapter
20
- class << self
21
-
22
- # Pass given app to the Handler, handler is chosen based on
23
- # config.adapter option.
24
- # If there is a method named start_name_of_adapter it will be run instead
25
- # of the default run method of the handler, this makes it easy to define
26
- # custom startup of handlers for your server of choice
27
- def start(app, options = Innate.options)
28
- adapter_name = options[:adapter].to_s.downcase
29
- config = { :Host => options[:host], :Port => options[:port] }
30
- Log.debug "Using #{adapter_name}"
31
-
32
- if respond_to?(method = "start_#{adapter_name}")
33
- send(method, app, config)
34
- else
35
- Rack::Handler.get(adapter_name).run(app, config)
36
- end
37
- end
20
+ include Optional
38
21
 
39
- # Due to buggy autoload on Ruby 1.8 we have to require 'ebb' manually.
40
- # This most likely happens because autoload doesn't respect the require
41
- # of rubygems and uses the C require directly.
42
- def start_ebb(app, config)
43
- require 'ebb'
44
- Rack::Handler.get('ebb').run(app, config)
45
- end
22
+ options.dsl do
23
+ o "IP address or hostname that we respond to - 0.0.0.0 for all",
24
+ :host, "0.0.0.0"
46
25
 
47
- # We want webrick to use our logger.
26
+ o "Port for the server",
27
+ :port, 7000
48
28
 
49
- def start_webrick(app, config)
50
- handler = Rack::Handler.get('webrick')
51
- config = {
52
- :BindAddress => config[:Host],
53
- :Port => config[:Port],
54
- :Logger => Log,
55
- :AccessLog => [
56
- [Log, ::WEBrick::AccessLog::COMMON_LOG_FORMAT],
57
- [Log, ::WEBrick::AccessLog::REFERER_LOG_FORMAT]]
58
- }
29
+ o "Web server to run on",
30
+ :handler, :webrick
31
+ end
59
32
 
60
- handler.run(app, config)
61
- end
33
+ # Pass given app to the Handler, handler is chosen based on config.adapter
34
+ # option.
35
+ # If there is a method named start_name_of_adapter it will be run instead
36
+ # of the default run method of the handler, this makes it easy to define
37
+ # custom startup of handlers for your server of choice.
38
+ def self.start(app, given_options = nil)
39
+ options.merge!(given_options) if given_options
62
40
 
63
- # Thin shouldn't give excessive output, especially not to $stdout
41
+ handler = options[:handler].to_s.downcase
42
+ config = { :Host => options[:host], :Port => options[:port] }
64
43
 
65
- def start_thin(app, config)
66
- require 'thin'
67
- handler = Rack::Handler.get('thin')
68
- ::Thin::Logging.silent = true
69
- handler.run(app, config)
44
+ Log.debug "Using #{handler}"
45
+
46
+ if respond_to?(method = "start_#{handler}")
47
+ send(method, app, config)
48
+ else
49
+ Rack::Handler.get(handler).run(app, config)
70
50
  end
51
+ end
71
52
 
72
- # swiftcore has its own handler outside of rack
53
+ # Due to buggy autoload on Ruby 1.8 we have to require 'ebb' manually.
54
+ # This most likely happens because autoload doesn't respect the require of
55
+ # rubygems and uses the C require directly.
56
+ def self.start_ebb(app, config)
57
+ require 'ebb'
58
+ Rack::Handler.get('ebb').run(app, config)
59
+ end
73
60
 
74
- def start_emongrel(app, config)
75
- require 'swiftcore/evented_mongrel'
76
- handler = Rack::Handler.get('emongrel')
77
- handler.run(app, config)
78
- end
61
+ # We want webrick to use our logger.
62
+ def self.start_webrick(app, config)
63
+ handler = Rack::Handler.get('webrick')
64
+ config = {
65
+ :BindAddress => config[:Host],
66
+ :Port => config[:Port],
67
+ :Logger => Log,
68
+ :AccessLog => [
69
+ [Log, ::WEBrick::AccessLog::COMMON_LOG_FORMAT],
70
+ [Log, ::WEBrick::AccessLog::REFERER_LOG_FORMAT]]
71
+ }
72
+
73
+ handler.run(app, config)
74
+ end
75
+
76
+ # Thin shouldn't give excessive output, especially not to $stdout
77
+ def self.start_thin(app, config)
78
+ require 'thin'
79
+ handler = Rack::Handler.get('thin')
80
+ ::Thin::Logging.silent = true
81
+ handler.run(app, config)
82
+ end
79
83
 
80
- # swiftcore has its own handler outside of rack
84
+ # swiftcore has its own handler outside of rack
85
+ def self.start_emongrel(app, config)
86
+ require 'swiftcore/evented_mongrel'
87
+ handler = Rack::Handler.get('emongrel')
88
+ handler.run(app, config)
89
+ end
81
90
 
82
- def start_smongrel(app, config)
83
- require 'swiftcore/swiftiplied_mongrel'
84
- handler = Rack::Handler.get('smongrel')
85
- handler.run(app, config)
86
- end
91
+ # swiftcore has its own handler outside of rack
92
+ def self.start_smongrel(app, config)
93
+ require 'swiftcore/swiftiplied_mongrel'
94
+ handler = Rack::Handler.get('smongrel')
95
+ handler.run(app, config)
87
96
  end
88
97
  end
89
98
  end
data/lib/innate/cache.rb CHANGED
@@ -14,7 +14,7 @@ module Innate
14
14
  #
15
15
  # Configuration:
16
16
  #
17
- # Innate.options.cache do |cache|
17
+ # Innate::Cache.options do |cache|
18
18
  # cache.names = [:session, :user]
19
19
  # cache.session = Innate::Cache::Marshal
20
20
  # cache.user = Innate::Cache::YAML
@@ -61,20 +61,28 @@ module Innate
61
61
  autoload :Marshal, 'innate/cache/marshal'
62
62
  autoload :FileBased, 'innate/cache/file_based'
63
63
 
64
+ include Optional
65
+
66
+ options.dsl do
67
+ o "Assign a cache to each of these names on Innate::Cache::setup",
68
+ :names, [:session]
69
+
70
+ default "If no option for the cache name exists, fall back to this",
71
+ Innate::Cache::Memory
72
+ end
73
+
64
74
  attr_reader :name, :instance
65
75
 
66
76
  def initialize(name, klass = nil)
67
77
  @name = name.to_s.dup.freeze
68
78
 
69
- options = Innate.options
70
-
71
- klass ||= options[:cache, @name.to_sym]
79
+ klass ||= options[@name.to_sym]
72
80
  @instance = klass.new
73
81
 
74
82
  @instance.cache_setup(
75
- options.env.host,
76
- options.env.user,
77
- options.app.name,
83
+ ENV['HOSTNAME'],
84
+ ENV['USER'],
85
+ 'pristine',
78
86
  @name
79
87
  )
80
88
  end
@@ -86,7 +94,7 @@ module Innate
86
94
  # @return [Array] names of caches initialized
87
95
  # @author manveru
88
96
  def self.setup
89
- Innate.options.cache.names.each{|name| add(name) }
97
+ options.names.each{|name| add(name) }
90
98
  end
91
99
 
92
100
  # Add accessors for cache
@@ -1,38 +1,48 @@
1
1
  module Innate
2
+ class URLMap < Rack::URLMap
3
+ def initialize(map = {})
4
+ @originals = map
5
+ super
6
+ end
2
7
 
3
- # This is a dynamic routing mapper used to outsmart Rack::URLMap
4
- # Every time a mapping is changed a new Rack::URLMap will be put into
5
- # Innate::DynaMap::CACHE[:map]
6
- class DynaMap
7
- MAP = {}
8
- CACHE = {}
8
+ # super may raise when given invalid locations, so we only replace the
9
+ # `@originals` if we are sure the new map is valid
10
+ def remap(map)
11
+ value = super
12
+ @originals = map
13
+ value
14
+ end
9
15
 
10
- # Delegate the call to the current Rack::URLMap instance.
11
- #
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
- def self.call(env)
18
- if app = CACHE[:map]
19
- script_name, path_info = env['SCRIPT_NAME'], env['PATH_INFO']
20
- answer = app.call(env)
21
- env.merge!('SCRIPT_NAME' => script_name, 'PATH_INFO' => path_info)
22
- answer
23
- else
24
- raise "Nothing mapped yet"
25
- end
16
+ def map(location, object)
17
+ return unless location and object
18
+ remap(@originals.merge(location.to_s => object))
26
19
  end
27
20
 
28
- # Map node to location, create a new Rack::URLMap instance and cache it.
29
- def self.map(location, node)
30
- return unless location
31
- MAP[location.to_s] = node
32
- CACHE[:map] = Rack::URLMap.new(MAP)
21
+ def at(location)
22
+ @originals[location]
23
+ end
24
+
25
+ def to(object)
26
+ @originals.invert[object]
27
+ end
28
+
29
+ def to_hash
30
+ @originals.dup
31
+ end
32
+
33
+ def call(env)
34
+ raise "Nothing mapped yet" if @originals.empty?
35
+ super
33
36
  end
34
37
  end
35
38
 
39
+ DynaMap = URLMap.new
40
+
41
+ # script_name, path_info = env['SCRIPT_NAME'], env['PATH_INFO']
42
+ # answer = app.call(env)
43
+ # env.merge!('SCRIPT_NAME' => script_name, 'PATH_INFO' => path_info)
44
+ # answer
45
+
36
46
  module SingletonMethods
37
47
  # Maps the given +object+ or +block+ to +location+, +object+ must respond to
38
48
  # #call in order to be of any use.
@@ -61,7 +71,7 @@ module Innate
61
71
  #
62
72
  # Innate.at('/') # => Hello
63
73
  def at(location)
64
- DynaMap::MAP[location.to_s]
74
+ DynaMap.at(location)
65
75
  end
66
76
 
67
77
  # Returns one of the paths the given +object+ is mapped to.
@@ -75,7 +85,7 @@ module Innate
75
85
  #
76
86
  # Innate.to(Hello) # => '/'
77
87
  def to(object)
78
- DynaMap::MAP.invert[object]
88
+ DynaMap.to(object)
79
89
  end
80
90
  end
81
91
  end
@@ -2,11 +2,14 @@ module Innate
2
2
  module Helper
3
3
 
4
4
  # Provides before/after wrappers for actions
5
+ #
6
+ # This helper is essential for proper working of {Action#render}.
5
7
  module Aspect
6
8
  AOP = Hash.new{|h,k| h[k] = Hash.new{|hh,kk| hh[kk] = {} }}
7
9
 
8
10
  def self.included(into)
9
11
  into.extend(SingletonMethods)
12
+ into.add_action_wrapper(5.0, :aspect_wrap)
10
13
  end
11
14
 
12
15
  # Consider objects that have Aspect included
@@ -37,7 +40,56 @@ module Innate
37
40
  result
38
41
  end
39
42
 
43
+ # This awesome piece of hackery implements action AOP.
44
+ #
45
+ # The so-called aspects are simply methods that may yield the next aspect
46
+ # in the chain, this is similar to racks concept of middleware, but instead
47
+ # of initializing with an app we simply pass a block that may be yielded
48
+ # with the action being processed.
49
+ #
50
+ # This gives us things like logging, caching, aspects, authentication, etc.
51
+ #
52
+ # Add the name of your method to the trait[:wrap] to add your own method to
53
+ # the wrap_action_call chain.
54
+ #
55
+ # @example adding your method
56
+ #
57
+ # class MyNode
58
+ # Innate.node '/'
59
+ #
60
+ # private
61
+ #
62
+ # def wrap_logging(action)
63
+ # Innate::Log.info("Executing #{action.name}")
64
+ # yield
65
+ # end
66
+ #
67
+ # trait[:wrap]
68
+ # end
69
+ #
70
+ #
71
+ # methods may register
72
+ # themself in the trait[:wrap] and will be called in left-to-right order,
73
+ # each being passed the action instance and a block that they have to yield
74
+ # to continue the chain.
75
+ #
76
+ # @param [Action] action instance that is being passed to every registered method
77
+ # @param [Proc] block contains the instructions to call the action method if any
78
+ #
79
+ # @see Action#render
80
+ # @author manveru
81
+ def wrap_action_call(action, &block)
82
+ wrap = SortedSet.new
83
+ action.node.ancestral_trait_values(:wrap).each{|sset| wrap.merge(sset) }
84
+ head, *tail = wrap.map{|k,v| v }
85
+ tail.reverse!
86
+ combined = tail.inject(block){|s,v| lambda{ __send__(v, action, &s) } }
87
+ __send__(head, action, &combined)
88
+ end
89
+
40
90
  module SingletonMethods
91
+ include Traited
92
+
41
93
  def before_all(&block)
42
94
  AOP[self][:before_all] = block
43
95
  end
@@ -58,6 +110,14 @@ module Innate
58
110
  before(name, &block)
59
111
  after(name, &block)
60
112
  end
113
+
114
+ def add_action_wrapper(order, method_name)
115
+ if wrap = trait[:wrap]
116
+ wrap.merge(SortedSet[[order, method_name]])
117
+ else
118
+ trait :wrap => SortedSet[[order, method_name]]
119
+ end
120
+ end
61
121
  end
62
122
  end
63
123
  end
@@ -34,6 +34,8 @@ module Innate
34
34
  # one-letter versions help in case like #{h foo.inspect}
35
35
  # ERb/ERuby/Rails compatible
36
36
  alias u url_encode
37
+
38
+ module_function(:url_encode, :url_decode, :html_escape, :html_unescape, :h, :u)
37
39
  end
38
40
  end
39
41
  end
@@ -28,31 +28,63 @@ module Innate
28
28
  hashes, names = args.partition{|arg| arg.respond_to?(:merge!) }
29
29
  hashes.each{|to_merge| hash.merge!(to_merge) }
30
30
 
31
- prefix = Innate.options.app.prefix
32
- location = Innate.to(self) || Innate.to(self.class)
33
- front = Array[prefix, location, name, *names].join('/').squeeze('/')
31
+ location = route_location(self)
32
+ front = Array[location, name, *names].join('/').squeeze('/')
34
33
 
35
- if hash.empty?
36
- URI(front)
37
- else
38
- escape = Rack::Utils.method(:escape)
39
- query = hash.map{|key, value| "#{escape[key]}=#{escape[value]}" }.join(';')
40
- URI("#{front}?#{query}")
41
- end
34
+ return URI(front) if hash.empty?
35
+
36
+ escape = Rack::Utils.method(:escape)
37
+ query = hash.map{|k, v| "#{escape[k]}=#{escape[v]}" }.join(';')
38
+ URI("#{front}?#{query}")
42
39
  end
43
40
  alias r route
44
41
 
42
+ def route_location(klass)
43
+ prefix = Innate.options.prefix
44
+ location = Innate.to(klass) || Innate.to(klass.class)
45
+ [prefix, location].join('/')
46
+ end
47
+
48
+ # Create a route to the currently active Node.
49
+ #
50
+ # This method is mostly here in case you include this helper elsewhere
51
+ # and don't want (or can't) type SomeNode.r all the time.
52
+ #
53
+ # The usage is identical with {route}.
54
+ #
55
+ # @param [#to_s] name
56
+ # @return [URI] to the location
57
+ # @see Ramaze::Helper::Link#route
58
+ # @author manveru
59
+ def route_self(name = '/', *args)
60
+ Current.action.node.route(name, *args)
61
+ end
62
+ alias rs route_self
63
+
45
64
  # Create a link tag
46
65
  #
47
66
  # Usage, given Wiki is mapped to `/wiki`:
67
+ #
48
68
  # Wiki.a(:home) # => '<a href="/wiki/home">home</a>'
49
69
  # Wiki.a('home', :home) # => '<a href="/wiki/home">home</a>'
50
70
  # Wiki.a('home', :/) # => '<a href="/wiki/">home</a>'
51
71
  # Wiki.a('foo', :/, :foo => :bar) # => '<a href="/wiki/?foo=bar">foo</a>'
72
+ # Wiki.a('example', 'http://example.com')
73
+ # # => '<a href="http://example.com">example</a>'
52
74
  #
53
75
  # @return [String]
54
76
  def anchor(text, *args)
55
- href = args.empty? ? r(text) : r(*args)
77
+ case first = (args.first || text)
78
+ when URI
79
+ href = first.to_s
80
+ when /^\w+:\/\//
81
+ uri = URI(first)
82
+ uri.query = Rack::Utils.escape_html(uri.query)
83
+ href = uri.to_s
84
+ else
85
+ href = args.empty? ? r(text) : r(*args)
86
+ end
87
+
56
88
  text = Rack::Utils.escape_html(text)
57
89
  %(<a href="#{href}">#{text}</a>)
58
90
  end
@@ -72,16 +72,17 @@ module Innate
72
72
 
73
73
  ext = File.extname(path)
74
74
  basename = File.basename(path, ext)
75
- ext = ext[1..-1] || action.node.provide[action.wish].to_s
76
-
75
+
77
76
  action = Innate::Current.action.dup
78
77
  action.layout = nil
79
- action.view = action.node.find_view(basename, ext)
78
+ action.view = action.node.find_view(basename, 'html')
80
79
  action.method = action.node.find_method(basename, action.params)
80
+
81
81
  action.variables = action.variables.merge(variables)
82
82
  action.sync_variables(action)
83
-
84
- action.call
83
+
84
+ return action.call if action.valid?
85
+ raise(ArgumentError, "cannot render %p" % path)
85
86
  end
86
87
 
87
88
  def render_action(method, *params)