nitro 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +382 -0
  2. data/ProjectInfo +4 -4
  3. data/README +1 -1
  4. data/doc/AUTHORS +15 -15
  5. data/doc/MIGRATION +13 -0
  6. data/doc/RELEASES +102 -0
  7. data/lib/glue/sweeper.rb +1 -1
  8. data/lib/nitro.rb +38 -9
  9. data/lib/nitro/adapter/acgi.rb +1 -3
  10. data/lib/nitro/adapter/cgi.rb +1 -1
  11. data/lib/nitro/adapter/fastcgi.rb +1 -3
  12. data/lib/nitro/adapter/mongrel.rb +8 -6
  13. data/lib/nitro/adapter/webrick.rb +1 -2
  14. data/lib/nitro/cgi.rb +1 -1
  15. data/lib/nitro/compiler.rb +21 -40
  16. data/lib/nitro/compiler/elements.rb +72 -32
  17. data/lib/nitro/compiler/errors.rb +92 -42
  18. data/lib/nitro/compiler/include.rb +47 -17
  19. data/lib/nitro/compiler/morphing.rb +1 -3
  20. data/lib/nitro/compiler/script.rb +2 -2
  21. data/lib/nitro/context.rb +36 -0
  22. data/lib/nitro/controller.rb +140 -31
  23. data/lib/nitro/dispatcher.rb +27 -28
  24. data/lib/nitro/element.rb +52 -15
  25. data/lib/nitro/flash.rb +44 -0
  26. data/lib/nitro/helper/buffer.rb +0 -2
  27. data/lib/nitro/helper/form.rb +2 -2
  28. data/lib/nitro/helper/form/controls.rb +14 -3
  29. data/lib/nitro/helper/pager.rb +1 -1
  30. data/lib/nitro/helper/table.rb +4 -3
  31. data/lib/nitro/helper/xml.rb +1 -1
  32. data/lib/nitro/part.rb +20 -0
  33. data/lib/nitro/render.rb +44 -5
  34. data/lib/nitro/router.rb +81 -0
  35. data/lib/nitro/scaffolding.rb +24 -23
  36. data/lib/nitro/server.rb +12 -1
  37. data/lib/nitro/server/runner.rb +12 -0
  38. data/lib/nitro/session.rb +3 -12
  39. data/lib/nitro/session/drb.rb +2 -5
  40. data/lib/nitro/session/file.rb +2 -2
  41. data/lib/nitro/session/memcached.rb +14 -0
  42. data/lib/nitro/session/memory.rb +3 -26
  43. data/lib/nitro/session/og.rb +1 -1
  44. data/lib/nitro/test/assertions.rb +1 -1
  45. data/lib/nitro/test/context.rb +8 -2
  46. data/lib/nitro/test/testcase.rb +16 -7
  47. data/proto/public/error.xhtml +58 -21
  48. data/proto/public/js/controls.js +60 -15
  49. data/proto/public/js/dragdrop.js +105 -16
  50. data/proto/public/js/effects.js +19 -12
  51. data/proto/public/js/scriptaculous.js +1 -1
  52. data/proto/public/js/slider.js +2 -2
  53. data/proto/public/js/unittest.js +29 -20
  54. data/proto/public/scaffold/edit.xhtml +1 -1
  55. data/proto/public/scaffold/index.xhtml +2 -2
  56. data/proto/public/scaffold/list.xhtml +2 -2
  57. data/proto/public/scaffold/new.xhtml +1 -1
  58. data/proto/public/scaffold/search.xhtml +1 -1
  59. data/src/part/admin/controller.rb +5 -5
  60. data/src/part/admin/template/index.xhtml +2 -2
  61. data/test/nitro/compiler/tc_compiler.rb +23 -0
  62. data/test/nitro/helper/tc_table.rb +35 -0
  63. data/test/nitro/tc_cgi.rb +1 -1
  64. data/test/nitro/tc_controller.rb +3 -3
  65. data/test/nitro/tc_controller_aspect.rb +2 -0
  66. data/test/nitro/tc_dispatcher.rb +10 -1
  67. data/test/nitro/tc_flash.rb +14 -0
  68. data/test/nitro/tc_router.rb +58 -0
  69. data/test/nitro/tc_session.rb +26 -9
  70. metadata +13 -12
  71. data/lib/nitro/routing.rb +0 -41
  72. data/test/nitro/caching/tc_stores.rb +0 -17
  73. data/test/nitro/tc_table.rb +0 -66
@@ -58,6 +58,50 @@ module Nitro
58
58
 
59
59
  (@dirty.keys - keys).each { |k| @dirty.delete k }
60
60
  end
61
+
62
+ # :section: Helpers
63
+
64
+ # Push a value in an array flash variable.
65
+ #
66
+ # === Example
67
+ #
68
+ # flash.push :errors, 'This is the first error'
69
+ # flash.push :errors, 'This is the second error'
70
+ #
71
+ # flash[:errors] # => []
72
+
73
+ def push(key, value)
74
+ if value.is_a? Array
75
+ (self[key] ||= []).concat(value)
76
+ else
77
+ (self[key] ||= []) << value
78
+ end
79
+ end
80
+
81
+ # Pop a value from an array flash variable.
82
+
83
+ def pop(key)
84
+ if arr = self[key]
85
+ if arr.is_a? Array
86
+ return arr.pop
87
+ else
88
+ return arr
89
+ end
90
+ end
91
+ return nil
92
+ end
93
+
94
+ # Join helper
95
+
96
+ def join(key, sep = ', ')
97
+ value = self[key]
98
+
99
+ if value.is_a? Array
100
+ return value.join(sep)
101
+ else
102
+ return value
103
+ end
104
+ end
61
105
 
62
106
  private
63
107
 
@@ -1,5 +1,3 @@
1
- require 'glue/attribute'
2
-
3
1
  module Nitro
4
2
 
5
3
  # The output buffering mixin. Provides php-style output
@@ -118,7 +118,7 @@ module FormHelper
118
118
  unless options[:all]
119
119
  next if prop.symbol == obj.class.primary_key.symbol or prop[:control] == :none or prop[:relation]
120
120
  end
121
- control = Control.fetch(obj, prop, options).render
121
+ control = Form::Control.fetch(obj, prop, options).render
122
122
  str << FormBuilder.element(prop, control)
123
123
  end
124
124
  end
@@ -130,7 +130,7 @@ module FormHelper
130
130
  unless options[:all]
131
131
  next if rel[:control] == :none
132
132
  end
133
- control = Control.fetch(obj, rel, options).render
133
+ control = Form::Control.fetch(obj, rel, options).render
134
134
  str << FormBuilder.element(rel, control)
135
135
  end
136
136
  end
@@ -4,6 +4,8 @@ module Nitro
4
4
 
5
5
  # :section: Property controls.
6
6
 
7
+ module Form
8
+
7
9
  # A Form control.
8
10
 
9
11
  class Control
@@ -174,7 +176,7 @@ class ArrayControl < Control
174
176
  else
175
177
  removable = values.size != 1 ? true : false
176
178
  values.each do |item|
177
- str << emit_array_element(:selected => item.pk)
179
+ str << emit_array_element()
178
180
  end
179
181
  end
180
182
  str << emit_container_end
@@ -182,7 +184,6 @@ class ArrayControl < Control
182
184
 
183
185
  def emit_array_element(options={})
184
186
  removable = options.fetch(:removable, true)
185
- selected = options.fetch(:selected, nil)
186
187
  %{
187
188
  <div>
188
189
  <input type="text" id="#{prop.symbol}_ctl" name="#{prop.symbol}[]" value="#{value}"#{emit_style}#{emit_disabled} />
@@ -192,6 +193,14 @@ class ArrayControl < Control
192
193
  }
193
194
  end
194
195
 
196
+ def emit_container_start
197
+ %{<div class="array_container">}
198
+ end
199
+
200
+ def emit_container_end
201
+ %{</div>}
202
+ end
203
+
195
204
  def emit_js
196
205
  %{
197
206
  <script type="text/javascript">
@@ -210,7 +219,7 @@ class ArrayControl < Control
210
219
  node.getElementsByTagName('input')[0].removeAttribute('disabled');
211
220
  if(container.lastChild==ctl) container.appendChild(node);
212
221
  else container.insertBefore(node, ctl.nextSibling);
213
- if(container.childNodes.length>1) container.getElementsByTagName('input')[0].disabled='';
222
+ if(container.childNodes.length>1) container.getElementsByTagName('input')[1].disabled='';
214
223
  }
215
224
  </script>
216
225
  }
@@ -373,5 +382,7 @@ end
373
382
 
374
383
  end
375
384
 
385
+ end
386
+
376
387
  # * George Moschovitis <gm@navel.gr>
377
388
  # * Chris Farmiloe <chris.farmiloe@farmiloe.com>
@@ -233,7 +233,7 @@ private
233
233
 
234
234
  def target_uri(page)
235
235
  params = { @key => page }
236
- return UriUtils.update_query_string(@request.uri.to_s, params)
236
+ return Glue::UriUtils.update_query_string(@request.uri.to_s, params)
237
237
  end
238
238
 
239
239
  end
@@ -53,6 +53,7 @@ module TableHelper
53
53
  # A hash of options.
54
54
  #
55
55
  # :id = id of the component.
56
+ # :class = class of the component
56
57
  # :headers = an array of the header values
57
58
  # :values = an array of arrays.
58
59
  # :order = options hash (:left, :right, :asc_pic, :desc_pic, :values)
@@ -62,6 +63,7 @@ module TableHelper
62
63
  def table(options)
63
64
  str = '<table'
64
65
  str << %| id="#{options[:id]}"| if options[:id]
66
+ str << %| class="#{options[:class]}"| if options[:class]
65
67
  str << '>'
66
68
 
67
69
  str << table_rows(options)
@@ -73,7 +75,6 @@ module TableHelper
73
75
  # [+options+]
74
76
  # A hash of options.
75
77
  #
76
- # :id = id of the component.
77
78
  # :headers = an array of the header values
78
79
  # :values = an array of arrays.
79
80
  # :order = options hash (:left, :right, :asc_pic, :desc_pic, :values)
@@ -85,7 +86,7 @@ module TableHelper
85
86
  options[:values] = options[:values] || options[:items] || options[:rows]
86
87
 
87
88
  str = ''
88
- str << table_header(options)
89
+ str << table_header(options) if options[:headers]
89
90
  str << table_footer(options) if options[:footers]
90
91
 
91
92
  items = options[:values]
@@ -228,7 +229,7 @@ module TableHelper
228
229
  params = { TableHelper.order_by_key => order_by,
229
230
  TableHelper.order_direction_key => direction }
230
231
 
231
- return UriUtils.update_query_string(request.uri.to_s, params)
232
+ return Glue::UriUtils.update_query_string(request.uri.to_s, params)
232
233
  end
233
234
 
234
235
  def create_tbody?(options)
@@ -98,7 +98,7 @@ end
98
98
  # functionality. Utilizes duck typing to redirect
99
99
  # output to a target buffer.
100
100
 
101
- class XmlBuilder
101
+ class Glue::XmlBuilder
102
102
  include XmlHelper
103
103
 
104
104
  # The target receives the generated xml,
@@ -0,0 +1,20 @@
1
+ module Nitro
2
+
3
+ # A part is a module of reusable functionality encapsulated as
4
+ # a mini site/app. You can require (include) multiple parts in
5
+ # your application. A part is in essence a high level component.
6
+
7
+ class Part
8
+
9
+ # Require (include) a part in the current application.
10
+
11
+ def self.require(name)
12
+ Logger.debug "Requiring part '#{name}'."
13
+ Kernel.require "part/#{name}/run.rb"
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ # * George Moschovitis <gm@navel.gr>
@@ -7,7 +7,6 @@ require 'facet/string/blank'
7
7
  require 'glue/attribute'
8
8
  require 'glue/settings'
9
9
  require 'glue/template'
10
- require 'glue/builder'
11
10
  require 'glue/builder/xml'
12
11
 
13
12
  require 'nitro/helper/xhtml'
@@ -157,6 +156,8 @@ private
157
156
  @out.flush if @out.is_a?(IO)
158
157
  end
159
158
 
159
+ # :section: Redirection methods.
160
+
160
161
  # Send a redirect response.
161
162
  #
162
163
  # If the url starts with '/' it is considered absolute, else
@@ -184,6 +185,8 @@ private
184
185
  redirect("#{@context.referer}#{postfix}", status)
185
186
  end
186
187
  alias_method :redirect_to_referer, :redirect_referer
188
+ alias_method :redirect_referrer, :redirect_referer
189
+ alias_method :redirect_to_referrer, :redirect_referer
187
190
 
188
191
  # Redirect to home.
189
192
 
@@ -192,6 +195,42 @@ private
192
195
  end
193
196
  alias_method :redirect_to_home, :redirect_home
194
197
 
198
+ # :section: Seaside style call/answer methods.
199
+
200
+ # Call redirects to the given url but push the original
201
+ # url in a callstack, so that the target can return by
202
+ # executing answer.
203
+ #
204
+ # === Example
205
+ #
206
+ # caller:
207
+ # color, type = call('utils/select_color')
208
+ #
209
+ # target:
210
+ # answer(color, type)
211
+ #--
212
+ # FIXME: dont use yet, you have to encode the branch to
213
+ # make this safe for use.
214
+ #++
215
+
216
+ def call(url, status = 303)
217
+ (session[:CALL_STACK] ||= {}) << request.uri
218
+ redirect(url, status)
219
+ end
220
+
221
+ # Returns from a call by poping the callstack.
222
+ #--
223
+ # FIXME: don't use yet.
224
+ #++
225
+
226
+ def answer(index = 0, status = 303)
227
+ if stack = session[:CALL_STACK] and not stack.empty?
228
+ redirect(stack.pop, status)
229
+ else
230
+ raise 'Cannot answer, call stack is empty'
231
+ end
232
+ end
233
+
195
234
  # Log a rendering error.
196
235
 
197
236
  def log_error(error, path, full = true)
@@ -226,16 +265,16 @@ private
226
265
  def render_template(filename)
227
266
  filename = "#{filename}.xhtml" unless filename =~ /\.xhtml$/
228
267
  template = File.read("#{template_root}/#{filename}")
229
- Template.process_template(template, '@out', binding)
268
+ Glue::Template.process_template(template, '@out', binding)
230
269
  end
231
270
 
232
271
  # Access the programmatic renderer (builder).
233
272
 
234
273
  def build(&block)
235
274
  if block.arity == 1
236
- yield XmlBuilder.new(@out)
275
+ yield Glue::XmlBuilder.new(@out)
237
276
  else
238
- XmlBuilder.new(@out).instance_eval(&block)
277
+ Glue::XmlBuilder.new(@out).instance_eval(&block)
239
278
  end
240
279
  end
241
280
 
@@ -243,7 +282,7 @@ private
243
282
  # output buffer.
244
283
 
245
284
  def builder
246
- XmlBuilder.new(@out)
285
+ Glue::XmlBuilder.new(@out)
247
286
  end
248
287
 
249
288
  # A Helper class to access rendering mixins. Useful to avoid
@@ -0,0 +1,81 @@
1
+ module Nitro
2
+
3
+ # Router mixin. Typically used to generate 'nice' urls.
4
+ # Nice urls apart from looking more beautiful are considered (?)
5
+ # more Search Engine friendly.
6
+ #
7
+ # However, due to the power of Nitro's intelligent dispatching
8
+ # mechanism, routing is almost never used! It is only needed
9
+ # for really special urls.
10
+ #
11
+ # === Example
12
+ #
13
+ # r.add_route(%r{rewritten/url/(.*)}, :controller => IdController, :action => :register, :param => :name)
14
+ # r.add_route(%r{another/zelo/(.*)/(.*)}, :controller => AdminController, :action => :kick, :params => [:name, :age])
15
+ # r.add_route(%r{cool/(.*)_(.*).html}, :controller => AdminController, :action => :long, :params => [:name, :age])
16
+
17
+ module Router
18
+
19
+ # Strip the beginning of the path, used by cgi adapter.
20
+
21
+ setting :strip_path, :default => nil, :doc => 'Strip the beginning of the path, used by cgi adapter'
22
+
23
+ # The route table maps 'nice URLs' to real URLs that
24
+ # can be handled by the Dispatcher.
25
+
26
+ attr_accessor :routes
27
+
28
+ # Decodes a url to a [controller, action, params] tupple.
29
+ # Returns false if no decoding is possible.
30
+
31
+ def decode_route(url)
32
+ for rule, options in @routes
33
+ if md = url.match(rule)
34
+ params = nil
35
+ if param_names = options[:params] || options[:param]
36
+ param_names = [ param_names ] unless param_names.is_a?(Array)
37
+ params = {}
38
+ md.captures.each_with_index do |val, idx|
39
+ params[param_names[idx].to_s] = val
40
+ end
41
+ end
42
+ return options[:controller], options[:action], params
43
+ end
44
+ end
45
+ return false
46
+ end
47
+ alias_method :route, :decode_route
48
+
49
+ # Encodes a [controller, action, params] tupple into a url.
50
+ # Returns false if no encoding is possible.
51
+
52
+ def encode_route(controller, action, *params)
53
+ if route = @routes.find { |r| (r.last[:controller] == controller) and (r.last[:action] == action) }
54
+ rule, options = *route
55
+ url = rule.source
56
+
57
+ (params.size / 2).times do |i|
58
+ val = params[i + i + 1]
59
+ url.sub!(/\(.*?\)/, val.to_s)
60
+ end
61
+
62
+ return url
63
+ end
64
+ return false
65
+ end
66
+
67
+ # Add a route to the routing table.
68
+
69
+ def add_route(rule, options)
70
+ (@routes ||= []) << [rule, options]
71
+ end
72
+ alias_method :<<, :add_route
73
+
74
+ def add_routes
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ # * George Moschovitis <gm@navel.gr>
@@ -33,12 +33,16 @@ module Scaffolding
33
33
  base.extend(ClassMethods)
34
34
  end
35
35
 
36
- def self.class_to_name(klass)
37
- klass.to_s.demodulize.underscore.downcase
36
+ def self.class_to_method(klass)
37
+ klass.name.underscore.gsub('::','__').downcase
38
38
  end
39
39
 
40
40
  def self.class_to_list(klass)
41
- klass.to_s.demodulize.underscore.downcase.plural
41
+ class_to_method(klass).plural
42
+ end
43
+
44
+ def self.class_to_path(klass)
45
+ klass.name.underscore.gsub('::','/').downcase
42
46
  end
43
47
 
44
48
  #--
@@ -66,10 +70,6 @@ module Scaffolding
66
70
  @base__ = "#{@base}__" if @base
67
71
  end
68
72
 
69
- def class_to_name(klass)
70
- klass.to_s.demodulize.underscore.downcase
71
- end
72
-
73
73
  #--
74
74
  # A helper that defines a class method.
75
75
  #++
@@ -160,28 +160,28 @@ module Scaffolding
160
160
 
161
161
  def scaffold_controller
162
162
  define_controller_action 'index', %{
163
- #{Aspects.gen_advice_code(:scaffold_index, @controller.advices, :pre)}
163
+ #{Glue::Aspects.gen_advice_code(:scaffold_index, @controller.advices, :pre)}
164
164
  @list, @pager = paginate(@klass, :per_page => Scaffolding.per_page)
165
165
  }
166
166
 
167
167
  define_controller_action 'list', %{
168
- #{Aspects.gen_advice_code(:scaffold_list, @controller.advices, :pre)}
168
+ #{Glue::Aspects.gen_advice_code(:scaffold_list, @controller.advices, :pre)}
169
169
  @list, @pager = paginate(@klass, :per_page => Scaffolding.per_page)
170
170
  }
171
171
 
172
172
  define_controller_action 'view(oid)', %{
173
- #{Aspects.gen_advice_code(:scaffold_view, @controller.advices, :pre)}
173
+ #{Glue::Aspects.gen_advice_code(:scaffold_view, @controller.advices, :pre)}
174
174
  @obj = @klass[oid]
175
175
  }
176
176
 
177
177
  define_controller_action 'new(all = false)', %{
178
- #{Aspects.gen_advice_code(:scaffold_new, @controller.advices, :pre)}
178
+ #{Glue::Aspects.gen_advice_code(:scaffold_new, @controller.advices, :pre)}
179
179
  @obj = @klass.new
180
180
  @all = all
181
181
  }
182
182
 
183
183
  define_controller_action 'edit(oid = nil, all = false)', %{
184
- #{Aspects.gen_advice_code(:scaffold_edit, @controller.advices, :pre)}
184
+ #{Glue::Aspects.gen_advice_code(:scaffold_edit, @controller.advices, :pre)}
185
185
  @obj = @klass[oid]
186
186
  @all = all
187
187
  }
@@ -195,18 +195,19 @@ module Scaffolding
195
195
  else
196
196
  obj = request.fill(@klass.create, :assign_relations => true, :preprocess => true)
197
197
  end
198
- #{Aspects.gen_advice_code(:scaffold_save, @controller.advices, :pre)}
198
+ #{Glue::Aspects.gen_advice_code(:scaffold_save, @controller.advices, :pre)}
199
199
  unless obj.valid?
200
200
  flash[:ERRORS] = obj.errors
201
201
  redirect_to_referer
202
202
  end
203
203
  obj.save
204
- #{Aspects.gen_advice_code(:scaffold_save, @controller.advices, :post)}
204
+ oid = obj.pk
205
+ #{Glue::Aspects.gen_advice_code(:scaffold_save, @controller.advices, :post)}
205
206
  redirect '#{action_path(:list)}'
206
207
  }
207
208
 
208
209
  define_controller_action 'search(query)', %{
209
- #{Aspects.gen_advice_code(:scaffold_search, @controller.advices, :pre)}
210
+ #{Glue::Aspects.gen_advice_code(:scaffold_search, @controller.advices, :pre)}
210
211
  @query = query
211
212
  if @klass.respond_to? :search
212
213
  @list = #@klass.search(query)
@@ -214,9 +215,9 @@ module Scaffolding
214
215
  }
215
216
 
216
217
  define_controller_action 'delete(oid)', %{
217
- #{Aspects.gen_advice_code(:scaffold_delete, @controller.advices, :pre)}
218
+ #{Glue::Aspects.gen_advice_code(:scaffold_delete, @controller.advices, :pre)}
218
219
  @klass.delete(oid)
219
- #{Aspects.gen_advice_code(:scaffold_delete, @controller.advices, :post)}
220
+ #{Glue::Aspects.gen_advice_code(:scaffold_delete, @controller.advices, :post)}
220
221
  redirect_to_referer
221
222
  }
222
223
 
@@ -225,18 +226,18 @@ module Scaffolding
225
226
  for rel in @klass.relations
226
227
  define_controller_action "remove_#{rel.target_singular_name}(oid, rid)", %{
227
228
  obj = @klass[oid]
228
- #{Aspects.gen_advice_code(:scaffold_remove_relation, @controller.advices, :pre)}
229
+ #{Glue::Aspects.gen_advice_code(:scaffold_remove_relation, @controller.advices, :pre)}
229
230
  rob = #{rel.target_class}[rid]
230
231
  obj.#{rel.name}.remove(rob)
231
- #{Aspects.gen_advice_code(:scaffold_remove_relation, @controller.advices, :post)}
232
+ #{Glue::Aspects.gen_advice_code(:scaffold_remove_relation, @controller.advices, :post)}
232
233
  redirect_to_referer
233
234
  }
234
235
  define_controller_action "delete_#{rel.target_singular_name}(oid, rid)", %{
235
- #{Aspects.gen_advice_code(:scaffold_delete_relation, @controller.advices, :pre)}
236
+ #{Glue::Aspects.gen_advice_code(:scaffold_delete_relation, @controller.advices, :pre)}
236
237
  obj = @klass[oid]
237
238
  rob = #{rel.target_class}[rid]
238
239
  obj.#{rel.name}.delete(rob)
239
- #{Aspects.gen_advice_code(:scaffold_delete_relation, @controller.advices, :post)}
240
+ #{Glue::Aspects.gen_advice_code(:scaffold_delete_relation, @controller.advices, :post)}
240
241
  redirect_to_referer
241
242
  }
242
243
  end
@@ -298,9 +299,9 @@ module Scaffolding
298
299
  #++
299
300
 
300
301
  def scaffold(klass, options = {})
301
- o = {
302
+ o = scaffolding_classes[klass] || {
302
303
  :pk => 'oid',
303
- :name => Scaffolding.class_to_name(klass),
304
+ :name => Scaffolding.class_to_method(klass),
304
305
  :plural_name => Scaffolding.class_to_list(klass),
305
306
  :mount => true
306
307
  }