couchdb-ruby-server 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.hound.yml +4 -0
- data/.travis.yml +13 -0
- data/Gemfile +5 -0
- data/HISTORY.md +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +38 -0
- data/Rakefile +16 -0
- data/TODO.md +4 -0
- data/bin/couchdb-ruby-server +18 -0
- data/couchdb-ruby-server.gemspec +28 -0
- data/couchdb-ruby-server.sublime-project +27 -0
- data/lib/couchdb-ruby-server/server.rb +330 -0
- data/lib/couchdb-ruby-server/version.rb +5 -0
- data/lib/couchdb-ruby-server.rb +8 -0
- data/spec/couchdb-ruby-server_spec.rb +952 -0
- data/spec/spec_helper.rb +10 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aa9cbfeea39a79efb59cfa9b1d278ee06ea5451c
|
4
|
+
data.tar.gz: 1fa6648489a455bc477d446af41b2f84c48eb215
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 196f7dd41438a62fe78b9d98c3f6824b04e29639f82fc9994017ad95d3594c8476f01211bdc985405283d1a80c9674f9a443191a8b72afc2ae57a6a8fee287a9
|
7
|
+
data.tar.gz: 1e4608f3551c2937ce0acc9f2a7f13075e2ccb9b079b3c1935cf7e1537af679053c32649c310aa3393ec442ea5eab0d1777053dba02fa6da703bdf6a67a542b7
|
data/.gitignore
ADDED
data/.hound.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/HISTORY.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
MIT License, http://opensource.org/licenses/MIT
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# couchdb-ruby-server
|
2
|
+
CouchDB query server to run ruby design docs.
|
3
|
+
|
4
|
+
[](https://travis-ci.org/sinm/couchdb-ruby-server) [](http://badge.fury.io/rb/couchdb-ruby-server) [](https://gemnasium.com/sinm/couchdb-ruby-server) [](https://raw.githubusercontent.com/sinm/couchdb-ruby-server/master/LICENSE.txt) [](https://codeclimate.com/github/sinm/couchdb-ruby-server) [](https://codeclimate.com/github/sinm/couchdb-ruby-server) [](http://inch-ci.org/github/sinm/couchdb-ruby-server)
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
`gem install couchdb-ruby-server` as usual.
|
8
|
+
|
9
|
+
Edit your couchdb's local.ini and set this in the `[query_servers]` group:
|
10
|
+
|
11
|
+
ruby = couchdb-ruby-server
|
12
|
+
|
13
|
+
Restart CouchDB then.
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
Design functions (map, reduce, update, etc.) are well defined as `lambda` or `proc` blocks:
|
17
|
+
|
18
|
+
lambda { |doc| emit [1,2,3], doc }
|
19
|
+
|
20
|
+
It looks like any object responding to `#call` will survive.
|
21
|
+
|
22
|
+
[Couchapp](couchapp.org) works well with `rb` files, so use it to manage design files. Just do `echo ruby > language` inside your app's root directory and you're in.
|
23
|
+
|
24
|
+
You may load your own code into the query server context by providing path to a library as an `--require LIB` option. This way you could deserialize documents right into the instances of the library classes instead of plain hashes.
|
25
|
+
|
26
|
+
Actually any tagname for the query server may be set in `local.ini` instead of `ruby` so it's possible to run several query servers e.g. per application. Just remember to copy that tagname into couchapp's `language` file.
|
27
|
+
|
28
|
+
## Tests
|
29
|
+
`bundle exec rake test`. Gem following original [tests](https://github.com/apache/couchdb/blob/master/test/view_server/query_server_spec.rb).
|
30
|
+
|
31
|
+
## Documentation
|
32
|
+
This README is all i could say in a rush. No other documentation provided at this moment, see the sources.
|
33
|
+
|
34
|
+
## If you've found a bug or drawback
|
35
|
+
Don't hesistate to leave a report.
|
36
|
+
|
37
|
+
## License
|
38
|
+
MIT for now.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new :test do |t|
|
5
|
+
t.pattern = 'spec/**/*_spec.rb'
|
6
|
+
t.libs.push 'spec'
|
7
|
+
# bundle exec rake test TESTOPTS='-v'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :build => :test do
|
11
|
+
system('gem build couchdb-ruby-server.gemspec')
|
12
|
+
# gem install couchdb-ruby-server-1.0.0.gem
|
13
|
+
# gem push couchdb-ruby-server-1.0.0.gem
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
data/TODO.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env RUBY_GC_MALLOC_LIMIT=24000000 ruby
|
2
|
+
require 'couchdb-ruby-server'
|
3
|
+
require 'argparser'
|
4
|
+
|
5
|
+
args = ArgParser.new :program => 'couchdb-ruby-server',
|
6
|
+
:version => CouchdbRubyServer::VERSION,
|
7
|
+
:info => 'CouchDB query server to run ruby design docs.',
|
8
|
+
:licence => 'MIT',
|
9
|
+
:copyright=> '2014-2015 sinmsinm@gmail.com',
|
10
|
+
:options => [{ :names => %w[r require],
|
11
|
+
:argument => 'LIB',
|
12
|
+
:multiple => true,
|
13
|
+
:help => 'Aux library to require.' }]
|
14
|
+
args['require'].value.each do |r|
|
15
|
+
require r
|
16
|
+
end
|
17
|
+
|
18
|
+
CouchdbRubyServer.run
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.expand_path('../lib/couchdb-ruby-server/version.rb', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'couchdb-ruby-server'
|
6
|
+
s.version = CouchdbRubyServer::VERSION
|
7
|
+
s.authors = ['sinm']
|
8
|
+
s.email = 'sinm.sinm@gmail.com'
|
9
|
+
s.summary = 'CouchDB query server to run ruby design docs'
|
10
|
+
s.description = '== CouchDB query server to run ruby design docs'
|
11
|
+
s.homepage = 'https://github.com/sinm/couchdb-ruby-server'
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.files = `git ls-files -z`.split("\x0")
|
14
|
+
s.test_files = `git ls-files -z spec/`.split("\0")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
s.bindir = 'bin'
|
17
|
+
s.executables << 'couchdb-ruby-server'
|
18
|
+
# s.extra_rdoc_files = 'README.md'
|
19
|
+
# s.rdoc_options << '--title' << 'couchdb-ruby-server' <<
|
20
|
+
# '--main' << 'README' <<
|
21
|
+
# '--markup' << 'markdown' <<
|
22
|
+
# '--line-numbers'
|
23
|
+
s.add_development_dependency 'bundler', '~> 1'
|
24
|
+
s.add_development_dependency 'rake', '~> 10'
|
25
|
+
s.add_development_dependency 'minitest', '~> 4'
|
26
|
+
s.add_dependency 'argparser', '~> 1'
|
27
|
+
s.add_dependency 'oj', '~> 2.7'
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"folders":
|
3
|
+
[
|
4
|
+
{
|
5
|
+
"path": "/Users/sinm/Projects/couchdb-ruby-server"
|
6
|
+
}
|
7
|
+
],
|
8
|
+
"build_systems":
|
9
|
+
[
|
10
|
+
{
|
11
|
+
"name": "Run tests",
|
12
|
+
"cmd": ["bundle", "exec", "rake", "test"],
|
13
|
+
"selector": "source.rb"
|
14
|
+
}
|
15
|
+
],
|
16
|
+
"settings":
|
17
|
+
{
|
18
|
+
"ensure_newline_at_eof_on_save": true,
|
19
|
+
"rulers":
|
20
|
+
[
|
21
|
+
80
|
22
|
+
],
|
23
|
+
"tab_size": 2,
|
24
|
+
"translate_tabs_to_spaces": true,
|
25
|
+
"trim_trailing_white_space_on_save": true
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,330 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
$stdin.sync = true
|
5
|
+
$stdout.sync = true
|
6
|
+
|
7
|
+
module CouchdbRubyServer
|
8
|
+
class Error < RuntimeError
|
9
|
+
attr_reader :key
|
10
|
+
def initialize key
|
11
|
+
@key = key
|
12
|
+
end
|
13
|
+
end
|
14
|
+
class Fatal < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.update_case fn, args
|
18
|
+
result = fn.call(*args)
|
19
|
+
return ["up", result[0], maybe_wrap_response(result[1])]
|
20
|
+
end
|
21
|
+
|
22
|
+
MIME_TYPES = {
|
23
|
+
:all => "*/*",
|
24
|
+
:text => "text/plain; charset=utf-8",
|
25
|
+
:html => "text/html; charset=utf-8",
|
26
|
+
:xhtml => "application/xhtml+xml",
|
27
|
+
:xml => "application/xml",
|
28
|
+
:js => "text/javascript",
|
29
|
+
:css => "text/css",
|
30
|
+
:ics => "text/calendar",
|
31
|
+
:csv => "text/csv; charset=utf-8",
|
32
|
+
:rss => "application/rss+xml",
|
33
|
+
:atom => "application/atom+xml",
|
34
|
+
:yaml => "application/x-yaml",
|
35
|
+
:multipart_form => "multipart/form-data",
|
36
|
+
:url_encoded_form => "application/x-www-form-urlencoded",
|
37
|
+
:json => "application/json; charset=utf-8"
|
38
|
+
}
|
39
|
+
|
40
|
+
TYPES_MIME = Hash[MIME_TYPES.map {|k, v| [v, k] }]
|
41
|
+
|
42
|
+
def self.to_json(obj)
|
43
|
+
Oj.dump(obj)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.json_parse(str)
|
47
|
+
return nil if str.nil?
|
48
|
+
Oj.load str
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.log message
|
52
|
+
respond ['log', "#{message}"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.respond obj
|
56
|
+
puts to_json obj
|
57
|
+
rescue
|
58
|
+
log "respond error converting object to JSON: #{$!.message}\n#{$!.backtrace.join("\n")}"
|
59
|
+
puts 'false'
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.respond_error error, reason
|
63
|
+
if reason.respond_to?(:message) && reason.respond_to?(:backtrace)
|
64
|
+
reason = "#{reason.class.name}: #{reason.message}\n#{reason.backtrace.join("\n")}"
|
65
|
+
end
|
66
|
+
respond ['error', error, reason]
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.respond_fatal error, reason
|
70
|
+
respond_error error, reason
|
71
|
+
exit(2)
|
72
|
+
end
|
73
|
+
|
74
|
+
COMPILED_PROCS = {}
|
75
|
+
|
76
|
+
def self.compile_proc source, binding, name
|
77
|
+
source_hash = source.hash
|
78
|
+
fn = COMPILED_PROCS[source_hash]
|
79
|
+
return fn if fn
|
80
|
+
begin
|
81
|
+
fn = eval source, binding, name, 1
|
82
|
+
raise "#{name} must respond_to #call" unless fn.respond_to?(:call)
|
83
|
+
COMPILED_PROCS[source_hash] = fn
|
84
|
+
rescue Exception
|
85
|
+
respond_error :compilation_error, $!
|
86
|
+
end
|
87
|
+
fn
|
88
|
+
end
|
89
|
+
|
90
|
+
@@map_results = []
|
91
|
+
@@callables = []
|
92
|
+
@@ddocs = {}
|
93
|
+
#list state
|
94
|
+
@@chunks = []
|
95
|
+
@@start_resp = {}
|
96
|
+
@@got_row = false
|
97
|
+
@@last_row = false
|
98
|
+
@@provides_used = []
|
99
|
+
@@resp_mime = nil
|
100
|
+
#view filter
|
101
|
+
@@view_emit = nil
|
102
|
+
|
103
|
+
def self.emit(key, value)
|
104
|
+
if @@view_emit.nil?
|
105
|
+
@@map_results.push [key, value]
|
106
|
+
else
|
107
|
+
@@view_emit = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.provides(*args, &block)
|
112
|
+
@@provides_used << {:format => args[0], :block => block}.merge(
|
113
|
+
args.size > 1 ? {:mime => args[1]} : {}
|
114
|
+
)
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.start(resp)
|
119
|
+
@@start_resp = resp if resp
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.send(chunk)
|
124
|
+
@@chunks << "#{chunk}"
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.get_row
|
129
|
+
return nil if @@last_row
|
130
|
+
if not @@got_row
|
131
|
+
@@got_row = true
|
132
|
+
render_headers
|
133
|
+
respond ["start", @@chunks, @@start_resp]
|
134
|
+
@@start_resp = {}
|
135
|
+
else
|
136
|
+
respond ["chunks", @@chunks]
|
137
|
+
end
|
138
|
+
@@chunks = []
|
139
|
+
json = json_parse ARGF.gets
|
140
|
+
if json[0] == 'list_end'
|
141
|
+
@@last_row = true
|
142
|
+
return nil
|
143
|
+
elsif json[0] != 'list_row'
|
144
|
+
respond_fatal :list_error, "not a row: #{json[0]}"
|
145
|
+
end
|
146
|
+
json[1]
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def self.maybe_wrap_response resp
|
151
|
+
{:body=>''}.merge(
|
152
|
+
resp.respond_to?(:to_hash) ? resp.to_hash : {:body=>"#{resp}"})
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.run_reduce(srcs, ks, vs, rereduce, b)
|
156
|
+
fns = srcs.collect { |src| compile_proc(src, b, "reduce (#{src[0..100]}...)") || return }
|
157
|
+
reductions = fns.collect { |fn|
|
158
|
+
begin
|
159
|
+
fn.call(ks, vs, rereduce)
|
160
|
+
rescue
|
161
|
+
log "#{rereduce ? 'rereduce' : 'reduce'} raised exception", $!
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
}
|
165
|
+
respond [true, reductions]
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.reset_list
|
169
|
+
@@got_row = false
|
170
|
+
@@last_row = false
|
171
|
+
@@chunks = []
|
172
|
+
@@start_resp = {}
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.reset_provides
|
176
|
+
@@provides_used = []
|
177
|
+
@@resp_mime = nil
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.render_headers
|
181
|
+
@@start_resp[:headers] ||= {}
|
182
|
+
@@start_resp[:headers]["Content-Type"] ||= @@resp_mime if @@resp_mime
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.run_provides req
|
186
|
+
format = :unknown
|
187
|
+
best_fun = if req['query'] and req['query']['format']
|
188
|
+
format = req['query']['format']
|
189
|
+
@@provides_used.find do |p|
|
190
|
+
p[:format].to_s == format
|
191
|
+
end
|
192
|
+
elsif req['headers'] && (format = req['headers']['Accept'])
|
193
|
+
f = nil
|
194
|
+
format.split(',').each do |a|
|
195
|
+
break if (f = a =~ /^\*\/\*/ ? @@provides_used.first :
|
196
|
+
@@provides_used.find do |p|
|
197
|
+
a == p[:mime] or p[:format] == TYPES_MIME[a]
|
198
|
+
end)
|
199
|
+
end
|
200
|
+
f
|
201
|
+
else
|
202
|
+
@@provides_used.first
|
203
|
+
end
|
204
|
+
raise "format #{format} not supported" unless best_fun
|
205
|
+
raise "no block in provides" unless best_fun[:block] &&
|
206
|
+
best_fun[:block].respond_to?(:call)
|
207
|
+
@@resp_mime = best_fun[:mime] || MIME_TYPES[best_fun[:format]]
|
208
|
+
best_fun[:block].call
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.run
|
212
|
+
log "CouchdbRubyServer started, RUBY_VERSION: #{RUBY_VERSION}"
|
213
|
+
log "RUBY_GC_MALLOC_LIMIT=#{ENV['RUBY_GC_MALLOC_LIMIT']}"
|
214
|
+
while cmd = $stdin.gets
|
215
|
+
cmd = json_parse cmd
|
216
|
+
case cmd[0]
|
217
|
+
when "reset"
|
218
|
+
@@callables = []
|
219
|
+
respond(true)
|
220
|
+
when "add_fun"
|
221
|
+
fn = compile_proc cmd[1], binding, "map (#{cmd[1][0..100]}...)"
|
222
|
+
next unless fn
|
223
|
+
@@callables << fn
|
224
|
+
respond(true)
|
225
|
+
when "map_doc"
|
226
|
+
results = []
|
227
|
+
@@view_emit = nil
|
228
|
+
doc = cmd[1]
|
229
|
+
doc.freeze
|
230
|
+
@@callables.each do |callable|
|
231
|
+
@@map_results = []
|
232
|
+
begin
|
233
|
+
callable.call(doc)
|
234
|
+
results.push(@@map_results)
|
235
|
+
rescue
|
236
|
+
log "map raised exception with doc._id #{doc['_id']}", $!
|
237
|
+
results.push([])
|
238
|
+
end
|
239
|
+
end
|
240
|
+
respond(results)
|
241
|
+
when "reduce"
|
242
|
+
ks, vs = cmd[2].transpose
|
243
|
+
run_reduce(cmd[1], ks, vs, false, binding)
|
244
|
+
when "rereduce"
|
245
|
+
run_reduce(cmd[1], nil, cmd[2], true, binding)
|
246
|
+
when "ddoc"
|
247
|
+
cmd.shift
|
248
|
+
ddoc_id = cmd.shift
|
249
|
+
if ddoc_id == 'new'
|
250
|
+
ddoc_id = cmd.shift
|
251
|
+
@@ddocs[ddoc_id] = cmd.shift
|
252
|
+
respond true
|
253
|
+
else
|
254
|
+
ddoc = @@ddocs[ddoc_id]
|
255
|
+
if not ddoc
|
256
|
+
respond_fatal :query_protocol_error, "uncached design doc: #{ddoc_id}"
|
257
|
+
end
|
258
|
+
point = ddoc
|
259
|
+
path = cmd.shift
|
260
|
+
begin
|
261
|
+
path.each do |level|
|
262
|
+
point = (point[level] || raise("missing #{level} function in #{ddoc_id}"))
|
263
|
+
end
|
264
|
+
rescue
|
265
|
+
respond_error :not_found, $!
|
266
|
+
next
|
267
|
+
end
|
268
|
+
next unless (fn = compile_proc point, binding, "#{ddoc['_id']}/#{path.join("/")}")
|
269
|
+
begin
|
270
|
+
response = nil
|
271
|
+
args = cmd.shift
|
272
|
+
case path[0]
|
273
|
+
when "updates"
|
274
|
+
respond update_case fn, args
|
275
|
+
when "lists"
|
276
|
+
#head = args[0]
|
277
|
+
reset_list
|
278
|
+
reset_provides
|
279
|
+
tail = fn.call(*args)
|
280
|
+
begin
|
281
|
+
tail = run_provides(args[1]) unless @@provides_used.empty?
|
282
|
+
rescue
|
283
|
+
respond_error :not_acceptable, $!
|
284
|
+
next
|
285
|
+
end
|
286
|
+
get_row unless @@got_row
|
287
|
+
@@chunks << tail unless tail.nil?
|
288
|
+
respond ["end", @@chunks]
|
289
|
+
when "shows"
|
290
|
+
respond ["resp", maybe_wrap_response(fn.call(*args))]
|
291
|
+
when "filters"
|
292
|
+
res = []
|
293
|
+
args[0].each do |doc|
|
294
|
+
res.push(fn.call(doc, args[1]) ? true : false)
|
295
|
+
end
|
296
|
+
respond [true, res]
|
297
|
+
when "views"
|
298
|
+
res = []
|
299
|
+
args[0].each do |doc|
|
300
|
+
@@view_emit = false
|
301
|
+
fn.call doc
|
302
|
+
res.push(@@view_emit ? true : false)
|
303
|
+
end
|
304
|
+
respond [true, res]
|
305
|
+
when "validate_doc_update"
|
306
|
+
begin
|
307
|
+
fn.call(*args)
|
308
|
+
respond 1
|
309
|
+
rescue
|
310
|
+
respond({:forbidden => "bad doc"})
|
311
|
+
end
|
312
|
+
else
|
313
|
+
respond_fatal :unknown_command, "unknown ddoc command #{path[0]}"
|
314
|
+
next
|
315
|
+
end # case ddoc
|
316
|
+
rescue Fatal
|
317
|
+
respond_fatal $!.key, $!.message
|
318
|
+
rescue Error
|
319
|
+
respond_error $!.key, $!.message
|
320
|
+
rescue
|
321
|
+
respond_error :render_error, $!
|
322
|
+
end
|
323
|
+
end
|
324
|
+
else
|
325
|
+
respond_fatal :unknown_command, "unknown command #{cmd[0]}"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|