couch-shell 0.0.1 → 0.0.2

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.
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