nitro 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/CHANGELOG +752 -543
  2. data/INSTALL +38 -38
  3. data/README +264 -225
  4. data/Rakefile +48 -49
  5. data/bin/nitro +3 -3
  6. data/bin/nitrogen +6 -6
  7. data/doc/AUTHORS +10 -10
  8. data/doc/CHANGELOG.1 +1939 -1939
  9. data/doc/CHANGELOG.2 +954 -954
  10. data/doc/LICENSE +3 -3
  11. data/doc/MIGRATION +28 -0
  12. data/doc/RELEASES +814 -643
  13. data/doc/config.txt +5 -5
  14. data/install.rb +7 -17
  15. data/lib/nitro.rb +38 -9
  16. data/lib/nitro/adapter/cgi.rb +311 -312
  17. data/lib/nitro/adapter/fastcgi.rb +18 -25
  18. data/lib/nitro/adapter/webrick.rb +128 -137
  19. data/lib/nitro/adapter/wee.rb +51 -0
  20. data/lib/nitro/caching.rb +20 -20
  21. data/lib/nitro/caching/actions.rb +43 -43
  22. data/lib/nitro/caching/fragments.rb +46 -46
  23. data/lib/nitro/caching/invalidation.rb +11 -11
  24. data/lib/nitro/caching/output.rb +65 -65
  25. data/lib/nitro/caching/stores.rb +67 -67
  26. data/lib/nitro/compiler.rb +262 -0
  27. data/lib/nitro/compiler/elements.rb +0 -0
  28. data/lib/nitro/compiler/errors.rb +65 -0
  29. data/lib/nitro/compiler/localization.rb +25 -0
  30. data/lib/nitro/compiler/markup.rb +19 -0
  31. data/lib/nitro/compiler/shaders.rb +206 -0
  32. data/lib/nitro/compiler/squeeze.rb +20 -0
  33. data/lib/nitro/compiler/xslt.rb +61 -0
  34. data/lib/nitro/context.rb +87 -88
  35. data/lib/nitro/controller.rb +151 -158
  36. data/lib/nitro/cookie.rb +34 -34
  37. data/lib/nitro/dispatcher.rb +195 -186
  38. data/lib/nitro/element.rb +132 -126
  39. data/lib/nitro/element/java_script.rb +6 -6
  40. data/lib/nitro/flash.rb +66 -66
  41. data/lib/nitro/mail.rb +192 -192
  42. data/lib/nitro/mixin/buffer.rb +66 -0
  43. data/lib/nitro/mixin/debug.rb +16 -16
  44. data/lib/nitro/mixin/form.rb +88 -0
  45. data/lib/nitro/mixin/helper.rb +2 -2
  46. data/lib/nitro/mixin/javascript.rb +108 -108
  47. data/lib/nitro/mixin/markup.rb +144 -0
  48. data/lib/nitro/mixin/pager.rb +202 -202
  49. data/lib/nitro/mixin/rss.rb +67 -0
  50. data/lib/nitro/mixin/table.rb +63 -0
  51. data/lib/nitro/mixin/xhtml.rb +75 -0
  52. data/lib/nitro/mixin/xml.rb +124 -0
  53. data/lib/nitro/render.rb +183 -359
  54. data/lib/nitro/request.rb +140 -140
  55. data/lib/nitro/response.rb +27 -27
  56. data/lib/nitro/routing.rb +21 -21
  57. data/lib/nitro/scaffold.rb +124 -118
  58. data/lib/nitro/server.rb +117 -80
  59. data/lib/nitro/server/runner.rb +341 -0
  60. data/lib/nitro/service.rb +12 -12
  61. data/lib/nitro/service/xmlrpc.rb +22 -22
  62. data/lib/nitro/session.rb +122 -120
  63. data/lib/nitro/session/drb.rb +9 -9
  64. data/lib/nitro/session/drbserver.rb +34 -34
  65. data/lib/nitro/template.rb +171 -155
  66. data/lib/nitro/testing/assertions.rb +90 -90
  67. data/lib/nitro/testing/context.rb +16 -16
  68. data/lib/nitro/testing/testcase.rb +34 -34
  69. data/proto/conf/lhttpd.conf +9 -9
  70. data/proto/public/error.xhtml +75 -75
  71. data/proto/public/index.xhtml +18 -18
  72. data/proto/public/js/behaviour.js +65 -65
  73. data/proto/public/js/controls.js +1 -1
  74. data/proto/public/js/prototype.js +3 -3
  75. data/proto/public/settings.xhtml +61 -61
  76. data/proto/run.rb +1 -5
  77. data/test/nitro/adapter/raw_post1.bin +0 -0
  78. data/test/nitro/adapter/tc_cgi.rb +57 -57
  79. data/test/nitro/adapter/tc_webrick.rb +4 -4
  80. data/test/nitro/mixin/tc_pager.rb +25 -25
  81. data/test/nitro/mixin/tc_rss.rb +24 -0
  82. data/test/nitro/mixin/tc_table.rb +31 -0
  83. data/test/nitro/mixin/tc_xhtml.rb +13 -0
  84. data/test/nitro/tc_caching.rb +10 -10
  85. data/test/nitro/tc_context.rb +8 -8
  86. data/test/nitro/tc_controller.rb +48 -48
  87. data/test/nitro/tc_cookie.rb +6 -6
  88. data/test/nitro/tc_dispatcher.rb +64 -64
  89. data/test/nitro/tc_element.rb +27 -27
  90. data/test/nitro/tc_flash.rb +31 -31
  91. data/test/nitro/tc_mail.rb +63 -63
  92. data/test/nitro/tc_server.rb +26 -26
  93. data/test/nitro/tc_session.rb +9 -9
  94. data/test/nitro/tc_template.rb +19 -19
  95. data/test/public/blog/list.xhtml +1 -1
  96. metadata +31 -37
  97. data/lib/nitro/buffering.rb +0 -45
  98. data/lib/nitro/builder/form.rb +0 -104
  99. data/lib/nitro/builder/rss.rb +0 -104
  100. data/lib/nitro/builder/table.rb +0 -80
  101. data/lib/nitro/builder/xhtml.rb +0 -132
  102. data/lib/nitro/builder/xml.rb +0 -131
  103. data/lib/nitro/conf.rb +0 -36
  104. data/lib/nitro/environment.rb +0 -21
  105. data/lib/nitro/errors.rb +0 -69
  106. data/lib/nitro/localization.rb +0 -153
  107. data/lib/nitro/markup.rb +0 -147
  108. data/lib/nitro/output.rb +0 -24
  109. data/lib/nitro/runner.rb +0 -348
  110. data/lib/nitro/shaders.rb +0 -206
  111. data/test/nitro/builder/tc_rss.rb +0 -23
  112. data/test/nitro/builder/tc_table.rb +0 -30
  113. data/test/nitro/builder/tc_xhtml.rb +0 -39
  114. data/test/nitro/builder/tc_xml.rb +0 -56
  115. data/test/nitro/tc_localization.rb +0 -49
@@ -6,71 +6,72 @@ require 'nitro/render'
6
6
  require 'nitro/scaffold'
7
7
  require 'nitro/caching'
8
8
  require 'nitro/flash'
9
+ require 'nitro/mixin/markup'
9
10
 
10
11
  module Nitro
11
12
 
12
13
  # Encapsulates metadata that describe an action
13
14
  # parameter.
14
15
  #
15
- # [+default+]
16
- # The default value.
16
+ # [+default+]
17
+ # The default value.
17
18
  #
18
- # [+format+]
19
- # The expected format.
19
+ # [+format+]
20
+ # The expected format.
20
21
  #
21
- # [+required+]
22
- # Is this parameter required?
22
+ # [+required+]
23
+ # Is this parameter required?
23
24
 
24
25
  unless const_defined? :ActionParam
25
- ActionParam = Struct.new(:default, :format, :required)
26
+ ActionParam = Struct.new(:default, :format, :required)
26
27
  end
27
28
 
28
29
  # Encapsulates metadata that describe an action.
29
30
 
30
31
  class ActionMeta < Hash
31
32
 
32
- # The arguments of the given method.
33
-
34
- attr_accessor :params
35
-
36
- # Initialize the metadata.
37
-
38
- def initialize(options)
39
- @params = {}
40
- update(options)
41
- end
42
-
43
- # Update the metadata.
44
- #
45
- # [+options+]
46
- # A hash containing the metadata. Options with Symbol
47
- # keys are considered metadata, options with
48
- # String keys are the named parameters for the action.
49
-
50
- def update(options)
51
- options.each do |k, v|
52
- case k
53
- when String
54
- # A key of type String denotes a parameter.
55
- case v
56
- when Regexp
57
- @params[k] = ActionParam.new(nil, v, nil)
58
- when ActionParam
59
- @params[k] = v
60
- else
61
- if v == :required
62
- @params[k] = ActionParam.new(nil, nil, true)
63
- else
64
- @params[k] = ActionParam.new(v, nil, nil)
65
- end
66
- end
67
- when Symbol
68
- self[k] = v
69
- else
70
- raise TypeError.new('The keys must be either Symbols or Strings.')
71
- end
72
- end
73
- end
33
+ # The arguments of the given method.
34
+
35
+ attr_accessor :params
36
+
37
+ # Initialize the metadata.
38
+
39
+ def initialize(options)
40
+ @params = {}
41
+ update(options)
42
+ end
43
+
44
+ # Update the metadata.
45
+ #
46
+ # [+options+]
47
+ # A hash containing the metadata. Options with Symbol
48
+ # keys are considered metadata, options with
49
+ # String keys are the named parameters for the action.
50
+
51
+ def update(options)
52
+ options.each do |k, v|
53
+ case k
54
+ when String
55
+ # A key of type String denotes a parameter.
56
+ case v
57
+ when Regexp
58
+ @params[k] = ActionParam.new(nil, v, nil)
59
+ when ActionParam
60
+ @params[k] = v
61
+ else
62
+ if v == :required
63
+ @params[k] = ActionParam.new(nil, nil, true)
64
+ else
65
+ @params[k] = ActionParam.new(v, nil, nil)
66
+ end
67
+ end
68
+ when Symbol
69
+ self[k] = v
70
+ else
71
+ raise TypeError.new('The keys must be either Symbols or Strings.')
72
+ end
73
+ end
74
+ end
74
75
 
75
76
  end
76
77
 
@@ -79,103 +80,94 @@ end
79
80
  # interface.
80
81
 
81
82
  module Publishable
82
- def self.append_features(base)
83
-
84
- base.module_eval do
85
- include Render
86
- include Glue::Aspects
87
- include Flashing
88
- include Glue::Helpers
89
-
90
- class << self
91
- attr_accessor :template_root
92
- end
93
-
94
- @template_root = 'public'
95
- end
96
-
97
- # Define metadata for an action. This is a helper
98
- # macro.
99
-
100
- base.module_eval do
101
- def self.action(name, options)
102
- if meta = action_metadata[name]
103
- meta.update(options)
104
- else
105
- action_metadata[name] = ActionMeta.new(options)
106
- end
107
- end
108
- end
109
-
110
- # Aliases an action
111
- #--
112
- # gmosx, FIXME: better implementation needed.
113
- #++
114
-
115
- base.module_eval do
116
- def self.alias_action(new, old)
117
- alias_method new, old
118
- md = action_metadata[old] || ActionMetadata.new
119
- md[:view] = old
120
- action_metadata[new] = md
121
- end
122
- end
123
-
124
- # Return the 'action' methods for this Controller.
125
- # Some dangerous methods from ancestors are removed.
126
- # All private methods are ignored.
127
-
128
- base.module_eval do
129
- def self.action_methods
130
- classes = self.ancestors.reject do |a|
131
- [Object, Kernel, Render, Controller, Caching].include?(a)
132
- end
133
-
134
- classes.delete(PP::ObjectMixin) if defined?(PP::ObjectMixin)
135
-
136
- methods = classes.inject([]) do |action_methods, klass|
137
- action_methods + klass.public_instance_methods(false)
138
- end
139
-
140
- # gmosx: add the default action (leave this?)
141
- # methods << 'index'
142
-
143
- return methods
144
- end
145
- end
146
-
147
- # A hash containing metadata for the action
148
- # methods.
149
-
150
- base.module_eval do
151
- def self.action_metadata
152
- # FIXME: improve this.
153
- @action_metadata ||= {}
154
- @action_metadata
155
- end
156
- end
157
-
158
- base.module_eval do
159
- def initialize(context, base = nil)
160
- super
161
- self.class.template_root ||= 'public'
162
- end
163
- end
164
-
165
- # Use the method_missing hook to compile the actions
166
- # for this controller.
167
-
168
- base.module_eval do
169
- def method_missing(action, *args)
170
- if Rendering.compile_action(self.class, action)
171
- send(action, *args)
172
- else
173
- super
174
- end
175
- end
176
- end
177
-
178
- end
83
+ def self.append_features(base)
84
+
85
+ base.module_eval do
86
+ include Render
87
+ include Glue::Aspects
88
+ include Flashing
89
+ include Glue::Helpers
90
+
91
+ class << self
92
+ attr_accessor :template_root
93
+ end
94
+ end
95
+
96
+ # Define metadata for an action. This is a helper
97
+ # macro.
98
+
99
+ base.module_eval do
100
+ def self.action(name, options)
101
+ if meta = action_metadata[name]
102
+ meta.update(options)
103
+ else
104
+ action_metadata[name] = ActionMeta.new(options)
105
+ end
106
+ end
107
+ end
108
+
109
+ # Aliases an action
110
+ #--
111
+ # gmosx, FIXME: better implementation needed.
112
+ #++
113
+
114
+ base.module_eval do
115
+ def self.alias_action(new, old)
116
+ alias_method new, old
117
+ md = action_metadata[old] || ActionMetadata.new
118
+ md[:view] = old
119
+ action_metadata[new] = md
120
+ end
121
+ end
122
+
123
+ # Return the 'action' methods for this Controller.
124
+ # Some dangerous methods from ancestors are removed.
125
+ # All private methods are ignored.
126
+
127
+ base.module_eval do
128
+ def self.action_methods
129
+ classes = self.ancestors.reject do |a|
130
+ [Object, Kernel, Render, Controller, Caching].include?(a)
131
+ end
132
+
133
+ classes.delete(PP::ObjectMixin) if defined?(PP::ObjectMixin)
134
+
135
+ methods = classes.inject([]) do |action_methods, klass|
136
+ action_methods + klass.public_instance_methods(false)
137
+ end
138
+
139
+ # gmosx: add the default action (leave this?)
140
+ # methods << 'index'
141
+
142
+ return methods
143
+ end
144
+ end
145
+
146
+ # A hash containing metadata for the action
147
+ # methods.
148
+
149
+ base.module_eval do
150
+ def self.action_metadata
151
+ # FIXME: improve this.
152
+ @action_metadata ||= {}
153
+ @action_metadata
154
+ end
155
+ end
156
+
157
+ # Use the method_missing hook to compile the actions
158
+ # for this controller.
159
+
160
+ base.module_eval do
161
+ def method_missing(action, *args)
162
+ if Compiler.new.compile(self.class, action)
163
+ send(action, *args)
164
+ else
165
+ super
166
+ end
167
+ end
168
+ end
169
+
170
+ end
179
171
  end
180
172
 
181
173
  # The Controller part in the MVC paradigm. The controller's
@@ -183,18 +175,19 @@ end
183
175
  # contains the Publishable mixin and additional helper mixins.
184
176
 
185
177
  class Controller
186
- include Publishable
187
- include Scaffolding
188
- include Caching
189
-
190
- # The default action.
191
- =begin
192
- def index
193
- print %{
194
- This is the placeholder action is provided as a default for #{self.class.name}.<br />
195
- You probably want to <b>implement your custom action</b> here.
196
- }
197
- end
178
+ include Publishable
179
+ include Scaffolding
180
+ include Caching
181
+ include Markup
182
+
183
+ # The default action.
184
+ =begin
185
+ def index
186
+ print %{
187
+ This is the placeholder action is provided as a default for #{self.class.name}.<br />
188
+ You probably want to <b>implement your custom action</b> here.
189
+ }
190
+ end
198
191
  =end
199
192
  end
200
193
 
data/lib/nitro/cookie.rb CHANGED
@@ -3,40 +3,40 @@ module Nitro
3
3
  # Encapsulates a HTTP Cookie.
4
4
 
5
5
  class Cookie
6
- attr_reader :name
7
- attr_accessor :value, :version
8
- attr_accessor :domain, :path, :secure
9
- attr_accessor :comment, :max_age
10
-
11
- def initialize(name, value)
12
- @name = name
13
- @value = value
14
- @version = 0 # Netscape Cookie
15
- @path = '/' # gmosx: KEEP this!
16
- @domain = @secure = @comment = @max_age =
17
- @expires = @comment_url = @discard = @port = nil
18
- end
19
-
20
- def expires=(t)
21
- @expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
22
- end
23
-
24
- def expires
25
- @expires && Time.parse(@expires)
26
- end
27
-
28
- def to_s
29
- ret = ""
30
- ret << @name << "=" << @value
31
- ret << "; " << "Version=" << @version.to_s if @version > 0
32
- ret << "; " << "Domain=" << @domain if @domain
33
- ret << "; " << "Expires=" << @expires if @expires
34
- ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
35
- ret << "; " << "Comment=" << @comment if @comment
36
- ret << "; " << "Path=" << @path if @path
37
- ret << "; " << "Secure" if @secure
38
- ret
39
- end
6
+ attr_reader :name
7
+ attr_accessor :value, :version
8
+ attr_accessor :domain, :path, :secure
9
+ attr_accessor :comment, :max_age
10
+
11
+ def initialize(name, value)
12
+ @name = name
13
+ @value = value
14
+ @version = 0 # Netscape Cookie
15
+ @path = '/' # gmosx: KEEP this!
16
+ @domain = @secure = @comment = @max_age =
17
+ @expires = @comment_url = @discard = @port = nil
18
+ end
19
+
20
+ def expires=(t)
21
+ @expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
22
+ end
23
+
24
+ def expires
25
+ @expires && Time.parse(@expires)
26
+ end
27
+
28
+ def to_s
29
+ ret = ""
30
+ ret << @name << "=" << @value
31
+ ret << "; " << "Version=" << @version.to_s if @version > 0
32
+ ret << "; " << "Domain=" << @domain if @domain
33
+ ret << "; " << "Expires=" << @expires if @expires
34
+ ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
35
+ ret << "; " << "Comment=" << @comment if @comment
36
+ ret << "; " << "Path=" << @path if @path
37
+ ret << "; " << "Secure" if @secure
38
+ ret
39
+ end
40
40
 
41
41
  end
42
42
 
@@ -10,192 +10,201 @@ module Nitro
10
10
 
11
11
  class Dispatcher
12
12
 
13
- include Router
14
-
15
- unless const_defined?('ROOT')
16
- ROOT = '/'
17
- end
18
-
19
- # The public root directory. The files in this
20
- # directory are published by the web server.
21
-
22
- attr_accessor :public_root
23
-
24
- # The template root directory. By default this
25
- # points to the public root directory to allow
26
- # for PHP/JSP/ASP style programming. But you should
27
- # probably change this to another directory for
28
- # extra security.
29
-
30
- attr_accessor :template_root
31
-
32
- # The controllers map.
33
-
34
- attr_accessor :controllers
35
-
36
- # Create a new Dispatcher.
37
- #
38
- # Input:
39
- #
40
- # [+controllers+]
41
- # Either a hash of controller mappings or a single
42
- # controller that gets mapped to :root.
43
-
44
- def initialize(controllers = nil)
45
- @public_root = 'public'
46
- @template_root = @public_root
47
-
48
- if controllers and controllers.is_a?(Class) and controllers.ancestors.include?(Controller)
49
- controllers = { '/' => controllers }
50
- else
51
- controllers ||= { '/' => SimpleController }
52
- end
53
-
54
- mount(controllers)
55
- end
56
-
57
- # A published object is exposed through a REST interface.
58
- # Only the public non standard methods of the object are
59
- # accessible. Published objects implement the Controller
60
- # part of MVC.
61
- #
62
- # Process the given hash and mount the
63
- # defined classes (controllers).
64
- #
65
- # Input:
66
- #
67
- # [+controllers+]
68
- # A hash representing the mapping of
69
- # mount points to controllers.
70
- #
71
- # === Examples
72
- #
73
- # disp.mount(
74
- # '/' => MainController, # mounts /
75
- # '/users' => UsersController # mounts /users
76
- # )
77
- # disp.publish '/' => MainController
78
-
79
- def add_controller(controllers)
80
- for c in controllers.values
81
- unless (c.ancestors.include?(Controller) or c.ancestors.include?(Publishable))
82
- c.send :include, Publishable
83
- end
84
-
85
- auto_mixin(c)
86
- end
87
-
88
- (@controllers ||= {}).update(controllers)
89
-
90
- update_routes()
91
- end
92
- alias_method :mount, :add_controller
93
- alias_method :publish, :add_controller
94
-
95
- # Call this method to automatically include helpers in the
96
- # Controllers. For each Controller 'XxxController' the
97
- # default helper 'Helper' and the auto mixin
98
- # 'XxxControllerMixin' (if it exists) are included.
99
-
100
- def auto_mixin(c)
101
- c.helper(Helper)
102
-
103
- begin
104
- if helper = Module.by_name("#{c}Mixin")
105
- c.helper(helper)
106
- end
107
- rescue NameError
108
- # The auto helper is not defined.
109
- end
110
- end
111
-
112
- # Update the routes. Typically called after a new
113
- # Controller is mounted.
114
-
115
- def update_routes
116
- @routes = []
117
- @controllers.each do |base, c|
118
- base = '' if base == '/'
119
- c.action_metadata.each do |action, meta|
120
- if route = meta[:route]
121
- @routes << [route, "#{base}/#{action}", *meta.params.keys]
122
- end
123
- end
124
- end
125
- end
126
-
127
- # Processes the path and dispatches to the corresponding
128
- # controller/action pair.
129
- # The base returned contains a trailing '/'.
130
- #
131
- # [+path+]
132
- # The path to dispatch.
133
- #
134
- # [:context]
135
- # The dispatching context.
136
- #
137
- #--
138
- # FIXME: this is a critical method that should be optimized
139
- # watch out for excessive String creation.
140
- #++
141
-
142
- def dispatch(path, context = nil)
143
- path = route(path, context)
144
-
145
- parts = path.split('/')
146
- parts.shift
147
-
148
- case parts.size
149
- when 0
150
- # / -> root.index
151
- base = '/'
152
- klass = controller_class_for(base)
153
- action = 'index'
154
-
155
- when 1
156
- base = "/#{parts[0]}"
157
- if klass = controller_class_for(base)
158
- # controller/ -> controller.index
159
- action = 'index'
160
- else
161
- # action/ -> root.action
162
- base = '/'
163
- klass = controller_class_for(base)
164
- action = parts[0]
165
- end
166
-
167
- else
168
- base = "/#{parts[0]}"
169
- if klass = controller_class_for(base)
170
- # controller/action -> controller.action
171
- parts.shift
172
- action = parts.join('__')
173
- else
174
- # action/ -> root.action
175
- base = '/'
176
- klass = controller_class_for(base)
177
- action = parts.join('__')
178
- end
179
- end
180
-
181
- return klass, "#{action}_action", base
182
- end
183
- alias_method :split_path, :dispatch
184
-
185
- # Get the controller for the given key.
186
- # Also handles reloading of controllers.
187
-
188
- def controller_class_for(key)
189
- klass = @controllers[key]
190
-
191
- if klass and (:full == Rendering.reload)
192
- klass.instance_methods.grep(/(action$)|(template$)/).each do |m|
193
- klass.send(:remove_method, m) rescue nil
194
- end
195
- end
196
-
197
- return klass
198
- end
13
+ include Router
14
+
15
+ # The map.
16
+
17
+ setting :map, :default => { '/' => SimpleController }, :doc => 'The controller map'
18
+
19
+ unless const_defined? :ROOT
20
+ ROOT = '/'
21
+ end
22
+
23
+ # The public root directory. The files in this
24
+ # directory are published by the web server.
25
+
26
+ attr_accessor :public_root
27
+
28
+ # The template root directory. By default this points to the
29
+ # public root directory to allow for PHP/JSP/ASP style
30
+ # programming. But you should probably change this to
31
+ # another directory for extra security.
32
+
33
+ attr_accessor :template_root
34
+
35
+ # The controllers map.
36
+
37
+ attr_accessor :controllers
38
+
39
+ # Create a new Dispatcher.
40
+ #
41
+ # Input:
42
+ #
43
+ # [+controllers+]
44
+ # Either a hash of controller mappings or a single
45
+ # controller that gets mapped to :root.
46
+
47
+ def initialize(controllers = nil)
48
+ @public_root = 'public'
49
+ @template_root = @public_root
50
+
51
+ if controllers and controllers.is_a?(Class) and controllers.ancestors.include?(Controller)
52
+ controllers = { '/' => controllers }
53
+ else
54
+ controllers ||= { '/' => SimpleController }
55
+ end
56
+
57
+ mount(controllers)
58
+ end
59
+
60
+ # A published object is exposed through a REST interface.
61
+ # Only the public non standard methods of the object are
62
+ # accessible. Published objects implement the Controller
63
+ # part of MVC.
64
+ #
65
+ # Process the given hash and mount the
66
+ # defined classes (controllers).
67
+ #
68
+ # Input:
69
+ #
70
+ # [+controllers+]
71
+ # A hash representing the mapping of
72
+ # mount points to controllers.
73
+ #
74
+ # === Examples
75
+ #
76
+ # disp.mount(
77
+ # '/' => MainController, # mounts /
78
+ # '/users' => UsersController # mounts /users
79
+ # )
80
+ # disp.publish '/' => MainController
81
+
82
+ def add_controller(controllers)
83
+ for path, c in controllers
84
+ unless (c.ancestors.include?(Controller) or c.ancestors.include?(Publishable))
85
+ c.send :include, Publishable
86
+ end
87
+
88
+ auto_mixin(c)
89
+
90
+ # Try to setup a template_root if none is defined:
91
+
92
+ unless c.template_root
93
+ c.template_root = "#{Template.root}#{path}".gsub(/\/$/, '')
94
+ end
95
+ end
96
+
97
+ (@controllers ||= {}).update(controllers)
98
+
99
+ update_routes()
100
+ end
101
+ alias_method :mount, :add_controller
102
+ alias_method :publish, :add_controller
103
+ alias_method :map=, :add_controller
104
+
105
+ # Call this method to automatically include helpers in the
106
+ # Controllers. For each Controller 'XxxController' the
107
+ # default helper 'Helper' and the auto mixin
108
+ # 'XxxControllerMixin' (if it exists) are included.
109
+
110
+ def auto_mixin(c)
111
+ c.helper(Helper)
112
+
113
+ begin
114
+ if helper = Module.by_name("#{c}Mixin")
115
+ c.helper(helper)
116
+ end
117
+ rescue NameError
118
+ # The auto helper is not defined.
119
+ end
120
+ end
121
+
122
+ # Update the routes. Typically called after a new
123
+ # Controller is mounted.
124
+
125
+ def update_routes
126
+ @routes = []
127
+ @controllers.each do |base, c|
128
+ base = '' if base == '/'
129
+ c.action_metadata.each do |action, meta|
130
+ if route = meta[:route]
131
+ @routes << [route, "#{base}/#{action}", *meta.params.keys]
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ # Processes the path and dispatches to the corresponding
138
+ # controller/action pair.
139
+ # The base returned contains a trailing '/'.
140
+ #
141
+ # [+path+]
142
+ # The path to dispatch.
143
+ #
144
+ # [:context]
145
+ # The dispatching context.
146
+ #--
147
+ # FIXME: this is a critical method that should be optimized
148
+ # watch out for excessive String creation.
149
+ #++
150
+
151
+ def dispatch(path, context = nil)
152
+ path = route(path, context)
153
+
154
+ parts = path.split('/')
155
+ parts.shift
156
+
157
+ case parts.size
158
+ when 0
159
+ # / -> root.index
160
+ base = '/'
161
+ klass = controller_class_for(base)
162
+ action = 'index'
163
+
164
+ when 1
165
+ base = "/#{parts[0]}"
166
+ if klass = controller_class_for(base)
167
+ # controller/ -> controller.index
168
+ action = 'index'
169
+ else
170
+ # action/ -> root.action
171
+ base = '/'
172
+ klass = controller_class_for(base)
173
+ action = parts[0]
174
+ end
175
+
176
+ else
177
+ base = "/#{parts[0]}"
178
+ if klass = controller_class_for(base)
179
+ # controller/action -> controller.action
180
+ parts.shift
181
+ action = parts.join('__')
182
+ else
183
+ # action/ -> root.action
184
+ base = '/'
185
+ klass = controller_class_for(base)
186
+ action = parts.join('__')
187
+ end
188
+ end
189
+
190
+ return klass, "#{action}_action", base
191
+ end
192
+ alias_method :split_path, :dispatch
193
+
194
+ # Get the controller for the given key.
195
+ # Also handles reloading of controllers.
196
+
197
+ def controller_class_for(key)
198
+ klass = @controllers[key]
199
+
200
+ if klass and Compiler.reload
201
+ klass.instance_methods.grep(/(action$)|(template$)/).each do |m|
202
+ klass.send(:remove_method, m) rescue nil
203
+ end
204
+ end
205
+
206
+ return klass
207
+ end
199
208
 
200
209
  end
201
210