couch-shell 0.0.4 → 0.0.5

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.
@@ -2,4 +2,4 @@
2
2
 
3
3
  require "couch-shell"
4
4
 
5
- CouchShell.run ARGV
5
+ exit CouchShell.run(ARGV)
@@ -1,21 +1,54 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require "couch-shell/shell"
4
- require "couch-shell/version"
4
+ require "couch-shell/commandline"
5
5
 
6
6
  module CouchShell
7
7
 
8
8
  def self.run(args)
9
- puts "couch-shell #{VERSION}"
10
- shell = Shell.new(STDIN, STDOUT, STDERR)
11
- if ARGV[0]
12
- shell.server = ARGV[0]
13
- end
14
- if ARGV[1]
15
- shell.cd ARGV[1], true
9
+ server = nil
10
+ path = nil
11
+ user = nil
12
+
13
+ begin
14
+ Commandline.new { |cmd|
15
+ cmd.arg { |arg|
16
+ if server.nil?
17
+ server = arg
18
+ elsif path.nil?
19
+ path = arg
20
+ else
21
+ raise Commandline::Error, "too many arguments"
22
+ end
23
+ }
24
+ cmd.opt "-u", "--user=USER",
25
+ "Connect with CouchDB as USER. Password will be asked." do |val|
26
+ user = val
27
+ end
28
+ cmd.opt "-h", "--help", "Show help and exit." do
29
+ STDOUT.puts "Usage: couch-shell [options] [--] [HOSTNAME[:PORT]] [PATH]"
30
+ STDOUT.puts
31
+ STDOUT.puts "Available options:"
32
+ STDOUT.puts cmd.optlisting
33
+ STDOUT.puts
34
+ STDOUT.puts "Example: couch-shell -u admin 127.0.0.1:5984 mydb"
35
+ return 0
36
+ end
37
+ }.process(args)
38
+ rescue Commandline::Error => e
39
+ STDERR.puts e.message
40
+ STDERR.puts "Run `couch-shell -h' for help."
41
+ return 1
16
42
  end
17
- shell.repl
18
- exit 0
43
+
44
+ shell = Shell.new(STDIN, STDOUT, STDERR)
45
+ shell.execute "user #{user}" if user
46
+ shell.execute "server #{server}" if server
47
+ shell.execute "cg #{path}" if path
48
+
49
+ shell.read_execute_loop
50
+
51
+ 0
19
52
  end
20
53
 
21
54
  end
@@ -0,0 +1,213 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module CouchShell
4
+
5
+ class Commandline
6
+
7
+ class Error < StandardError
8
+ end
9
+
10
+ class ArgNotAllowedError < Error
11
+
12
+ attr_reader :arg
13
+
14
+ def initialize(arg)
15
+ @arg = arg
16
+ end
17
+
18
+ def message
19
+ "no argument allowed, argument `#{arg}' was given"
20
+ end
21
+
22
+ end
23
+
24
+ class UnknownOptError < Error
25
+
26
+ attr_reader :optname
27
+
28
+ def initialize(optname)
29
+ @optname = optname
30
+ end
31
+
32
+ def message
33
+ "unkown option `#{optname}'"
34
+ end
35
+
36
+ end
37
+
38
+ class OptArgMissingError < Error
39
+
40
+ attr_reader :optname
41
+
42
+ def initialize(optname)
43
+ @optname = optname
44
+ end
45
+
46
+ def message
47
+ "option `#{optname}' requires argument"
48
+ end
49
+
50
+ end
51
+
52
+ class Opt
53
+
54
+ attr_accessor :short, :long, :value, :doc, :action
55
+
56
+ def initialize
57
+ @short = nil
58
+ @long = nil
59
+ @value = nil
60
+ @doc = nil
61
+ @action = nil
62
+ end
63
+
64
+ def parse_spec!(optspec)
65
+ value = nil
66
+ case optspec
67
+ when /\A--([^-= ][^= ]+)(=[^= ]+)?\z/
68
+ raise "long option already set" if @long
69
+ @long = $1
70
+ value = ($2 ? $2[1..-1] : nil)
71
+ when /\A-([^- ])( [^= ]+)?\z/
72
+ raise "short option already set" if @short
73
+ @short = $1
74
+ value = ($2 ? $2[1..-1] : nil)
75
+ else
76
+ raise "invalid optspec `#{optspec}'"
77
+ end
78
+ if value
79
+ if @value && @value != value
80
+ raise "option value name mismatch: `#{value}' != `#{@value}'"
81
+ end
82
+ @value = value
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ def initialize
89
+ @arg_action = nil
90
+ @opts = []
91
+ @leading_help = nil
92
+ @trailing_help = nil
93
+ yield self
94
+ end
95
+
96
+ def arg(&block)
97
+ @arg_action = block
98
+ end
99
+
100
+ def opt(optspec1, optspec2 = nil, doc = nil, &block)
101
+ raise "optspec and action block required" unless optspec1 && block
102
+ option = Opt.new
103
+ option.doc = doc
104
+ option.parse_spec! optspec1
105
+ if optspec2 && doc
106
+ option.parse_spec! optspec2 if optspec2
107
+ elsif optspec2
108
+ option.doc = doc
109
+ end
110
+ option.action = block
111
+ @opts << option
112
+ end
113
+
114
+ def optlisting(indent = " ")
115
+ max_value_len = @opts.map(&:value).compact.map(&:length).max || 0
116
+ String.new.tap do |t|
117
+ @opts.each { |opt|
118
+ t << indent
119
+ if opt.short
120
+ t << "-#{opt.short} "
121
+ if opt.value
122
+ t << opt.value
123
+ t << (" " * (max_value_len - opt.value.length))
124
+ end
125
+ else
126
+ t << " " << (" " * max_value_len)
127
+ end
128
+ t << " "
129
+ if opt.long
130
+ t << "--#{opt.long}"
131
+ t << "=#{opt.value}" if opt.value
132
+ end
133
+ t << "\n"
134
+ if opt.doc
135
+ opt.doc.each_line { |l|
136
+ t << (indent * 3) << l
137
+ }
138
+ end
139
+ t << "\n"
140
+ }
141
+ end
142
+ end
143
+
144
+ # May raise Commandline::Error or subclass thereof.
145
+ def process(args)
146
+ optstop = false
147
+ opt_to_eat_arg = nil
148
+ opt_to_eat_arg_arg = nil
149
+ args.each { |arg|
150
+ if optstop
151
+ if opt_to_eat_arg
152
+ raise OptArgMissingError.new(opt_to_eat_arg_arg)
153
+ end
154
+ @arg_action.call(arg)
155
+ next
156
+ end
157
+ if opt_to_eat_arg
158
+ opt_to_eat_arg.action.call(arg)
159
+ opt_to_eat_arg = nil
160
+ opt_to_eat_arg_arg = nil
161
+ next
162
+ end
163
+ case arg
164
+ when "--"
165
+ optstop = true
166
+ when /\A--([^=]*)(=.*)?\z/
167
+ long = $1
168
+ if long.nil? || long.empty?
169
+ raise Error, "expected option name in `#{arg}'"
170
+ end
171
+ opt = @opts.find { |opt| opt.long == long }
172
+ raise UnknownOptError.new(arg) unless opt
173
+ if opt.value
174
+ if $2
175
+ opt.action.call($2[1..-1])
176
+ else
177
+ raise OptArgMissingError.new(arg)
178
+ end
179
+ else
180
+ if $2
181
+ raise ArgNotAllowedError.new(arg)
182
+ else
183
+ opt.action.call
184
+ end
185
+ end
186
+ when /\A-([^-])\z/
187
+ short = $1
188
+ opt = @opts.find { |opt| opt.short == short }
189
+ raise UnknownOptError.new(arg) unless opt
190
+ if opt.value
191
+ opt_to_eat_arg = opt
192
+ opt_to_eat_arg_arg = arg
193
+ else
194
+ opt.action.call
195
+ end
196
+ when /\A-/
197
+ raise UnknownOptError.new(arg)
198
+ else
199
+ if @arg_action
200
+ @arg_action.call(arg)
201
+ else
202
+ ArgNotAllowedError.new(arg)
203
+ end
204
+ end
205
+ }
206
+ if opt_to_eat_arg
207
+ raise OptArgMissingError.new(opt_to_eat_arg_arg)
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ end
@@ -5,15 +5,24 @@ require "uri"
5
5
  require "net/http"
6
6
  require "httpclient"
7
7
  require "highline"
8
+ require "couch-shell/version"
8
9
  require "couch-shell/response"
9
10
  require "couch-shell/ring_buffer"
10
11
  require "couch-shell/eval_context"
11
12
 
12
13
  module CouchShell
13
14
 
15
+ # Starting a shell:
16
+ #
17
+ # require "couch-shell/shell"
18
+ #
19
+ # shell = CouchShell::Shell.new(STDIN, STDOUT, STDERR)
20
+ # # returns at end of STDIN or on a quit command
21
+ # shell.read_execute_loop
22
+ #
14
23
  class Shell
15
24
 
16
- class BreakReplLoop < Exception
25
+ class Quit < Exception
17
26
  end
18
27
 
19
28
  class ShellUserError < Exception
@@ -62,6 +71,9 @@ module CouchShell
62
71
  @responses = RingBuffer.new(10)
63
72
  @eval_context = EvalContext.new(self)
64
73
  @viewtext = nil
74
+ @stdout.puts "couch-shell #{VERSION}"
75
+ @username = nil
76
+ @password = nil
65
77
  end
66
78
 
67
79
  def normalize_server_url(url)
@@ -186,6 +198,9 @@ module CouchShell
186
198
  else
187
199
  raise "unsupported http method: `#{method}'"
188
200
  end).new(fpath)
201
+ if @username && @password
202
+ req.basic_auth @username, @password
203
+ end
189
204
  if body
190
205
  req.body = body
191
206
  if req.content_type.nil? && req.body =~ JSON_DOC_START_RX
@@ -203,12 +218,17 @@ module CouchShell
203
218
  if body.kind_of?(FileToUpload)
204
219
  file_to_upload = body
205
220
  file = File.open(file_to_upload.filename, "rb")
206
- body = [{'Content-Type' => file_to_upload.content_type!,
207
- :content => file}]
221
+ #body = [{'Content-Type' => file_to_upload.content_type!,
222
+ # :content => file}]
223
+ body = {'upload' => file}
208
224
  elsif body && body =~ JSON_DOC_START_RX
209
225
  headers['Content-Type'] = "application/json"
210
226
  end
211
- res = HTTPClient.new.request(method, absolute_url, body, headers)
227
+ hclient = HTTPClient.new
228
+ if @username && @password
229
+ hclient.set_auth lookup_var("server"), @username, @password
230
+ end
231
+ res = hclient.request(method, absolute_url, body, headers)
212
232
  Response.new(res)
213
233
  ensure
214
234
  file.close if file
@@ -266,11 +286,36 @@ module CouchShell
266
286
  end
267
287
  end
268
288
 
269
- def rep
270
- input = read
289
+ # When the user enters something, it is passed to this method for
290
+ # execution. You may call if programmatically to simulate user input.
291
+ #
292
+ # If input is nil, it is interpreted as "end of input", raising a
293
+ # CouchShell::Shell::Quit exception. This exception is also raised by other
294
+ # commands (e.g. "exit" and "quit"). All other exceptions are caught and
295
+ # displayed on stderr.
296
+ def execute(input)
297
+ begin
298
+ execute!(input)
299
+ rescue Quit => e
300
+ raise e
301
+ rescue Interrupt
302
+ @stdout.puts
303
+ errmsg "interrupted"
304
+ rescue UndefinedVariable => e
305
+ errmsg "Variable `" + e.varname + "' is not defined."
306
+ rescue ShellUserError => e
307
+ errmsg e.message
308
+ rescue Exception => e
309
+ errmsg e.message
310
+ errmsg e.backtrace[0..5].join("\n")
311
+ end
312
+ end
313
+
314
+ # Basic execute without error handling. Raises various exceptions.
315
+ def execute!(input)
271
316
  case input
272
317
  when nil
273
- raise BreakReplLoop
318
+ raise Quit
274
319
  when ""
275
320
  # do nothing
276
321
  else
@@ -284,20 +329,13 @@ module CouchShell
284
329
  end
285
330
  end
286
331
 
287
- def repl
332
+ # Start regular shell operation, i.e. reading commands from stdin and
333
+ # executing them. Returns when the user issues a quit command.
334
+ def read_execute_loop
288
335
  loop {
289
- begin
290
- rep
291
- rescue Interrupt
292
- @stdout.puts
293
- errmsg "interrupted"
294
- rescue UndefinedVariable => e
295
- errmsg "Variable `" + e.varname + "' is not defined."
296
- rescue ShellUserError => e
297
- errmsg e.message
298
- end
336
+ execute(read)
299
337
  }
300
- rescue BreakReplLoop
338
+ rescue Quit
301
339
  msg "bye"
302
340
  end
303
341
 
@@ -325,7 +363,7 @@ module CouchShell
325
363
  end
326
364
  elsif c == ')'
327
365
  if expr
328
- res << shell_eval(expr)
366
+ res << shell_eval(expr).to_s
329
367
  expr = nil
330
368
  else
331
369
  res << c
@@ -352,7 +390,8 @@ module CouchShell
352
390
  command_uuids nil
353
391
  if @responses.current(&:ok?)
354
392
  json = @responses.current.json
355
- if json && (uuids = json["uuids"]) && uuids.kind_of?(Array) && uuids.size > 0
393
+ if json && (uuids = json.couch_shell_ruby_value!["uuids"]) &&
394
+ uuids.kind_of?(Array) && uuids.size > 0
356
395
  uuids[0]
357
396
  else
358
397
  raise ShellUserError,
@@ -416,7 +455,9 @@ module CouchShell
416
455
  else
417
456
  body = bodyarg
418
457
  end
419
- request method, interpolate(url), body
458
+ real_url = interpolate(url)
459
+ request method, real_url, body
460
+ real_url
420
461
  end
421
462
 
422
463
  def editor_bin!
@@ -432,6 +473,11 @@ module CouchShell
432
473
  request_command_with_body("PUT", argstr)
433
474
  end
434
475
 
476
+ def command_cput(argstr)
477
+ url = request_command_with_body("PUT", argstr)
478
+ cd url if @responses.current(&:ok?)
479
+ end
480
+
435
481
  def command_post(argstr)
436
482
  request_command_with_body("POST", argstr)
437
483
  end
@@ -449,11 +495,11 @@ module CouchShell
449
495
  end
450
496
 
451
497
  def command_exit(argstr)
452
- raise BreakReplLoop
498
+ raise Quit
453
499
  end
454
500
 
455
501
  def command_quit(argstr)
456
- raise BreakReplLoop
502
+ raise Quit
457
503
  end
458
504
 
459
505
  def command_uuids(argstr)
@@ -680,6 +726,14 @@ module CouchShell
680
726
  request "PUT", "?rev=#{rev}", json.to_s
681
727
  end
682
728
 
729
+ def command_user(argstr)
730
+ prompt_msg("Password:", false)
731
+ @password = @highline.ask(" ") { |q| q.echo = "*" }
732
+ # we save the username only after the password was entered
733
+ # to allow cancellation during password input
734
+ @username = argstr
735
+ end
736
+
683
737
  end
684
738
 
685
739
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CouchShell
4
4
 
5
- VERSION = "0.0.4"
5
+ VERSION = "0.0.5"
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
- - 4
9
- version: 0.0.4
8
+ - 5
9
+ version: 0.0.5
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-20 00:00:00 +01:00
17
+ date: 2011-01-21 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -59,6 +59,7 @@ extra_rdoc_files: []
59
59
  files:
60
60
  - lib/couch-shell/eval_context.rb
61
61
  - lib/couch-shell/response.rb
62
+ - lib/couch-shell/commandline.rb
62
63
  - lib/couch-shell/ring_buffer.rb
63
64
  - lib/couch-shell/json_value.rb
64
65
  - lib/couch-shell/version.rb