couchdb-ruby-server 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,21 @@
1
+ *~
2
+ ~*
3
+ .DS_Store
4
+ ._.DS_Store
5
+ thumbs.db
6
+ *.log
7
+ *.pid
8
+ *.dump
9
+ *.prof
10
+ *.prof.gif
11
+ *.prof.symbols
12
+ .svn/
13
+ .hg/
14
+ *.sublime-workspace
15
+ .env
16
+ .rbenv-version
17
+ .ruby-version
18
+ Gemfile.lock
19
+ core
20
+ *.gem
21
+ /doc/
data/.hound.yml ADDED
@@ -0,0 +1,4 @@
1
+ # NOTE: it works on pull requests only
2
+ ruby:
3
+ config_file: .rubocop.yml
4
+ enabled: true
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.8.7"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - "2.1" # latest 2.1.x
7
+ - "2.2" # latest 2.2.x
8
+ - "jruby-18mode"
9
+ - "jruby-19mode"
10
+ script:
11
+ CODECLIMATE_REPO_TOKEN=PLACE_TOKEN_HERE bundle exec rake test
12
+ branches:
13
+ - "master"
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ gem 'codeclimate-test-reporter', :group => :development,
4
+ :require => nil,
5
+ :platforms => [:ruby_20]
data/HISTORY.md ADDED
@@ -0,0 +1,4 @@
1
+ # 1.0.0
2
+ 2015-MM-DD
3
+
4
+ * First public release
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
+ [![Build Status](https://travis-ci.org/sinm/couchdb-ruby-server.svg?branch=master)](https://travis-ci.org/sinm/couchdb-ruby-server) [![Gem Version](https://badge.fury.io/rb/couchdb-ruby-server.svg)](http://badge.fury.io/rb/couchdb-ruby-server) [![Dependency Status](https://gemnasium.com/sinm/couchdb-ruby-server.svg)](https://gemnasium.com/sinm/couchdb-ruby-server) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/sinm/couchdb-ruby-server/master/LICENSE.txt) [![Code Climate](https://codeclimate.com/github/sinm/couchdb-ruby-server/badges/gpa.svg)](https://codeclimate.com/github/sinm/couchdb-ruby-server) [![Test Coverage](https://codeclimate.com/github/sinm/couchdb-ruby-server/badges/coverage.svg)](https://codeclimate.com/github/sinm/couchdb-ruby-server) [![Inline docs](http://inch-ci.org/github/sinm/couchdb-ruby-server.svg?branch=master)](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,4 @@
1
+ # TODO file
2
+
3
+ 3. Client and server libs -- see what i have to copy here
4
+ 4. Implement $SAFE=4 thread, set safe level via args
@@ -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