nitro 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG +109 -1250
  2. data/INSTALL +3 -2
  3. data/README +5 -4
  4. data/Rakefile +1 -1
  5. data/bin/nitrogen +1 -1
  6. data/doc/AUTHORS +20 -6
  7. data/doc/CHANGELOG.3 +1314 -0
  8. data/doc/RELEASES +90 -0
  9. data/lib/nitro.rb +6 -17
  10. data/lib/nitro/adapter/cgi.rb +11 -0
  11. data/lib/nitro/adapter/webrick.rb +7 -1
  12. data/lib/nitro/caching/stores.rb +4 -6
  13. data/lib/nitro/compiler.rb +1 -1
  14. data/lib/nitro/compiler/errors.rb +1 -1
  15. data/lib/nitro/context.rb +11 -4
  16. data/lib/nitro/controller.rb +0 -3
  17. data/lib/nitro/cookie.rb +4 -3
  18. data/lib/nitro/dispatcher.rb +7 -1
  19. data/lib/nitro/dispatcher/nice.rb +6 -1
  20. data/lib/nitro/element.rb +2 -2
  21. data/lib/nitro/mixin/benchmark.rb +16 -0
  22. data/lib/nitro/mixin/form.rb +171 -55
  23. data/lib/nitro/mixin/rss.rb +1 -1
  24. data/lib/nitro/mixin/xhtml.rb +91 -4
  25. data/lib/nitro/render.rb +25 -6
  26. data/lib/nitro/request.rb +13 -3
  27. data/lib/nitro/scaffold.rb +91 -68
  28. data/lib/nitro/scaffold/relations.rb +54 -0
  29. data/lib/nitro/server.rb +8 -5
  30. data/lib/nitro/server/runner.rb +4 -3
  31. data/lib/nitro/service.rb +3 -1
  32. data/lib/nitro/service/xmlrpc.rb +1 -1
  33. data/lib/nitro/session.rb +69 -51
  34. data/lib/nitro/session/drb.rb +5 -7
  35. data/lib/nitro/session/drbserver.rb +4 -6
  36. data/lib/nitro/session/memory.rb +4 -6
  37. data/lib/part/admin.rb +6 -0
  38. data/lib/part/admin/controller.rb +24 -0
  39. data/lib/part/admin/skin.rb +21 -0
  40. data/lib/part/admin/template/index.xhtml +9 -0
  41. data/proto/public/js/cookies.js +122 -0
  42. data/proto/public/scaffold/edit.xhtml +4 -0
  43. data/proto/public/scaffold/form.xhtml +7 -0
  44. data/proto/public/scaffold/list.xhtml +15 -0
  45. data/proto/public/scaffold/new.xhtml +4 -0
  46. data/proto/public/scaffold/view.xhtml +0 -0
  47. data/proto/script/benchmark +19 -0
  48. data/test/nitro/adapter/tc_cgi.rb +32 -2
  49. data/test/nitro/mixin/tc_xhtml.rb +6 -0
  50. data/test/nitro/tc_dispatcher.rb +0 -17
  51. data/test/nitro/tc_render.rb +58 -0
  52. data/test/nitro/tc_server.rb +2 -0
  53. data/test/nitro/tc_session.rb +16 -0
  54. metadata +104 -85
@@ -1,7 +1,7 @@
1
1
  require 'cgi'
2
2
  require 'rss/0.9'
3
3
 
4
- require 'facet/string/first_char'
4
+ require 'nano/string/first_char'
5
5
 
6
6
  require 'nitro/mixin/markup'
7
7
 
@@ -5,6 +5,8 @@ module Nitro
5
5
 
6
6
  module XhtmlMixin
7
7
 
8
+ private
9
+
8
10
  # Render select options. The parameter is a hash of options.
9
11
  #
10
12
  # [+labels+]
@@ -13,14 +15,17 @@ module XhtmlMixin
13
15
  # [+values+]
14
16
  # The corresponding values.
15
17
  #
16
- # [+selected+]
18
+ # [+labels_values+]
19
+ # Use when labels == values.
20
+ #
21
+ # [+selected+]
17
22
  # The value of the selected option.
18
23
  #
19
24
  # === Examples
20
25
  #
21
26
  # labels = ['Male', 'Female']
22
27
  # o.select(:name => 'sex') {
23
- # o.options(:labels => labels, :selected => 1)
28
+ # o.options(:labels => labels, :selected => 1)
24
29
  # }
25
30
  #
26
31
  # or
@@ -29,11 +34,14 @@ module XhtmlMixin
29
34
  # #{build :options, :labels => labels, :values => [..], :selected => 1}
30
35
 
31
36
  def options(options = {})
32
- if labels = options[:labels]
37
+ if labels = options[:labels] || options[:labels_values]
33
38
  str = ''
34
39
 
35
40
  unless values = options[:values]
36
- values = (0...labels.size).to_a
41
+ if values = options[:labels_values]
42
+ else
43
+ values = (0...labels.size).to_a
44
+ end
37
45
  end
38
46
 
39
47
  selected = (options[:selected] || -1).to_i
@@ -67,6 +75,85 @@ module XhtmlMixin
67
75
 
68
76
  return str
69
77
  end
78
+
79
+ # Render a date select. Override to customize this.
80
+ #
81
+ # === Example
82
+ #
83
+ # #{date_select date, :name => 'brithday'}
84
+
85
+ def date_select(date, options = {})
86
+ raise 'No name provided to date_select' unless name = options[:name]
87
+ date ||= Time.now
88
+ %{
89
+ <select id="#{name}.day" name="#{name}.day">
90
+ #{options(:labels_values => (1..31).to_a, :selected => date.day)}
91
+ </select>&nbsp;
92
+ <select id="#{name}.month" name="#{name}.month">
93
+ #{options(:labels => Date::MONTHNAMES, :values => (1..12).to_a, :selected => (date.month+1))}
94
+ </select>&nbsp;
95
+ <select id="#{name}.year" name="#{name}.year">
96
+ #{options(:labels_values => ((Time.now.year-10)..(Time.now.year+10)).to_a, :selected => date.year)}
97
+ </select>}
98
+ end
99
+
100
+ # Render a time select. Override to customize this.
101
+
102
+ def time_select(time, options = {})
103
+ raise 'No name provided to time_select' unless name = options[:name]
104
+ time ||= Time.now
105
+ %{
106
+ <select id="#{name}.hour" name="#{name}.hour">
107
+ #{options(:labels_values => (1..60).to_a, :selected => time.hour)}
108
+ </select>&nbsp;
109
+ <select id="#{name}.min" name="#{name}.min">
110
+ #{options(:labels_values => (1..60).to_a, :selected => time.min)}
111
+ </select>}
112
+ end
113
+
114
+ # Render a datetime select. Override to customize this.
115
+
116
+ def datetime_select(time, options)
117
+ date_select(time, options) + '&nbsp;at&nbsp;' + time_select(time, options)
118
+ end
119
+
120
+
121
+ # gmosx: keep the leading / to be IE friendly.
122
+
123
+ def js_popup(options = {})
124
+ o = {
125
+ :width => 320,
126
+ :height => 240,
127
+ :title => 'Popup',
128
+ :resizable => false,
129
+ :scrollbars => false,
130
+ }.merge(options)
131
+
132
+ poptions = (o[:resizable] ? 'resizable=yes' : 'resizable=no')
133
+ poptions << (o[:scrollbars] ? 'scrollbars=yes' : 'scrollbars=no')
134
+
135
+ url = o[:url] || o[:uri]
136
+
137
+ %[javascript: var pwl = (screen.width - #{o[:width]}) / 2; var pwt = (screen.height - #{o[:height]}) / 2; window.open('#{url}', '#{o[:title]}', 'width=#{o[:width]},height=#{o[:height]},top='+pwt+',left='+pwl+', #{poptions}'); return false;"]
138
+ end
139
+
140
+ # === Example
141
+ #
142
+ # <a href="#" #{onclick_popup 'add-comment', :scrollbars => true}>Hello</a>
143
+
144
+ def onclick_popup(options = {})
145
+ %|onclick="#{js_popup(options)}"|
146
+ end
147
+
148
+ # Emit a link that spawns a popup window.
149
+ #
150
+ # === Example
151
+ #
152
+ # <a href="#" #{onclick :text => 'Hello', :url => 'add-comment', :scrollbars => true}>Hello</a>
153
+
154
+ def popup(options = {})
155
+ %|<a href="#" #{onclick_popup(options)}>#{options[:text] || 'Popup'}</a>|
156
+ end
70
157
 
71
158
  end
72
159
 
@@ -1,11 +1,9 @@
1
1
  require 'singleton'
2
2
  require 'sync'
3
3
 
4
- require 'facet/string/blank%3F'
4
+ require 'nano/string/blank%3F'
5
5
 
6
6
  require 'glue/attribute'
7
- require 'glue/misc'
8
- require 'glue/object'
9
7
  require 'glue/settings'
10
8
  require 'glue/template'
11
9
  require 'glue/builder'
@@ -76,8 +74,15 @@ module Render
76
74
  # The name of the currently executing action.
77
75
 
78
76
  attr_accessor :action_name
77
+
78
+ # The base url for this render.
79
+
80
+ attr_accessor :base
79
81
 
80
82
  # The name of the current controller.
83
+ #--
84
+ # gmosx: Needed for WEE. Will be deprecated.
85
+ #++
81
86
 
82
87
  attr_accessor :controller_name
83
88
 
@@ -88,17 +93,24 @@ module Render
88
93
 
89
94
  def initialize(context, base = nil)
90
95
  @request = @response = @context = context
91
- @controller_name = base
96
+ @controller_name = @base = base
92
97
  @out = context.out
93
98
  end
94
99
 
95
100
  # Renders the action denoted by path. The path
96
101
  # is resolved by the dispatcher to get the correct
97
102
  # controller.
103
+ #
104
+ # Both relative and absolute paths are supported. Relative
105
+ # paths are converted to absolute by prepending the @base
106
+ # path of the controller.
98
107
 
99
108
  def render(path)
109
+ # Convert relative paths to absolute paths.
110
+ path = "#@base/#{path}" unless path =~ /^\//
111
+
100
112
  Logger.debug "Rendering '#{path}'." if $DBG
101
-
113
+
102
114
  klass, action, base = @context.dispatcher.dispatch(path, @context)
103
115
 
104
116
  # FIXME:
@@ -129,10 +141,17 @@ module Render
129
141
  private
130
142
 
131
143
  # Send a redirect response.
144
+ #
145
+ # If the url starts with '/' it is considered absolute, else
146
+ # the url is considered relative to the current controller and
147
+ # the controller base is prepended.
132
148
 
133
149
  def redirect(url, status = 303)
134
150
  url = url.to_s
135
- url = "#{@context.host_url}/#{url.gsub(/^\//, '')}" unless url =~ /http/
151
+ unless url =~ /^http/
152
+ url = "#@base/#{url}" unless url =~ /^\//
153
+ url = "#{@context.host_url}/#{url.gsub(/^\//, '')}"
154
+ end
136
155
 
137
156
  @context.status = status
138
157
  @context.out = "<html><a href=\"#{url}\">#{url}</a>.</html>\n"
@@ -30,13 +30,14 @@ module Request
30
30
  # The request protocol.
31
31
 
32
32
  def protocol
33
- 443 == port ? 'https://' : 'http://'
33
+ @headers['HTTPS'] == 'on' ? 'https://' : 'http://'
34
34
  end
35
35
 
36
36
  # Is this an ssl request?
37
37
 
38
38
  def ssl?
39
- 443 == port
39
+ # 443 == port
40
+ @headers['HTTPS'] == 'on'
40
41
  end
41
42
 
42
43
  # The request uri.
@@ -208,8 +209,9 @@ module Request
208
209
  # The host url.
209
210
 
210
211
  def host_url
211
- "http://#{host}"
212
+ "#{protocol}#{host}"
212
213
  end
214
+ alias_method :server_url, :host_url
213
215
 
214
216
  # The raw data of the request.
215
217
  # Useful to implement Webservices.
@@ -243,8 +245,16 @@ module Request
243
245
  def fetch(param, default = nil)
244
246
  @params.fetch(param, default)
245
247
  end
248
+
249
+ # Check if a param is available.
250
+
251
+ def has_key?(key)
252
+ @params.has_key?(key)
253
+ end
254
+ alias_method :has_param?, :has_key?
246
255
  end
247
256
 
248
257
  end
249
258
 
250
259
  # * George Moschovitis <gm@navel.gr>
260
+ # * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
@@ -1,14 +1,12 @@
1
- require 'facet/string/singular'
2
- require 'facet/string/demodulize'
3
- require 'facet/string/underscore'
1
+ require 'mega/orm_support'
4
2
 
5
3
  require 'nitro/compiler'
4
+ require 'nitro/mixin/form'
6
5
 
7
6
  module Nitro
8
7
 
9
8
  # The scaffolder adds default actions to a Controller.
10
9
  #
11
- # WARNING: This code is slightly outdated.
12
10
  #--
13
11
  # FIXME: handle controller base in generated routes.
14
12
  # FIXME: better handle templates (check if action exists).
@@ -18,9 +16,35 @@ module Scaffolding
18
16
 
19
17
  def self.append_features(base) # :nodoc:
20
18
  super
19
+ base.send :include, FormMixin
21
20
  base.extend(ClassMethods)
22
21
  end
23
22
 
23
+ def self.class_to_name(klass)
24
+ klass.to_s.demodulize.underscore.downcase
25
+ end
26
+
27
+ def self.class_to_list(klass)
28
+ klass.to_s.demodulize.underscore.downcase.plural
29
+ end
30
+
31
+ def self.compile_scaffold_action(compiler, controller, action, suffix)
32
+ unless compiler.template_for_action("#{action}#{suffix}", controller.template_root)
33
+ source = File.read(File.join(Nitro.proto_path, 'public', 'scaffold', "#{action}.xhtml"))
34
+ template = compiler.transform_template(source)
35
+
36
+ yield template
37
+
38
+ return %{
39
+ def #{action}#{suffix}
40
+ #{template}
41
+ end
42
+ }
43
+ end
44
+
45
+ return nil
46
+ end
47
+
24
48
  module ClassMethods
25
49
 
26
50
  # Enchant the caller with a number of default methods.
@@ -30,108 +54,107 @@ module Scaffolding
30
54
  compiler = Compiler.new
31
55
 
32
56
  oid = options[:oid] || 'oid'
33
- name = options[:name] || klass.to_s.demodulize.underscore.downcase
34
- list_name = options[:list_name] || name.plural
35
- options[:nosuffix] ? suffix = nil : suffix = "_#{name}"
57
+ name = options[:name] || Scaffolding.class_to_name(klass)
58
+ list_name = options[:plural_name] || name.plural
59
+ suffix = options[:nosuffix] ? nil : "_#{name}"
36
60
 
37
61
  # Add methods to the scaffolded class.
38
62
 
39
- klass.module_eval %{
40
- def to_href
41
- "view#{suffix}?oid=\#\{@oid\}"
42
- # "view#{suffix}/\#\{@oid\}"
63
+ unless klass.respond_to? :to_href
64
+ klass.send(:define_method, :to_href) do
65
+ "#{name}/#{@oid}"
43
66
  end
44
- }
67
+ end
68
+
69
+ unless klass.respond_to? :to_edit_link
70
+ klass.send(:define_method, :to_edit_link) do |base|
71
+ %{<a href="#{base}/#{name}/#{@oid}">#{self}</a>&nbsp;(<a href="#{base}/edit#{suffix}/#{@oid}">edit</a>, <a href="#{base}/delete#{suffix}/#{@oid}">del</a>)}
72
+ end
73
+ end
45
74
 
46
75
  # Add methods to the service.
47
76
 
48
77
  code = ''
49
78
 
50
- if options[:index]
51
- code << %{
52
- def index
53
- list#{suffix}
54
- end
55
- }
56
- end
57
-
58
79
  code << %{
59
80
  def new#{suffix}
60
81
  @#{name} = #{klass}.new
61
- render '/form#{suffix}'
82
+ render 'form#{suffix}'
62
83
  end
63
84
 
64
- def edit#{suffix}
65
- @#{name} = #{klass}[request['oid']]
66
- render '/form#{suffix}'
85
+ def edit#{suffix}(oid)
86
+ @#{name} = #{klass}[oid]
87
+ render 'form#{suffix}'
88
+ end
89
+
90
+ def form#{suffix}
67
91
  end
68
92
  }
69
-
70
- unless compiler.template_for_action("form#{suffix}", self.template_root)
71
- code << %{
72
- def form#{suffix}
73
- o << %|
74
- <html>
75
- <form action="save#{suffix}" method="post">
76
- |
77
- if @#{name}.oid
78
- o << %|
79
- <input type="hidden" name="oid" value="\#\{@#{name}\}" />
80
- |
81
- end
82
- o.build_form(@#{name})
83
- o << %|
84
- <input type="submit" value="Save" />
85
- </form>
86
- </html>
87
- |
88
- end
89
- }
93
+
94
+ code << Scaffolding.compile_scaffold_action(compiler, self, 'form', suffix) do |template|
95
+ template.gsub!(/%name%/, "#{name}")
96
+ template.gsub!(/%name_plural%/, "#{name.plural}")
90
97
  end
91
98
 
92
99
  code << %{
93
100
  # TODO: add pager support here!
94
101
 
95
- def list#{suffix}
96
- @#{list_name} = #{klass}.all('ORDER BY oid')
102
+ def #{name.plural}
103
+ @#{list_name} = #{klass}.all
97
104
  }
98
- unless compiler.template_for_action("list#{suffix}", self.template_root)
105
+ =begin
106
+ code << Scaffolding.compile_scaffold_action(compiler, self, 'list', suffix) do |template|
107
+ template.gsub!(/%name%/, "#{name}")
108
+ template.gsub!(/%list_name%/, "#{list_name}")
109
+ end
110
+ =end
111
+ unless compiler.template_for_action(list_name, self.template_root)
112
+ source = File.read(File.join(Nitro.proto_path, 'public', 'scaffold', 'list.xhtml'))
113
+ template = Compiler.new.transform_template(source)
114
+
115
+ template.gsub!(/%name%/, "#{name}")
116
+ template.gsub!(/%list_name%/, "#{list_name}")
117
+
99
118
  code << %{
100
- o.ul {
101
- for item in @#{list_name}
102
- o.li(item.to_s)
103
- end
104
- }
119
+ #{template}
105
120
  }
106
121
  end
122
+
107
123
  code << %{
108
124
  end
109
125
 
110
- def view#{suffix}
111
- # @#{name} = #{klass}[@context['#{oid}']]
112
- @#{name} = #{klass}[@#{oid}]
126
+ def #{name}(oid)
127
+ @#{name} = #{klass}[oid]
113
128
  end
114
- action :view#{suffix}, :route => \%r\{#{@base}/view#{suffix}/(.*)\}, 'oid' => 1
115
129
 
116
130
  def save#{suffix}
117
- if oid = request['oid']
118
- @#{name} = request.fill(#{klass}[oid])
131
+ if oid = request['#{oid}']
132
+ obj = request.fill(#{klass}[oid])
119
133
  else
120
- @#{name} = request.fill(#{klass}.new)
134
+ obj = request.fill(#{klass}.new)
121
135
  end
122
-
123
- @#{name}.save
124
-
125
- redirect 'list#{suffix}'
136
+ unless obj.valid?
137
+ session[:ERRORS] = obj.errors
138
+ redirect_to_referer
139
+ end
140
+ obj.save
141
+ redirect '#{name.plural}'
126
142
  end
127
143
 
128
- def del#{suffix}
129
- #{klass}.delete(@context['#{oid}'])
130
- redirect_referer
144
+ def delete#{suffix}(oid)
145
+ #{klass}.delete(oid)
146
+ redirect_to_referer
131
147
  end
132
- alias_method :delete#{suffix}, :del#{suffix}
133
148
  }
134
149
 
150
+ if options[:index]
151
+ code << %{
152
+ alias_action :index, :#{name.plural}
153
+ }
154
+ end
155
+
156
+ # puts '--', klass, '..', code
157
+
135
158
  class_eval(code)
136
159
  end
137
160