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.
- data/bin/couch-shell +1 -1
- data/lib/couch-shell.rb +43 -10
- data/lib/couch-shell/commandline.rb +213 -0
- data/lib/couch-shell/shell.rb +78 -24
- data/lib/couch-shell/version.rb +1 -1
- metadata +4 -3
data/bin/couch-shell
CHANGED
data/lib/couch-shell.rb
CHANGED
@@ -1,21 +1,54 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
require "couch-shell/shell"
|
4
|
-
require "couch-shell/
|
4
|
+
require "couch-shell/commandline"
|
5
5
|
|
6
6
|
module CouchShell
|
7
7
|
|
8
8
|
def self.run(args)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
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
|
data/lib/couch-shell/shell.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
270
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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"]) &&
|
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
|
-
|
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
|
498
|
+
raise Quit
|
453
499
|
end
|
454
500
|
|
455
501
|
def command_quit(argstr)
|
456
|
-
raise
|
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
|
data/lib/couch-shell/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
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-
|
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
|