rsence 2.2.1 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.1
1
+ 2.2.2
@@ -78,13 +78,18 @@
78
78
  :latency: 0
79
79
  #
80
80
  # HTTP Port number to listen to.
81
- :port: 8001
81
+ :port: '8001'
82
82
  #
83
83
  # Bind this ip address ('0.0.0.0' means all)
84
84
  :bind_address: '127.0.0.1'
85
85
  #
86
- # Rack handler to use, defaults to thin
87
- :rack_require: mongrel
86
+ # Rack handler to use, defaults to puma
87
+ :rack_require: puma
88
+ #
89
+ # These are default options. Ymmv, but these work fine for puma
90
+ :handler_options:
91
+ :Verbose: false
92
+ :Threads: '4:64' # puma default is '0:16'
88
93
  #
89
94
  # When enabled, sets http cache headers
90
95
  # to cache content as long as possible.
@@ -39,7 +39,7 @@
39
39
  :plugin_directory_not_a_directory: |
40
40
  Plugin directory not a directory, expected:
41
41
  <%%= plugin_path.inspect %>
42
- :warn_no_plugin_directory_in_project: |
42
+ :warn_no_directory_creating: |
43
43
  Warning! No <%%= dir_name %> directory in project, creating:
44
44
  <%%= dir_path.inspect %>
45
45
  :invalid_environment: |
@@ -70,20 +70,18 @@ COMM.Transporter = HApplication.extend({
70
70
  * to report js errors to the server.
71
71
  * If no error, returns an empty string.
72
72
  **/
73
- getClientEvalError: function(){
74
- var _this = COMM.Transporter;
75
- return _this._clientEvalError?'&err_msg=' +
76
- COMM.Values._encodeString(_this._clientEvalError):'';
77
- },
73
+ // getClientEvalError: function(){
74
+ // var _this = COMM.Transporter;
75
+ // return _this._clientEvalError?'&err_msg=' +
76
+ // COMM.Values._encodeString(_this._clientEvalError):'';
77
+ // },
78
78
 
79
79
  parseResponseArray: function( _responseText ){
80
- var _arr = eval( _responseText );
81
- return _arr;
80
+ return HVM.decode( _responseText );
82
81
  },
83
82
 
84
83
  _nativeParseResponseArray: function( _responseText ){
85
- var _arr = JSON.parse( _responseText );
86
- return _arr;
84
+ return JSON.parse( _responseText );
87
85
  },
88
86
 
89
87
  setValues: function( _values ){
@@ -176,7 +174,9 @@ COMM.Transporter = HApplication.extend({
176
174
  flushBusy: function(){
177
175
  var _this = COMM.Transporter;
178
176
  _this.busy = false;
179
- COMM.Values.tosync.length !== 0 && _this.sync();
177
+ if( COMM.Values.tosync.length !== 0 ){
178
+ _this.sync();
179
+ }
180
180
  },
181
181
  failMessage: function(_title,_message){
182
182
  var _this = COMM.Transporter,
@@ -320,18 +320,34 @@ COMM.Transporter = HApplication.extend({
320
320
  }
321
321
  // console.log('sync.');
322
322
  this.busy = true;
323
+ var _now = new Date().getTime();
323
324
  if(window['sesWatcher'] && window.sesWatcher['sesTimeoutValue']){
324
325
  // Sets the value of the session watcher to the current time. It could cause an unnecessary re-sync poll immediately after this sync otherwise.
325
- sesWatcher.sesTimeoutValue.set( new Date().getTime() );
326
+ sesWatcher.sesTimeoutValue.set( _now );
326
327
  }
327
- var _this = this,
328
- _values = COMM.Values.sync(),
329
- _sesKey = 'ses_key='+COMM.Session.ses_key,
330
- _errorMessage = _this.getClientEvalError(),
331
- _body = [_sesKey,_errorMessage,_values?'&values='+_values:''].join('');
328
+ var
329
+ _this = this,
330
+ // _values = HVM.sync(),
331
+ // _boundary = _now.toString(36)+(Math.random()*10000).toString(36)+(Math.random()*10000).toString(36),
332
+ // _separator = '--'+_boundary,
333
+ // _errorMessage = _this.getClientEvalError(),
334
+ _body = HVM.sync();
335
+ // _body = _separator+
336
+ // '\r\nContent-Disposition: form-data; name="ses_key"\r\nContent-Type: text/plain\r\n'+
337
+ // '\r\n'+COMM.Session.ses_key+'\r\n'+_separator;
338
+ // if( _values ){
339
+ // _body += '\r\nContent-Disposition: form-data; name="values"\r\nContent-Type: application/json; charset=UTF-8\r\n'+
340
+ // '\r\n'+_values+'\r\n'+_separator+'--\r\n';
341
+ // }
342
+ // else {
343
+ // _body += '--\r\n';
344
+ // }
345
+ // _body = [_sesKey,_errorMessage,_values?'&values='+_values:''].join('');
332
346
  COMM.request(
333
347
  _this.url, {
334
348
  _this: _this,
349
+ // contentType: 'multipart/form-data; boundary='+_boundary,
350
+ contentType: 'application/json',
335
351
  onSuccess: COMM.Transporter.success,
336
352
  onFailure: COMM.Transporter.failure,
337
353
  method: 'POST',
@@ -468,22 +468,54 @@ COMM.Values = HClass.extend({
468
468
  * An encoded string representation of values to synchronize.
469
469
  **/
470
470
  sync: function(){
471
- if(this.tosync.length===0){
472
- return false;
471
+ var
472
+ _this = this,
473
+ _response = [ COMM.Session.ses_key,{},[] ],
474
+ _error = COMM.Transporter._clientEvalError;
475
+
476
+ if( _error ){
477
+ _response[2].push({'err_msg':_error});
473
478
  }
474
- var _syncValues = {},
475
- _this = this,
476
- _values = _this.values,
477
- _tosync = _this.tosync,
478
- _len = _tosync.length,
479
- i = 0, _id, _value;
480
- for(;i<_len;i++){
481
- _id = _tosync.shift();
482
- _value = _values[_id].value;
483
- _syncValues[_id] = _value;
479
+
480
+ // new implementation, symmetric with the server response format
481
+ if( this.tosync.length > 0 ){
482
+ _response[1].set=[];
483
+ var
484
+ _syncValues = _response[1].set,
485
+ _values = _this.values,
486
+ _tosync = _this.tosync,
487
+ i = _tosync.length,
488
+ _id, _value;
489
+ while(i--){
490
+ _id = _tosync.shift();
491
+ _value = _values[_id].value;
492
+ _syncValues.push( [ _id, _value ] );
493
+ }
484
494
  }
485
- return encodeURIComponent(_this.encode(_syncValues));
495
+ // console.log('response:',_response);
496
+ // console.log('encoded:',_this.encode(_response));
497
+ return _this.encode(_response);
486
498
  },
499
+
500
+ // Old sync implementation:
501
+ // sync: function(){
502
+ // if(this.tosync.length===0){
503
+ // return false;
504
+ // }
505
+ // var
506
+ // _syncValues = {},
507
+ // _this = this,
508
+ // _values = _this.values,
509
+ // _tosync = _this.tosync,
510
+ // _len = _tosync.length,
511
+ // i = 0, _id, _value;
512
+ // for(;i<_len;i++){
513
+ // _id = _tosync.shift();
514
+ // _value = _values[_id].value;
515
+ // _syncValues[_id] = _value;
516
+ // }
517
+ // return encodeURIComponent(_this.encode(_syncValues));
518
+ // },
487
519
 
488
520
  _detectNativeJSONSupport: function(){
489
521
  if(window['JSON']){
@@ -157,7 +157,7 @@ module ArgvUtil
157
157
  say @strs[:initenv][:enter_http_port]
158
158
  str_http_port = @strs[:initenv][:http_port]
159
159
  config[:http_server][:port] = ask(str_http_port) do |q|
160
- q.default = config[:http_server][:port]
160
+ q.default = config[:http_server][:port].to_s
161
161
  end
162
162
 
163
163
  say @strs[:initenv][:enter_tcp_ip]
@@ -115,18 +115,18 @@ class Broker
115
115
  puts conf.inspect if RSence.args[:debug]
116
116
 
117
117
  require rack_require
118
+ require 'rack/handler/puma.rb' if rack_require == 'puma'
118
119
 
119
120
  # Selects the handler for Rack
120
121
  handler = {
121
- 'mongrel2' => lambda { Rack::Handler::Mongrel2 },
122
122
  'webrick' => lambda { Rack::Handler::WEBrick },
123
- 'ebb' => lambda { Rack::Handler::Ebb },
124
123
  'thin' => lambda { Rack::Handler::Thin },
125
124
  'mongrel' => lambda { Rack::Handler::Mongrel },
126
- 'unicorn' => lambda { Rack::Handler::Unicorn },
127
- 'rainbows' => lambda { Rack::Handler::Rainbows }
125
+ 'puma' => lambda { Rack::Handler::Puma }
128
126
  }[rack_require].call
129
- handler.run( Rack::Lint.new(self.new), :Host => host, :Port => port )
127
+ handler.run( self.new, {
128
+ :Host => host, :Port => port
129
+ }.merge( conf[:handler_options] ) )
130
130
  end
131
131
 
132
132
  # Generic 404 error handler. Just sets up response status, headers, body as a small "Page Not Found" html page
data/lib/rsence/msg.rb CHANGED
@@ -135,7 +135,7 @@ module RSence
135
135
  if options[:servlet]
136
136
  @do_gzip = false
137
137
  else
138
- @response['Content-Type'] = 'text/javascript; charset=utf-8'
138
+ @response['Content-Type'] = 'application/json; charset=utf-8'
139
139
  @response['Cache-Control'] = 'no-cache'
140
140
 
141
141
  # gnu-zipped responses:
@@ -84,7 +84,7 @@ module RSence
84
84
  # session id, used internally
85
85
  :ses_id => ses_id,
86
86
 
87
- # session key, used externally (client xhr)
87
+ # session key, used externally (client sync)
88
88
  :ses_key => ses_sha,
89
89
 
90
90
  # session key, used externally (client cookies)
@@ -137,7 +137,7 @@ module RSence
137
137
  # new time-out
138
138
  ses_data[:timeout] = Time.now.to_i + @config[:timeout_secs]
139
139
 
140
- # re-generates the ses_key for each xhr
140
+ # re-generates the ses_key for each sync
141
141
  if @config[:disposable_keys]
142
142
 
143
143
  # disposes the old (current) ses_key:
@@ -233,7 +233,7 @@ module RSence
233
233
  ### Otherwise stops the client and returns false.
234
234
  def check_ses( msg, ses_key, ses_seed=false )
235
235
 
236
- # first, check if the session key exists (xhr)
236
+ # first, check if the session key exists (sync)
237
237
  if @session_keys.has_key?( ses_key )
238
238
 
239
239
  # get the session's id based on its key
@@ -455,7 +455,7 @@ module RSence
455
455
  ses_cookie_max_age = @config[:timeout_secs]
456
456
 
457
457
  ## Only match the handshaking address of rsence,
458
- ## prevents unnecessary cookie-juggling in xhr's
458
+ ## prevents unnecessary cookie-juggling in sync's
459
459
  if @config[:trust_cookies]
460
460
  ses_cookie_path = '/'
461
461
  elsif ses_cookie_path == nil
@@ -527,19 +527,19 @@ module RSence
527
527
  query = request.query
528
528
  end
529
529
 
530
- ## Perform old-session cleanup on all xhr:s
530
+ ## Perform old-session cleanup on all sync:s
531
531
  expire_sessions
532
532
 
533
533
  ## The 'ses_id' request query key is required.
534
534
  ## The client defaults to '0', which means the
535
535
  ## client needs to be initialized.
536
536
  ## The client's ses_id is the server's ses_key.
537
- if not query.has_key?( 'ses_key' )
537
+ if not options.has_key?( :ses_key )
538
538
  return Message.new( @transporter, request, response, options )
539
539
  else
540
540
 
541
541
  ## get the ses_key from the request query:
542
- ses_key = query[ 'ses_key' ]
542
+ ses_key = options[:ses_key]
543
543
 
544
544
  ## The message object binds request, response
545
545
  ## and all user/session -related data to one
@@ -94,13 +94,13 @@ module RSence
94
94
  uri = request.fullpath
95
95
 
96
96
  if request_type == :post
97
- ## /x handles xhr without cookies
97
+ ## /x handles sync without cookies
98
98
  if uri == broker_urls[:x] and @sessions.accept_requests
99
- xhr( request, response, { :cookies => true, :servlet => false } )
99
+ sync( request, response, { :cookies => true, :servlet => false } )
100
100
  return true
101
- ## /hello handles the first xhr (with cookies, for session key)
101
+ ## /hello handles the first sync (with cookies, for session key)
102
102
  elsif uri == broker_urls[:hello] and @sessions.accept_requests
103
- xhr( request, response, { :cookies => true, :servlet => false } )
103
+ sync( request, response, { :cookies => true, :servlet => false } )
104
104
  return true
105
105
  end
106
106
  end
@@ -108,7 +108,7 @@ module RSence
108
108
  end
109
109
 
110
110
  # wrapper for the session manager stop client functionality
111
- def xhr_error_handler(msg,err_name,err_extra_descr='')
111
+ def sync_error_handler(msg,err_name,err_extra_descr='')
112
112
  @sessions.stop_client_with_message( msg,
113
113
  @config[:messages][err_name][:title],
114
114
  @config[:messages][err_name][:descr]+err_extra_descr,
@@ -116,8 +116,8 @@ module RSence
116
116
  )
117
117
  end
118
118
 
119
- # wrapper for tracebacks in xhr
120
- def xhr_traceback_handler(e,err_descr='Transporter::UnspecifiedError')
119
+ # wrapper for tracebacks in sync
120
+ def sync_traceback_handler(e,err_descr='Transporter::UnspecifiedError')
121
121
  puts "=="*40 if RSence.args[:debug]
122
122
  puts err_descr
123
123
  if RSence.args[:debug]
@@ -127,10 +127,32 @@ module RSence
127
127
  puts "=="*40
128
128
  end
129
129
  end
130
+
131
+ def find_client_sync_error( options )
132
+ return false if options.length == 0
133
+ errors = []
134
+ options.each do |err|
135
+ if err.class == Hash and err.has_key?('err_msg')
136
+ errors.push( err['err_msg'] )
137
+ end
138
+ end
139
+ return false if errors.length == 0
140
+ return errors
141
+ end
130
142
 
131
143
  ## handles incoming XMLHttpRequests from the browser
132
- def xhr(request, response, options = { :cookies => false, :servlet => false } )
133
-
144
+ def sync(request, response, options = { :cookies => false, :servlet => false } )
145
+ request_body = request.body.read
146
+ begin
147
+ request_content = JSON.parse( request_body )
148
+ rescue JSON::ParseError
149
+ warn "Request body isn't valid JSON: #{request_body}"
150
+ request_content = ['-1:.o.:INVALID',{},[]]
151
+ end
152
+ options[:ses_key] = request_content[0]
153
+ options[:values] = request_content[1]
154
+ options[:messages] = request_content[2]
155
+
134
156
  session_conf = RSence.config[:session_conf]
135
157
 
136
158
  options[:cookies] = false unless options.has_key?(:cookies)
@@ -143,13 +165,15 @@ module RSence
143
165
  msg = @sessions.init_msg( request, response, options )
144
166
 
145
167
  response_success = true
146
-
168
+
169
+ client_errors = find_client_sync_error( options[:messages] )
170
+
147
171
  # If the client encounters an error, display error message
148
- if request.query.has_key?('err_msg')
172
+ if client_errors #request.query.has_key?('err_msg')
149
173
  response_success = false
150
- client_error_msg = request.query['err_msg'].inspect
174
+ client_error_msg = client_errors.inspect
151
175
  puts "\nCLIENT ERROR:\n#{client_error_msg}\n" if RSence.args[:debug]
152
- xhr_error_handler(msg,:client_error,client_error_msg)
176
+ sync_error_handler(msg,:client_error,client_error_msg)
153
177
  end
154
178
 
155
179
  # If the session is valid, continue:
@@ -175,14 +199,14 @@ module RSence
175
199
  end
176
200
 
177
201
  ## Pass the client XML to the value manager
178
- if request.query.has_key?( 'values' )
179
- syncdata_str = request.query[ 'values' ]
202
+ if options[:values].has_key?('set')#request.query.has_key?( 'values' )
203
+ # syncdata_str = request.query[ 'values' ]
180
204
  begin
181
- @valuemanager.xhr( msg, syncdata_str )
205
+ @valuemanager.sync( msg, options[:values]['set'] )
182
206
  rescue => e
183
207
  response_success = false
184
- xhr_error_handler( msg, :valuemanager_xhr_error, e.message )
185
- xhr_traceback_handler( e, "Transporter::ValueManagerXHRError: @valuemanager.xhr failed." )
208
+ sync_error_handler( msg, :valuemanager_sync_error, e.message )
209
+ sync_traceback_handler( e, "Transporter::ValueManagerXHRError: @valuemanager.sync failed." )
186
210
  end
187
211
  end
188
212
 
@@ -195,8 +219,8 @@ module RSence
195
219
  @plugins.delegate( :cloned_target, msg, msg.cloned_source )
196
220
  rescue => e
197
221
  response_success = false
198
- xhr_error_handler( msg, :plugin_delegate_cloned_target_error, e.message )
199
- xhr_traceback_handler( e, "Transporter::PluginDelegateClonedTargetError: @plugins.delegate 'cloned_target' failed." )
222
+ sync_error_handler( msg, :plugin_delegate_cloned_target_error, e.message )
223
+ sync_traceback_handler( e, "Transporter::PluginDelegateClonedTargetError: @plugins.delegate 'cloned_target' failed." )
200
224
  end
201
225
  end
202
226
 
@@ -205,8 +229,8 @@ module RSence
205
229
  msg.session[:plugin_incr] = @plugins.incr
206
230
  rescue => e
207
231
  response_success = false
208
- xhr_error_handler( msg, :plugin_delegate_restore_ses_error, e.message )
209
- xhr_traceback_handler( e, "Transporter::PluginDelegateRestoreSesError: @plugins.delegate 'restore_ses' failed." )
232
+ sync_error_handler( msg, :plugin_delegate_restore_ses_error, e.message )
233
+ sync_traceback_handler( e, "Transporter::PluginDelegateRestoreSesError: @plugins.delegate 'restore_ses' failed." )
210
234
  end
211
235
 
212
236
  elsif msg.new_session
@@ -216,8 +240,8 @@ module RSence
216
240
  msg.session[:plugin_incr] = @plugins.incr
217
241
  rescue => e
218
242
  response_success = false
219
- xhr_error_handler( msg, :plugin_delegate_init_ses_error, e.message )
220
- xhr_traceback_handler( e, "Transporter::PluginDelegateInitSesError: @plugins.delegate 'init_ses' failed." )
243
+ sync_error_handler( msg, :plugin_delegate_init_ses_error, e.message )
244
+ sync_traceback_handler( e, "Transporter::PluginDelegateInitSesError: @plugins.delegate 'init_ses' failed." )
221
245
  end
222
246
 
223
247
  elsif msg.cloned_targets
@@ -226,8 +250,8 @@ module RSence
226
250
  @plugins.delegate( :cloned_source, msg, msg.cloned_targets )
227
251
  rescue => e
228
252
  response_success = false
229
- xhr_error_handler( msg, :plugin_delegate_cloned_source_error, e.message )
230
- xhr_traceback_handler( e, "Transporter::PluginDelegateClonedSourceError: @plugins.delegate 'cloned_source' failed." )
253
+ sync_error_handler( msg, :plugin_delegate_cloned_source_error, e.message )
254
+ sync_traceback_handler( e, "Transporter::PluginDelegateClonedSourceError: @plugins.delegate 'cloned_source' failed." )
231
255
  end
232
256
 
233
257
  elsif msg.refresh_page?( @plugins.incr ) and @config[:client_autoreload]
@@ -244,8 +268,8 @@ module RSence
244
268
  @valuemanager.validate( msg )
245
269
  rescue => e
246
270
  response_success = false
247
- xhr_error_handler( msg, :valuemanager_validate_error, e.message )
248
- xhr_traceback_handler( e, "Transporter::ValueManagerValidateError: @valuemanager.validate failed." )
271
+ sync_error_handler( msg, :valuemanager_validate_error, e.message )
272
+ sync_traceback_handler( e, "Transporter::ValueManagerValidateError: @valuemanager.validate failed." )
249
273
  end
250
274
 
251
275
  ### Allows every plugin to respond to the idle call
@@ -253,8 +277,8 @@ module RSence
253
277
  @plugins.delegate( :idle, msg )
254
278
  rescue => e
255
279
  response_success = false
256
- xhr_error_handler( msg, :plugin_idle_error, e.message )
257
- xhr_traceback_handler( e, "Transporter::PluginIdleError: @plugins.idle failed." )
280
+ sync_error_handler( msg, :plugin_idle_error, e.message )
281
+ sync_traceback_handler( e, "Transporter::PluginIdleError: @plugins.idle failed." )
258
282
  end
259
283
 
260
284
  ### Processes outgoing values to client
@@ -262,8 +286,8 @@ module RSence
262
286
  @valuemanager.sync_client( msg )
263
287
  rescue => e
264
288
  response_success = false
265
- xhr_error_handler( msg, :valuemanager_sync_client_error, e.message )
266
- xhr_traceback_handler( e, "Transporter::ValueManagerSyncClientError: @valuemanager.sync_client failed." )
289
+ sync_error_handler( msg, :valuemanager_sync_client_error, e.message )
290
+ sync_traceback_handler( e, "Transporter::ValueManagerSyncClientError: @valuemanager.sync_client failed." )
267
291
  end
268
292
 
269
293
  else
@@ -114,10 +114,10 @@ module RSence
114
114
  end
115
115
 
116
116
  # @private Parses the json from the client and passes it on to associated values
117
- def xhr( msg, syncdata_str )
117
+ def sync( msg, syncdata )
118
118
 
119
119
  # parses the json data sent by the client
120
- syncdata = JSON.parse( syncdata_str )
120
+ # syncdata = JSON.parse( syncdata_str )
121
121
 
122
122
  session_values = msg.session[:values][:by_id]
123
123
  syncdata.each do |value_key, value_data|
@@ -125,7 +125,7 @@ module RSence
125
125
  value_obj = session_values[ value_key ]
126
126
  value_obj.from_client( msg, value_data )
127
127
  else
128
- raise "HValue; unassigned value id! (#{val_id.inspect})"
128
+ warn "HValue; unassigned value key: (#{value_key.inspect})"
129
129
  end
130
130
  end
131
131
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsence
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-27 00:00:00.000000000 Z
13
+ date: 2012-04-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rsence-deps
@@ -19,7 +19,7 @@ dependencies:
19
19
  requirements:
20
20
  - - '='
21
21
  - !ruby/object:Gem::Version
22
- version: '968'
22
+ version: '971'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,7 +27,7 @@ dependencies:
27
27
  requirements:
28
28
  - - '='
29
29
  - !ruby/object:Gem::Version
30
- version: '968'
30
+ version: '971'
31
31
  description: ! 'RSence is a different and unique development model and software frameworks
32
32
  designed first-hand for real-time web applications. RSence consists of separate,
33
33
  but tigtly integrated data- and user interface frameworks.
@@ -358,8 +358,7 @@ files:
358
358
  - docs/Values.rdoc
359
359
  - VERSION
360
360
  - .yardopts
361
- - !binary |-
362
- YmluL3JzZW5jZQ==
361
+ - bin/rsence
363
362
  homepage: http://www.rsence.org/
364
363
  licenses:
365
364
  - GPL-3
@@ -381,8 +380,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
381
380
  version: '0'
382
381
  requirements: []
383
382
  rubyforge_project: rsence-
384
- rubygems_version: 1.8.21
383
+ rubygems_version: 1.8.24
385
384
  signing_key:
386
385
  specification_version: 3
387
386
  summary: Release 2.2 version of RSence.
388
387
  test_files: []
388
+ has_rdoc: