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
@@ -77,7 +77,7 @@ class Server
77
77
 
78
78
  def dispatcher
79
79
  unless @dispatcher
80
- @dispatcher = Dispatcher.new(self.map)
80
+ @dispatcher = Dispatcher.new(@map)
81
81
  end
82
82
  @dispatcher
83
83
  end
@@ -87,6 +87,17 @@ class Server
87
87
  def start(options = {})
88
88
  @map['/'] = options[:controller] if options[:controller]
89
89
  @dispatcher = options[:dispatcher] || Dispatcher.new(@map)
90
+
91
+ # Create the actual store. Copy values already inserted
92
+ # in the temporary cache.
93
+ #--
94
+ # FIXME: cleanup this code
95
+ #++
96
+
97
+ temp = $global
98
+ $global = $application = Context.global_cache_class.new
99
+ $global.update(temp)
100
+
90
101
  return self
91
102
  end
92
103
 
@@ -124,6 +124,14 @@ class Runner
124
124
  self.class.mode = :live
125
125
  end
126
126
 
127
+ opts.on('--address IP', 'Force the server to run on this address.') do |a|
128
+ @server_address = a
129
+ end
130
+
131
+ opts.on('--port PORT', 'Force the server to run on this port.') do |p|
132
+ @server_port = p.to_i
133
+ end
134
+
127
135
  opts.on('-w', '--webrick', 'Use a webrick server [default].') do
128
136
  @server = :webrick
129
137
  end
@@ -293,6 +301,10 @@ class Runner
293
301
  def invoke_server(server)
294
302
  spider_thread = nil
295
303
 
304
+ # FIXME refactor !
305
+ server.address = @server_address if @server_address
306
+ server.port = @server_port if @server_port
307
+
296
308
  case @action
297
309
  when :start
298
310
 
@@ -7,7 +7,6 @@ require 'facet/times'
7
7
  require 'glue'
8
8
  require 'glue/attribute'
9
9
  require 'glue/configuration'
10
- require 'glue/logger'
11
10
  require 'glue/expirable'
12
11
 
13
12
  require 'nitro/cgi/cookie'
@@ -31,7 +30,7 @@ module Nitro
31
30
  #++
32
31
 
33
32
  class Session < Hash
34
- include Expirable
33
+ include Glue::Expirable
35
34
 
36
35
  # Session id salt.
37
36
 
@@ -50,14 +49,6 @@ class Session < Hash
50
49
 
51
50
  setting :keepalive, :default => 30.minutes, :doc => 'The session keepalive time'
52
51
 
53
- # The address of the Session cache / store (if distibuted).
54
-
55
- setting :cache_address, :default => '127.0.0.1', :doc => 'The address of the Session cache'
56
-
57
- # The port of the Session DRb cache / store (if distributed).
58
-
59
- setting :cache_port, :default => 9069, :doc => 'The port of the Session cache'
60
-
61
52
  # The sessions cache (store).
62
53
 
63
54
  cattr_accessor :cache
@@ -70,8 +61,8 @@ class Session < Hash
70
61
  # * :memory [default]
71
62
  # * :drb
72
63
  # * :og
73
- # * :file (not safe yet with multiple process as in fastcgi)
74
- # * :memcached (not available yet)
64
+ # * :file
65
+ # * :memcached
75
66
 
76
67
  def cache_type=(cache_type)
77
68
  # gmosx: RDoc friendly.
@@ -3,12 +3,9 @@ require 'nitro/session'
3
3
 
4
4
  module Nitro
5
5
 
6
- Logger.debug "Using DRb sessions at #{Session.cache_address}:#{Session.cache_port}."
6
+ Logger.debug "Using DRb sessions at #{Glue::DrbCache.address}:#{Glue::DrbCache.port}." if defined?(Logger)
7
7
 
8
- Session.cache = DrbCache.new(
9
- :address => Session.cache_address,
10
- :port => Session.cache_port
11
- )
8
+ Session.cache = Glue::DrbCache.new
12
9
 
13
10
  end
14
11
 
@@ -5,9 +5,9 @@ module Nitro
5
5
 
6
6
  # A Session manager that persists sessions on disk.
7
7
 
8
- Logger.debug "Using File sessions."
8
+ Logger.debug "Using File sessions." if defined?(Logger)
9
9
 
10
- Session.cache = FileCache.new("session_#{Session.cookie_name}", Session.keepalive)
10
+ Session.cache = Glue::FileCache.new("session_#{Session.cookie_name}", Session.keepalive)
11
11
 
12
12
  end
13
13
 
@@ -0,0 +1,14 @@
1
+ require 'glue/cache/memcached'
2
+ require 'nitro/session'
3
+
4
+ module Nitro
5
+
6
+ # A Session manager that persists sessions on disk.
7
+
8
+ Logger.debug "Using MemCached sessions." if defined?(Logger)
9
+
10
+ Session.cache = Glue::MemCached.new("session_#{Session.cookie_name}", Session.keepalive)
11
+
12
+ end
13
+
14
+ # * Guillaume Pierronnet <guillaume.pierronnet@gmail.com>
@@ -1,36 +1,13 @@
1
1
  require 'glue/cache/memory'
2
2
  require 'nitro/session'
3
+ require 'glue/logger'
3
4
 
4
5
  module Nitro
5
6
 
6
- Logger.debug "Using Memory sessions."
7
+ Logger.debug "Using Memory sessions." if defined?(Logger)
7
8
 
8
- Session.cache = MemoryCache.new
9
+ Session.cache = Glue::MemoryCache.new
9
10
 
10
11
  end
11
12
 
12
13
  # * George Moschovitis <gm@navel.gr>
13
-
14
- =begin
15
-
16
- module Nitro
17
-
18
- class MemorySessionStore < SyncHash
19
-
20
- # Perform session garbage collection. Typically this method
21
- # is called from a cron like mechanism (for example using
22
- # script/runner).
23
-
24
- def gc!
25
- delete_if { |key, s| s.expired? }
26
- end
27
-
28
- alias :all :values
29
- end
30
-
31
- Session.store = MemorySessionStore.new
32
-
33
- end
34
-
35
- # * George Moschovitis <gm@navel.gr>
36
- =end
@@ -5,7 +5,7 @@ module Nitro
5
5
 
6
6
  # A Session manager that persists sessions on an Og store.
7
7
 
8
- Logger.debug "Using Og sessions."
8
+ Logger.debug "Using Og sessions." if defined?(Logger)
9
9
 
10
10
  Session.cache = OgCache.new("session_#{Session.cookie_name}", Session.keepalive)
11
11
 
@@ -137,7 +137,7 @@ module Test::Unit::Assertions
137
137
  assert_block(msg) { cookie.value == value }
138
138
  end
139
139
 
140
- # :section: Template related assertions.
140
+ # :section: Glue::Template related assertions.
141
141
 
142
142
  # :section: Redirection assertions.
143
143
 
@@ -40,11 +40,17 @@ end
40
40
  # to include methods useful for testing.
41
41
 
42
42
  class Context
43
+ attr_writer :session, :cookies
44
+
43
45
  def session
44
46
  @session || @session = {}
45
- end
47
+ end
48
+
49
+ def cookies
50
+ @cookies || @cookies = {}
51
+ end
52
+
46
53
  end
47
54
 
48
55
  end
49
-
50
56
  # * George Moschovitis <gm@navel.gr>
@@ -10,8 +10,11 @@ module Test::Unit
10
10
  class TestCase
11
11
  include Nitro
12
12
 
13
- def controller(klass)
14
- @server = Server.run(klass)
13
+ def reset_context
14
+ @context_config = OpenStruct.new(
15
+ :dispatcher => Nitro::Dispatcher.new(Nitro::Server.map)
16
+ )
17
+ @context = Nitro::Context.new(@context_config)
15
18
  end
16
19
 
17
20
  # Send a request to the controller. Alternatively you can use
@@ -29,17 +32,23 @@ class TestCase
29
32
  uri = options[:uri]
30
33
  uri = "/#{uri}" unless uri =~ /^\//
31
34
 
32
- context = @context = Context.new(@server)
33
-
35
+ reset_context unless @context
36
+ context = @context
37
+ if @last_response_cookies
38
+ @last_response_cookies.each do |cookie|
39
+ context.cookies.merge! cookie.name => cookie.value
40
+ end
41
+ end
34
42
  context.params = options[:params] || {}
35
43
  context.headers = options[:headers] || options[:env] || {}
36
44
  context.headers['REQUEST_URI'] = uri
37
45
  context.headers['REQUEST_METHOD'] = options[:method].to_s.upcase
38
- context.cookies = options[:cookies]
39
- context.session = options[:session] if options[:session]
46
+ context.headers['REMOTE_ADDR'] ||= '127.0.0.1'
47
+ context.cookies.merge! options[:cookies] if options[:cookies]
48
+ context.session.merge! options[:session] if options[:session]
40
49
 
41
50
  context.render(context.path)
42
-
51
+ @last_response_cookies = context.response_cookies
43
52
  return context.body
44
53
  end
45
54
 
@@ -1,5 +1,17 @@
1
1
  <html>
2
2
  <head>
3
+ <script lang="javascript" type="text/javascript">
4
+ // <!--
5
+ function toggleVisible(element) {
6
+ if (element.style.display == 'block') {
7
+ element.style.display = 'none';
8
+ } else {
9
+ element.style.display = 'block';
10
+ }
11
+ return false;
12
+ }
13
+ // -->
14
+ </script>
3
15
  <title>Error</title>
4
16
  <style>
5
17
  .path {
@@ -38,27 +50,52 @@
38
50
  <?r for error, path in @context.rendering_errors ?>
39
51
  <div class="path"><strong>Path:</strong> #{path}</div>
40
52
  <div class="error"><strong>#{CGI.escapeHTML(error.to_s)}</strong></div>
41
- <div class="load">Click here to <strong><a href="#{request.uri}">reload</a></strong>.</div>
42
- <div class="load">Click here to go to the <strong><a href="#{request.referer}">referer</a></strong> or the <strong><a href="/">home page</a></strong>.</div>
43
- <?r if error.respond_to?(:source_extract) ?>
44
- <div class="source">
45
- <?r
46
- extract = error.source_extract.split("\n")
47
- extract.each_with_index do |line, idx|
48
- line = sanitize(line)
49
- if 4 == idx
50
- ?>
51
- <div style="background: #eee">#{line}</div>
52
- <?r else ?>
53
- <div>#{line}</div>
54
- <?r
55
- end
56
- end
57
- ?>
58
- </div>
59
- <?r end ?>
60
- <h2><a href="#" onclick="document.getElementById('trace').style.display = 'block'; return false">Stack Trace</a></h2>
61
- <p id="trace" style="display: none">#{error.backtrace.join('<br />')}</p>
53
+ <div class="load">
54
+ <strong><a href="#{request.uri}">Reload</a></strong> this page.
55
+ Go to the <strong><a href="#{request.referer}">referer</a></strong> or the <strong><a href="/">home page</a></strong>.
56
+ </div>
57
+ <div class="source">
58
+ <?r
59
+ extract = error.source_extract.split("\n")
60
+ ?>
61
+ In file <b>'#{error.hot_file}'</b> #{error.hot_file =~ /\.xhtml$/ ? '(line numbering is aproximate due to template transformation)' : nil}:
62
+ <br /><br />
63
+ <?r
64
+ extract.each_with_index do |line, idx|
65
+ line = sanitize(line)
66
+ if 5 == idx
67
+ ?>
68
+ <div style="background: #eee">#{line}</div>
69
+ <?r else ?>
70
+ <div>#{line}</div>
71
+ <?r
72
+ end
73
+ end
74
+ ?>
75
+ </div>
76
+ <h2><a href="#" onclick="return toggleVisible(document.getElementById('trace'));">Stack Trace</a></h2>
77
+ <div id="trace" style="display: none;">
78
+ <?r error.backtrace.zip(error.source_for_backtrace).each_with_index do |step,step_idx| ?>
79
+ <div><a href="#" onclick="return toggleVisible(document.getElementById('trace_#{step_idx}'));">#{sanitize(step.first)}</a></div>
80
+ <div class="source" id="trace_#{step_idx}" style="display: none;">
81
+ <?r
82
+ extract = step.last.split("\n")
83
+ extract.each_with_index do |line, idx|
84
+ line = sanitize(line)
85
+ if 5 == idx
86
+ ?>
87
+ <div style="background: #eee">#{line}</div>
88
+ <?r else ?>
89
+ <div>#{line}</div>
90
+ <?r
91
+ end
92
+ end
93
+ ?>
94
+ </div>
95
+
96
+
97
+ <?r end ?>
98
+ </div>
62
99
  <?r end ?>
63
100
 
64
101
  <h2><a href="#" onclick="document.getElementById('request').style.display = 'block'; return false">Request</a></h2>
@@ -152,6 +152,12 @@ Autocompleter.Base.prototype = {
152
152
  setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153
153
  },
154
154
 
155
+ activate: function() {
156
+ this.changed = false;
157
+ this.hasFocus = true;
158
+ this.getUpdatedChoices();
159
+ },
160
+
155
161
  onHover: function(event) {
156
162
  var element = Event.findElement(event, 'LI');
157
163
  if(this.index != element.autocompleteIndex)
@@ -477,9 +483,10 @@ Ajax.InPlaceEditor.prototype = {
477
483
  formClassName: 'inplaceeditor-form',
478
484
  highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
479
485
  highlightendcolor: "#FFFFFF",
480
- externalControl: null,
486
+ externalControl: null,
481
487
  submitOnBlur: false,
482
- ajaxOptions: {}
488
+ ajaxOptions: {},
489
+ evalScripts: false
483
490
  }, options || {});
484
491
 
485
492
  if(!this.options.formId && this.element.id) {
@@ -548,6 +555,7 @@ Ajax.InPlaceEditor.prototype = {
548
555
  okButton = document.createElement("input");
549
556
  okButton.type = "submit";
550
557
  okButton.value = this.options.okText;
558
+ okButton.className = 'editor_ok_button';
551
559
  this.form.appendChild(okButton);
552
560
  }
553
561
 
@@ -556,6 +564,7 @@ Ajax.InPlaceEditor.prototype = {
556
564
  cancelLink.href = "#";
557
565
  cancelLink.appendChild(document.createTextNode(this.options.cancelText));
558
566
  cancelLink.onclick = this.onclickCancel.bind(this);
567
+ cancelLink.className = 'editor_cancel';
559
568
  this.form.appendChild(cancelLink);
560
569
  }
561
570
  },
@@ -584,6 +593,7 @@ Ajax.InPlaceEditor.prototype = {
584
593
  textField.name = "value";
585
594
  textField.value = text;
586
595
  textField.style.backgroundColor = this.options.highlightcolor;
596
+ textField.className = 'editor_field';
587
597
  var size = this.options.size || this.options.cols || 0;
588
598
  if (size != 0) textField.size = size;
589
599
  if (this.options.submitOnBlur)
@@ -597,6 +607,7 @@ Ajax.InPlaceEditor.prototype = {
597
607
  textArea.value = this.convertHTMLLineBreaks(text);
598
608
  textArea.rows = this.options.rows;
599
609
  textArea.cols = this.options.cols || 40;
610
+ textArea.className = 'editor_field';
600
611
  if (this.options.submitOnBlur)
601
612
  textArea.onblur = this.onSubmit.bind(this);
602
613
  this.editField = textArea;
@@ -649,19 +660,26 @@ Ajax.InPlaceEditor.prototype = {
649
660
  // to be displayed indefinitely
650
661
  this.onLoading();
651
662
 
652
- new Ajax.Updater(
653
- {
654
- success: this.element,
655
- // don't update on failure (this could be an option)
656
- failure: null
657
- },
658
- this.url,
659
- Object.extend({
660
- parameters: this.options.callback(form, value),
661
- onComplete: this.onComplete.bind(this),
662
- onFailure: this.onFailure.bind(this)
663
- }, this.options.ajaxOptions)
664
- );
663
+ if (this.options.evalScripts) {
664
+ new Ajax.Request(
665
+ this.url, Object.extend({
666
+ parameters: this.options.callback(form, value),
667
+ onComplete: this.onComplete.bind(this),
668
+ onFailure: this.onFailure.bind(this),
669
+ asynchronous:true,
670
+ evalScripts:true
671
+ }, this.options.ajaxOptions));
672
+ } else {
673
+ new Ajax.Updater(
674
+ { success: this.element,
675
+ // don't update on failure (this could be an option)
676
+ failure: null },
677
+ this.url, Object.extend({
678
+ parameters: this.options.callback(form, value),
679
+ onComplete: this.onComplete.bind(this),
680
+ onFailure: this.onFailure.bind(this)
681
+ }, this.options.ajaxOptions));
682
+ }
665
683
  // stop the event to avoid a page refresh in Safari
666
684
  if (arguments.length > 1) {
667
685
  Event.stop(arguments[0]);
@@ -743,6 +761,33 @@ Ajax.InPlaceEditor.prototype = {
743
761
  }
744
762
  };
745
763
 
764
+ Ajax.InPlaceCollectionEditor = Class.create();
765
+ Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
766
+ Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
767
+ createEditField: function() {
768
+ if (!this.cached_selectTag) {
769
+ var selectTag = document.createElement("select");
770
+ var collection = this.options.collection || [];
771
+ var optionTag;
772
+ collection.each(function(e,i) {
773
+ optionTag = document.createElement("option");
774
+ optionTag.value = (e instanceof Array) ? e[0] : e;
775
+ if(this.options.value==optionTag.value) optionTag.selected = true;
776
+ optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
777
+ selectTag.appendChild(optionTag);
778
+ }.bind(this));
779
+ this.cached_selectTag = selectTag;
780
+ }
781
+
782
+ this.editField = this.cached_selectTag;
783
+ if(this.options.loadTextURL) this.loadExternalText();
784
+ this.form.appendChild(this.editField);
785
+ this.options.callback = function(form, value) {
786
+ return "value=" + encodeURIComponent(value);
787
+ }
788
+ }
789
+ });
790
+
746
791
  // Delayed observer, like Form.Element.Observer,
747
792
  // but waits for delay after last key input
748
793
  // Ideal for live-search fields