couch-shell 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -1,11 +1,14 @@
1
1
  couch-shell is a basic shell to interact with a CouchDB server.
2
2
 
3
- Use `ruby -I lib bin/couch-shell SERVER_URL' to start from a repository
4
- checkout.
3
+ Install with rubygems:
4
+
5
+ $ sudo gem install couch-shell
6
+
7
+ Only tested with Ruby 1.9!
5
8
 
6
9
  Example session:
7
10
 
8
- $ ruby19 -I lib bin/couch-shell http://mambofulani.couchone.com/
11
+ $ couch-shell http://mambofulani.couchone.com/
9
12
  couch-shell 0.0.1
10
13
  Set server to http://mambofulani.couchone.com:80/
11
14
  GET / 200 OK
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module CouchShell
4
+
5
+ class EvalContext < BasicObject
6
+
7
+ def initialize(vardict)
8
+ @vardict = vardict
9
+ end
10
+
11
+ def method_missing(msg, *args)
12
+ if args.empty?
13
+ @vardict.lookup_var msg.to_s
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,99 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "json"
4
+
5
+ module CouchShell
6
+
7
+ class JsonValue < BasicObject
8
+
9
+ def self.wrap(ruby_value)
10
+ case ruby_value
11
+ when ::Hash
12
+ h = ::Hash.new(ruby_value.size)
13
+ ruby_value.each { |k, v|
14
+ h[k] = wrap(v)
15
+ }
16
+ JsonValue.new(h, ruby_value)
17
+ when ::Array
18
+ a = ::Array.new(ruby_value.size)
19
+ ruby_value.each_with_index { |v, i|
20
+ a[i] = wrap(v)
21
+ }
22
+ JsonValue.new(a, ruby_value)
23
+ else
24
+ JsonValue.new(ruby_value, ruby_value)
25
+ end
26
+ end
27
+
28
+ attr_reader :type
29
+ attr_reader :ruby_value
30
+
31
+ def initialize(value, ruby_value)
32
+ @value = value
33
+ @ruby_value = ruby_value
34
+ @type = case @value
35
+ when ::Hash
36
+ :object
37
+ when ::Array
38
+ :array
39
+ when ::String
40
+ :string
41
+ when ::Numeric
42
+ :number
43
+ when ::TrueClass
44
+ :boolean
45
+ when ::FalseClass
46
+ :boolean
47
+ when nil
48
+ :null
49
+ else
50
+ raise "#{value.class} is not a valid json type"
51
+ end
52
+ end
53
+
54
+ def respond_to?(msg)
55
+ msg == :format || msg == :couch_shell_format_string ||
56
+ msg == :type || msg == :ruby_value || msg == :to_s ||
57
+ (@type == :object && @value.has_key?(msg.to_s)) ||
58
+ @value.respond_to?(msg)
59
+ end
60
+
61
+ def method_missing(msg, *args)
62
+ if args.empty? && @type == :object
63
+ msg_str = msg.to_s
64
+ if @value.has_key?(msg_str)
65
+ return @value[msg_str]
66
+ end
67
+ end
68
+ @value.__send__(msg, *args)
69
+ end
70
+
71
+ def to_s
72
+ case type
73
+ when :object, :array
74
+ ::JSON.generate(@ruby_value)
75
+ when :null
76
+ "null"
77
+ else
78
+ @ruby_value.to_s
79
+ end
80
+ end
81
+
82
+ def format
83
+ case type
84
+ when :object, :array
85
+ ::JSON.pretty_generate(@ruby_value)
86
+ when :null
87
+ "null"
88
+ when
89
+ @ruby_value.to_s
90
+ end
91
+ end
92
+
93
+ def couch_shell_format_string
94
+ format
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,72 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "json"
4
+ require "couch-shell/json_value"
5
+
6
+ module CouchShell
7
+
8
+ class Response
9
+
10
+ JSON_CONTENT_TYPES = [
11
+ "application/json", "text/plain"
12
+ ].freeze
13
+
14
+ # +response+ is a Net::HTTP response
15
+ def initialize(response)
16
+ @res = response
17
+ @json = nil
18
+ @computed_json = false
19
+ end
20
+
21
+ # Response body parsed as json. nil if body is empty, false if
22
+ # parsing failed.
23
+ def json
24
+ unless @computed_json
25
+ if JSON_CONTENT_TYPES.include?(content_type) &&
26
+ !@res.body.nil? && !@res.body.empty?
27
+ begin
28
+ @json = JsonValue.wrap(JSON.parse(@res.body))
29
+ rescue JSON::ParserError
30
+ @json = false
31
+ end
32
+ end
33
+ @computed_json = true
34
+ end
35
+ @json
36
+ end
37
+
38
+ def code
39
+ @res.code
40
+ end
41
+
42
+ def message
43
+ @res.message
44
+ end
45
+
46
+ def ok?
47
+ code.start_with?("2")
48
+ end
49
+
50
+ def body
51
+ @res.body
52
+ end
53
+
54
+ def content_type
55
+ @res.content_type
56
+ end
57
+
58
+ def attr(name, altname = nil)
59
+ name = name.to_sym
60
+ altname = altname ? altname.to_sym : nil
61
+ if json
62
+ if json.respond_to?(name)
63
+ json.__send__ name
64
+ elsif altname && json.respond_to?(altname)
65
+ json.__send__ altname
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,87 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module CouchShell
4
+
5
+ # Not threadsafe!
6
+ class RingBuffer
7
+
8
+ class UninitializedAccess < StandardError
9
+
10
+ attr_reader :index
11
+
12
+ def initialize(index)
13
+ @index = index
14
+ end
15
+
16
+ def message
17
+ "uninitalized RingBuffer access at index #@index"
18
+ end
19
+
20
+ end
21
+
22
+ def initialize(size)
23
+ @ary = Array.new(size, nil)
24
+ @index = nil
25
+ @written = 0
26
+ end
27
+
28
+ def size
29
+ @ary.size
30
+ end
31
+
32
+ def initialized_size
33
+ @written
34
+ end
35
+
36
+ def empty?
37
+ @written == 0
38
+ end
39
+
40
+ def current
41
+ if @index.nil?
42
+ if block_given?
43
+ nil
44
+ else
45
+ raise UninitializedAccess.new(0)
46
+ end
47
+ else
48
+ if block_given?
49
+ yield @ary[index]
50
+ else
51
+ @ary[index]
52
+ end
53
+ end
54
+ end
55
+
56
+ def readable_index?(i)
57
+ i >= 0 && i < @written
58
+ end
59
+
60
+ # index of current (last written) element, or nil if empty
61
+ def index
62
+ @index
63
+ end
64
+
65
+ def [](i)
66
+ i = i.to_int
67
+ if i >= @written
68
+ raise UninitializedAccess.new(i)
69
+ end
70
+ @ary[i]
71
+ end
72
+
73
+ def <<(elem)
74
+ if @index.nil? || @index == size - 1
75
+ @index = 0
76
+ else
77
+ @index += 1
78
+ end
79
+ @ary[@index] = elem
80
+ if @written < @index + 1
81
+ @written = @index + 1
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -2,8 +2,10 @@
2
2
 
3
3
  require "uri"
4
4
  require "net/http"
5
- require "json"
6
5
  require "highline"
6
+ require "couch-shell/response"
7
+ require "couch-shell/ring_buffer"
8
+ require "couch-shell/eval_context"
7
9
 
8
10
  module CouchShell
9
11
 
@@ -25,8 +27,12 @@ module CouchShell
25
27
 
26
28
  end
27
29
 
28
- PREDEFINED_VARS = ["uuid", "id", "rev", "idr", "content-type"].freeze
29
- JSON_CONTENT_TYPES = ["application/json", "text/plain"].freeze
30
+ PREDEFINED_VARS = [
31
+ "uuid", "id", "rev", "idr",
32
+ "content-type", "server"
33
+ ].freeze
34
+
35
+ JSON_DOC_START_RX = /\A[ \t\n\r]*[\(\{]/
30
36
 
31
37
  def initialize(stdin, stdout, stderr)
32
38
  @stdin = stdin
@@ -35,15 +41,26 @@ module CouchShell
35
41
  @server_url = nil
36
42
  @pathstack = []
37
43
  @highline = HighLine.new(@stdin, @stdout)
38
- @last_response = nil
39
- @last_response_json = nil
44
+ @responses = RingBuffer.new(10)
45
+ @eval_context = EvalContext.new(self)
46
+ end
47
+
48
+ def normalize_server_url(url)
49
+ return nil if url.nil?
50
+ # remove trailing slash
51
+ url = url.sub(%r{/\z}, '')
52
+ # prepend http:// if scheme is omitted
53
+ if url =~ /\A\p{Alpha}(?:\p{Alpha}|\p{Digit}|\+|\-|\.)*:/
54
+ url
55
+ else
56
+ "http://#{url}"
57
+ end
40
58
  end
41
59
 
42
60
  def server=(url)
43
61
  if url
44
- @server_url = URI.parse(url.sub(%r{/\z}, ''))
45
- msg "Set server to #{@server_url.scheme}://" +
46
- "#{@server_url.host}:#{@server_url.port}/#{@server_url.path}"
62
+ @server_url = URI.parse(normalize_server_url(url))
63
+ msg "Set server to #{lookup_var 'server'}"
47
64
  request("GET", nil)
48
65
  else
49
66
  @server_url = nil
@@ -87,49 +104,14 @@ module CouchShell
87
104
  @stderr.puts @highline.color(str, :red)
88
105
  end
89
106
 
90
- def response_info(str)
91
- @stdout.puts @highline.color(str, :cyan)
92
- end
93
-
94
- def print_last_response
95
- res = @last_response
96
- response_info "#{res.code} #{res.message}"
97
- if res.body
98
- if JSON_CONTENT_TYPES.include?(res.content_type)
99
- @stdout.puts JSON.pretty_generate(last_response_json)
100
- else
101
- @stdout.puts res.body if res.body
102
- end
103
- end
104
- end
105
107
 
106
- def response_json(res)
107
- if !res || res.body.nil? || res.body.empty?
108
- return nil
109
- end
110
- JSON.parse res.body
111
- rescue ParseError
112
- false
113
- end
114
-
115
- def last_response_json
116
- if @last_response_json
117
- return @last_response_json
118
- end
119
- if @last_response
120
- @last_response_json = response_json(@last_response)
121
- end
122
- end
123
-
124
- def last_response_ok?
125
- @last_response && @last_response.code == "200"
126
- end
127
-
128
- def last_response_attr(name, altname = nil)
129
- json = last_response_json
130
- if json && json.kind_of?(Hash) &&
131
- (json.has_key?(name) || json.has_key?(altname))
132
- json.has_key?(name) ? json[name] : json[altname]
108
+ def print_response(res, label = "")
109
+ @stdout.print @highline.color("#{res.code} #{res.message}", :cyan)
110
+ msg " #{label}"
111
+ if res.json
112
+ @stdout.puts res.json.format
113
+ elsif res.body
114
+ @stdout.puts res.body
133
115
  end
134
116
  end
135
117
 
@@ -151,17 +133,25 @@ module CouchShell
151
133
  Net::HTTP::Get
152
134
  when "PUT"
153
135
  Net::HTTP::Put
136
+ when "POST"
137
+ Net::HTTP::Post
154
138
  when "DELETE"
155
139
  Net::HTTP::Delete
156
140
  else
157
141
  raise "unsupported http method: `#{method}'"
158
142
  end).new(fpath)
159
- req.body = body if body
160
- res = http.request(req)
161
- @last_response = res
162
- @last_response_json = nil
143
+ if body
144
+ req.body = body
145
+ if req.content_type.nil? && req.body =~ JSON_DOC_START_RX
146
+ req.content_type = "application/json"
147
+ end
148
+ end
149
+ res = Response.new(http.request(req))
150
+ @responses << res
163
151
  rescode = res.code
164
- print_last_response
152
+ vars = ["r#{@responses.index}"]
153
+ vars << ["j#{@responses.index}"] if res.json
154
+ print_response res, " vars: #{vars.join(', ')}"
165
155
  end
166
156
  rescode
167
157
  end
@@ -237,7 +227,7 @@ module CouchShell
237
227
  String.new.force_encoding(str.encoding).tap { |res|
238
228
  escape = false
239
229
  dollar = false
240
- var = nil
230
+ expr = nil
241
231
  str.each_char { |c|
242
232
  if escape
243
233
  res << c
@@ -250,21 +240,21 @@ module CouchShell
250
240
  next
251
241
  elsif c == '('
252
242
  if dollar
253
- var = ""
243
+ expr = ""
254
244
  else
255
245
  res << c
256
246
  end
257
247
  elsif c == ')'
258
- if var
259
- res << var_interpolation_val(var)
260
- var = nil
248
+ if expr
249
+ res << shell_eval(expr)
250
+ expr = nil
261
251
  else
262
252
  res << c
263
253
  end
264
254
  elsif dollar
265
255
  res << "$"
266
- elsif var
267
- var << c
256
+ elsif expr
257
+ expr << c
268
258
  else
269
259
  res << c
270
260
  end
@@ -273,12 +263,16 @@ module CouchShell
273
263
  }
274
264
  end
275
265
 
276
- def var_interpolation_val(var)
266
+ def shell_eval(expr)
267
+ @eval_context.instance_eval(expr)
268
+ end
269
+
270
+ def lookup_var(var)
277
271
  case var
278
272
  when "uuid"
279
273
  command_uuids nil
280
- if last_response_ok?
281
- json = last_response_json
274
+ if @responses.current(&:ok?)
275
+ json = @responses.current.json
282
276
  if json && (uuids = json["uuids"]) && uuids.kind_of?(Array) && uuids.size > 0
283
277
  uuids[0]
284
278
  else
@@ -289,15 +283,40 @@ module CouchShell
289
283
  raise ShellUserError, "interpolation failed"
290
284
  end
291
285
  when "id"
292
- last_response_attr("id", "_id") or
286
+ @responses.current { |r| r.attr "id", "_id" } or
293
287
  raise ShellUserError, "variable `id' not set"
294
288
  when "rev"
295
- last_response_attr("rev", "_rev") or
289
+ @responses.current { |r| r.attr "rev", "_rev" } or
296
290
  raise ShellUserError, "variable `rev' not set"
297
291
  when "idr"
298
- "#{var_interpolation_val 'id'}?rev=#{var_interpolation_val 'rev'}"
292
+ "#{lookup_var 'id'}?rev=#{lookup_var 'rev'}"
299
293
  when "content-type"
300
- @last_response && @last_response.content_type
294
+ @responses.current(&:content_type)
295
+ when "server"
296
+ if @server_url
297
+ u = @server_url
298
+ "#{u.scheme}://#{u.host}:#{u.port}#{u.path}"
299
+ else
300
+ raise ShellUserError, "variable `server' not set"
301
+ end
302
+ when /\Ar(\d)\z/
303
+ i = $1.to_i
304
+ if @responses.readable_index?(i)
305
+ @responses[i]
306
+ else
307
+ raise ShellUserError, "no response index #{i}"
308
+ end
309
+ when /\Aj(\d)\z/
310
+ i = $1.to_i
311
+ if @responses.readable_index?(i)
312
+ if @responses[i].json
313
+ @responses[i].json
314
+ else
315
+ raise ShellUserError, "no json in response #{i}"
316
+ end
317
+ else
318
+ raise ShellUserError, "no response index #{i}"
319
+ end
301
320
  else
302
321
  raise UndefinedVariable.new(var)
303
322
  end
@@ -312,6 +331,15 @@ module CouchShell
312
331
  request "PUT", interpolate(url), body
313
332
  end
314
333
 
334
+ def command_post(argstr)
335
+ if argstr =~ JSON_DOC_START_RX
336
+ url, body = nil, argstr
337
+ else
338
+ url, body = argstr.split(/\s+/, 2)
339
+ end
340
+ request "POST", interpolate(url), body
341
+ end
342
+
315
343
  def command_delete(argstr)
316
344
  request "DELETE", interpolate(argstr)
317
345
  end
@@ -345,10 +373,27 @@ module CouchShell
345
373
 
346
374
  def command_print(argstr)
347
375
  unless argstr
348
- errmsg "variable name required"
376
+ errmsg "expression required"
349
377
  return
350
378
  end
351
- @stdout.puts var_interpolation_val(argstr)
379
+ @stdout.puts shell_eval(argstr)
380
+ end
381
+
382
+ def command_format(argstr)
383
+ unless argstr
384
+ errmsg "expression required"
385
+ return
386
+ end
387
+ val = shell_eval(argstr)
388
+ if val.respond_to?(:couch_shell_format_string)
389
+ @stdout.puts val.couch_shell_format_string
390
+ else
391
+ @stdout.puts val
392
+ end
393
+ end
394
+
395
+ def command_server(argstr)
396
+ self.server = argstr
352
397
  end
353
398
 
354
399
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CouchShell
4
4
 
5
- VERSION = "0.0.1"
5
+ VERSION = "0.0.2"
6
6
 
7
7
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 2
9
+ version: 0.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Stefan Lang
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-17 00:00:00 +01:00
17
+ date: 2011-01-19 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -27,6 +27,10 @@ extensions: []
27
27
  extra_rdoc_files: []
28
28
 
29
29
  files:
30
+ - lib/couch-shell/eval_context.rb
31
+ - lib/couch-shell/response.rb
32
+ - lib/couch-shell/ring_buffer.rb
33
+ - lib/couch-shell/json_value.rb
30
34
  - lib/couch-shell/version.rb
31
35
  - lib/couch-shell/shell.rb
32
36
  - lib/couch-shell.rb