couch-shell 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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