nitro 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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