tarkin 0.9.3.0
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.
- checksums.yaml +7 -0
- data/bin/tarkin +57 -0
- data/lib/cmd.rb +568 -0
- data/lib/tarkin.rb +201 -0
- data/lib/tarkin_commands.rb +105 -0
- data/lib/tarkin_sh.rb +145 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35f0d3f9b104422ff8e9006fdc86edc172a2bced
|
4
|
+
data.tar.gz: 07f422fe6aa640ce741e1db7e5c606066bdbec5b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 43ee41bc484127095e744870924552bbb11b595ff22dcffd15c9c4bf96950764298b2ee6d000e04707400f7bb945b80c1c5e41233b7a438bb765143016f94bcf
|
7
|
+
data.tar.gz: c83d91be2afe5deb122360ac73f0ca543b771a06749f9ab78786ad6b42f3560a9e2c9dbd2d2891a088fcc0a633de4a3c0d297a55c93e4d6b668c8cfd906b1f17
|
data/bin/tarkin
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'tarkin'
|
3
|
+
require 'optparse'
|
4
|
+
require 'highline/import'
|
5
|
+
require 'open-uri'
|
6
|
+
include CommandLineReporter if require 'command_line_reporter'
|
7
|
+
require 'tarkin_commands'
|
8
|
+
require 'tarkin_sh'
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: tarkin [options] [PATH TO PASSWORD*]"
|
13
|
+
opts.separator "Client for Tarkin Team Password Manager: https://github.com/grych/tarkin"
|
14
|
+
opts.separator "Options:"
|
15
|
+
opts.on("-l", "--ls PATH", "Lists the directory") do |p|
|
16
|
+
options[:ls] = p
|
17
|
+
end
|
18
|
+
opts.on("-f", "--find TERM", "Search for items and directories, may use asterisks *") do |p|
|
19
|
+
options[:find] = p
|
20
|
+
end
|
21
|
+
opts.on("-x", "--long", "Long listing (like ls -l)") do |l|
|
22
|
+
options[:long] = l
|
23
|
+
end
|
24
|
+
opts.separator "Examples:"
|
25
|
+
opts.separator "tarkin /db/prod/oracle/scott"
|
26
|
+
opts.separator "tarkin --long --list /db/prod"
|
27
|
+
opts.separator "tarkin --find scott"
|
28
|
+
end.parse!
|
29
|
+
|
30
|
+
# pp options
|
31
|
+
|
32
|
+
paths = ARGV
|
33
|
+
|
34
|
+
client = TarkinClient.new
|
35
|
+
commands = TarkinCommands.new(client)
|
36
|
+
|
37
|
+
if options[:ls]
|
38
|
+
commands.ls(options[:ls], options[:long])
|
39
|
+
elsif options[:find]
|
40
|
+
commands.find(options[:find])
|
41
|
+
elsif paths.empty?
|
42
|
+
TarkinSh.start client, commands
|
43
|
+
else
|
44
|
+
paths.each do |pwd|
|
45
|
+
if pwd[/^-?\d+$/]
|
46
|
+
pwd = pwd.to_i
|
47
|
+
else
|
48
|
+
pwd = URI::encode(pwd)
|
49
|
+
end
|
50
|
+
begin
|
51
|
+
commands.cat(pwd)
|
52
|
+
rescue TarkinClientException => e
|
53
|
+
puts "Not found (#{e.message})"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/lib/cmd.rb
ADDED
@@ -0,0 +1,568 @@
|
|
1
|
+
# From: https://github.com/hamxiaoz/cmd
|
2
|
+
# minor fixed to work with >= 1.9.3 by Tomek 'Grych' Gryszkiewicz <grych@tg.pl>
|
3
|
+
#
|
4
|
+
|
5
|
+
READLINE_SUPPORTED = (require 'readline'; true) rescue false
|
6
|
+
require 'abbrev'
|
7
|
+
|
8
|
+
# A simple framework for writing line-oriented command interpreters, based
|
9
|
+
# heavily on Python's {cmd.py}[http://docs.python.org/lib/module-cmd.html].
|
10
|
+
#
|
11
|
+
# These are often useful for test harnesses, administrative tools, and
|
12
|
+
# prototypes that will later be wrapped in a more sophisticated interface.
|
13
|
+
#
|
14
|
+
# A Cmd instance or subclass instance is a line-oriented interpreter
|
15
|
+
# framework. There is no good reason to instantiate Cmd itself; rather,
|
16
|
+
# it's useful as a superclass of an interpreter class you define yourself
|
17
|
+
# in order to inherit Cmd's methods and encapsulate action methods.
|
18
|
+
class Cmd
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
@@docs = {}
|
22
|
+
@@shortcuts = {}
|
23
|
+
@@handlers = {}
|
24
|
+
@@prompt = '> '
|
25
|
+
@@shortcut_table = {}
|
26
|
+
|
27
|
+
# Set documentation for a command
|
28
|
+
#
|
29
|
+
# doc :help, 'Display this help.'
|
30
|
+
# def do_help
|
31
|
+
# # etc
|
32
|
+
# end
|
33
|
+
def doc(command, docstring = nil)
|
34
|
+
docstring = docstring ? docstring : yield
|
35
|
+
@@docs[command.to_s] = docstring
|
36
|
+
end
|
37
|
+
|
38
|
+
def docs
|
39
|
+
@@docs
|
40
|
+
end
|
41
|
+
module_function :docs
|
42
|
+
|
43
|
+
# Set what to do in the event that the given exception is raised.
|
44
|
+
#
|
45
|
+
# handle StackOverflowError, :handle_stack_overflow
|
46
|
+
#
|
47
|
+
def handle(exception, handler)
|
48
|
+
@@handlers[exception.to_s] = handler
|
49
|
+
end
|
50
|
+
module_function :handle
|
51
|
+
|
52
|
+
# Sets what the prompt is. Accepts a String, a block or a Symbol.
|
53
|
+
#
|
54
|
+
# == Block
|
55
|
+
#
|
56
|
+
# prompt_with { Time.now }
|
57
|
+
#
|
58
|
+
# == Symbol
|
59
|
+
#
|
60
|
+
# prompt_with :set_prompt
|
61
|
+
#
|
62
|
+
# == String
|
63
|
+
#
|
64
|
+
# prompt_with "#{self.class.name}> "
|
65
|
+
#
|
66
|
+
def prompt_with(*p, &block)
|
67
|
+
@@prompt = block_given? ? block : p.first
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the evaluation of expression passed to prompt_with. Result has
|
71
|
+
# +to_s+ called on it as Readline expects a String for its prompt.
|
72
|
+
# XXX This could probably be more robust
|
73
|
+
def prompt
|
74
|
+
case @@prompt
|
75
|
+
when Symbol
|
76
|
+
self.send @@prompt
|
77
|
+
when Proc
|
78
|
+
@@prompt.call
|
79
|
+
else
|
80
|
+
@@prompt
|
81
|
+
end.to_s
|
82
|
+
end
|
83
|
+
module_function :prompt
|
84
|
+
|
85
|
+
# Create a command short cut
|
86
|
+
#
|
87
|
+
# shortcut '?', 'help'
|
88
|
+
# def do_help
|
89
|
+
# # etc
|
90
|
+
# end
|
91
|
+
def shortcut(short, command)
|
92
|
+
(@@shortcuts[command.to_s] ||= []).push short
|
93
|
+
@@shortcut_table[short] = command.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def shortcut_table
|
97
|
+
@@shortcut_table
|
98
|
+
end
|
99
|
+
module_function :shortcut_table
|
100
|
+
|
101
|
+
def shortcuts
|
102
|
+
@@shortcuts
|
103
|
+
end
|
104
|
+
module_function :shortcuts
|
105
|
+
|
106
|
+
def custom_exception_handlers
|
107
|
+
@@handlers
|
108
|
+
end
|
109
|
+
module_function :custom_exception_handlers
|
110
|
+
|
111
|
+
# Defines a method which returns all defined methods which start with the
|
112
|
+
# passed in prefix followed by an underscore. Used to define methods to
|
113
|
+
# collect things such as all defined 'complete' and 'do' methods.
|
114
|
+
def define_collect_method(prefix)
|
115
|
+
method = 'collect_' + prefix
|
116
|
+
unless self.respond_to?(method)
|
117
|
+
define_method(method) do
|
118
|
+
self.methods.grep(/^#{prefix}_/).map {|meth| meth[prefix.size + 1..-1]}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
extend ClassMethods
|
124
|
+
include ClassMethods
|
125
|
+
|
126
|
+
@hide_undocumented_commands = nil
|
127
|
+
class << self
|
128
|
+
# Flag that sets whether undocumented commands are listed in the help
|
129
|
+
attr_accessor :hide_undocumented_commands
|
130
|
+
|
131
|
+
def run(intro = nil)
|
132
|
+
new.cmdloop(intro)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# STDIN stream used
|
137
|
+
attr_writer :stdin
|
138
|
+
|
139
|
+
# STDOUT stream used
|
140
|
+
attr_writer :stdout
|
141
|
+
|
142
|
+
# The current command
|
143
|
+
attr_writer :current_command
|
144
|
+
|
145
|
+
prompt_with :default_prompt
|
146
|
+
|
147
|
+
def initialize
|
148
|
+
@stdin, @stdout = STDIN, STDOUT
|
149
|
+
@stop = false
|
150
|
+
setup
|
151
|
+
end
|
152
|
+
|
153
|
+
# Starts up the command loop
|
154
|
+
def cmdloop(intro = nil)
|
155
|
+
preloop
|
156
|
+
write intro if intro
|
157
|
+
begin
|
158
|
+
set_completion_proc(:complete)
|
159
|
+
begin
|
160
|
+
execute_command
|
161
|
+
# Catch ^C
|
162
|
+
rescue Interrupt
|
163
|
+
user_interrupt
|
164
|
+
# I don't know why ZeroDivisionError isn't caught below...
|
165
|
+
rescue ZeroDivisionError
|
166
|
+
handle_all_remaining_exceptions(ZeroDivisionError)
|
167
|
+
rescue => exception
|
168
|
+
handle_all_remaining_exceptions(exception)
|
169
|
+
end
|
170
|
+
end until @stop
|
171
|
+
postloop
|
172
|
+
end
|
173
|
+
alias :run :cmdloop
|
174
|
+
|
175
|
+
shortcut '?', 'help'
|
176
|
+
doc :help, 'This help message.'
|
177
|
+
def do_help(command = nil)
|
178
|
+
if command
|
179
|
+
command = translate_shortcut(command)
|
180
|
+
docs.include?(command) ? print_help(command) : no_help(command)
|
181
|
+
else
|
182
|
+
documented_commands.each {|cmd| print_help cmd}
|
183
|
+
print_undocumented_commands if undocumented_commands?
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Called when the +command+ has no associated documentation, this could
|
188
|
+
# potentially mean that the command is non existant
|
189
|
+
def no_help(command)
|
190
|
+
write "No help for command '#{command}'"
|
191
|
+
end
|
192
|
+
|
193
|
+
doc :exit, 'Terminate the program.'
|
194
|
+
def do_exit; stoploop end
|
195
|
+
|
196
|
+
# Turns off readline even if it is supported
|
197
|
+
def turn_off_readline
|
198
|
+
@readline_supported = false
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
|
204
|
+
def execute_command
|
205
|
+
unless ARGV.empty?
|
206
|
+
stoploop
|
207
|
+
execute_line(ARGV * ' ')
|
208
|
+
else
|
209
|
+
execute_line(display_prompt(prompt, true))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def handle_all_remaining_exceptions(exception)
|
214
|
+
if exception_is_handled?(exception)
|
215
|
+
run_custom_exception_handling(exception)
|
216
|
+
else
|
217
|
+
handle_exception(exception)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def execute_line(command)
|
222
|
+
postcmd(run_command(precmd(command)))
|
223
|
+
end
|
224
|
+
|
225
|
+
def stoploop
|
226
|
+
@stop = true
|
227
|
+
end
|
228
|
+
|
229
|
+
# Indicates whether readline support is enabled
|
230
|
+
def readline_supported?
|
231
|
+
@readline_supported = READLINE_SUPPORTED if @readline_supported.nil?
|
232
|
+
@readline_supported
|
233
|
+
end
|
234
|
+
|
235
|
+
# Determines if the given exception has a custome handler.
|
236
|
+
def exception_is_handled?(exception)
|
237
|
+
custom_exception_handler(exception)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Runs the customized exception handler for the given exception.
|
241
|
+
def run_custom_exception_handling(exception)
|
242
|
+
case handler = custom_exception_handler(exception)
|
243
|
+
when String
|
244
|
+
write handler
|
245
|
+
when Symbol
|
246
|
+
# grych@tg.pl FIX: added exception as an argument
|
247
|
+
self.send(handler, exception)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Returns the customized handler for the exception
|
252
|
+
def custom_exception_handler(exception)
|
253
|
+
# grych@tg.pl FIX, was: exception.to_s which return exception message in ruby >=(?) 1.9.3
|
254
|
+
custom_exception_handlers[exception.class.to_s]
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
# Called at object creation. This can be treated like 'initialize' for sub
|
259
|
+
# classes.
|
260
|
+
def setup
|
261
|
+
end
|
262
|
+
|
263
|
+
# Exceptions in the cmdloop are caught and passed to +handle_exception+.
|
264
|
+
# Custom exception classes must inherit from StandardError to be
|
265
|
+
# passed to +handle_exception+.
|
266
|
+
def handle_exception(exception)
|
267
|
+
raise exception
|
268
|
+
end
|
269
|
+
|
270
|
+
# Displays the prompt.
|
271
|
+
def display_prompt(prompt, with_history = true)
|
272
|
+
line = if readline_supported?
|
273
|
+
Readline::readline(prompt, with_history)
|
274
|
+
else
|
275
|
+
print prompt
|
276
|
+
@stdin.gets
|
277
|
+
end
|
278
|
+
line.respond_to?(:strip) ? line.strip : line
|
279
|
+
end
|
280
|
+
|
281
|
+
# The current command.
|
282
|
+
def current_command
|
283
|
+
translate_shortcut @current_command
|
284
|
+
end
|
285
|
+
|
286
|
+
# Called when the user hits ctrl-C or ctrl-D. Terminates execution by default.
|
287
|
+
def user_interrupt
|
288
|
+
write 'Terminating' # XXX get rid of this
|
289
|
+
stoploop
|
290
|
+
end
|
291
|
+
|
292
|
+
# XXX Not implementd yet. Called when a do_ method that takes arguments doesn't get any
|
293
|
+
def arguments_missing
|
294
|
+
write 'Invalid arguments'
|
295
|
+
do_help(current_command) if docs.include?(current_command)
|
296
|
+
end
|
297
|
+
|
298
|
+
# A bit of a hack I'm afraid. Since subclasses will be potentially
|
299
|
+
# overriding user_interrupt we want to ensure that it returns true so that
|
300
|
+
# it can be called with 'and return'
|
301
|
+
def interrupt
|
302
|
+
user_interrupt or true
|
303
|
+
end
|
304
|
+
|
305
|
+
# Displays the help for the passed in command.
|
306
|
+
def print_help(cmd)
|
307
|
+
offset = docs.keys.longest_string_length
|
308
|
+
write "#{cmd.ljust(offset)} -- #{docs[cmd]}" +
|
309
|
+
(has_shortcuts?(cmd) ? " #{display_shortcuts(cmd)}" : '')
|
310
|
+
end
|
311
|
+
|
312
|
+
def display_shortcuts(cmd)
|
313
|
+
"(aliases: #{shortcuts[cmd].join(', ')})"
|
314
|
+
end
|
315
|
+
|
316
|
+
# The method name that corresponds to the passed in command.
|
317
|
+
def command(cmd)
|
318
|
+
"do_#{cmd}".intern
|
319
|
+
end
|
320
|
+
|
321
|
+
# The method name that corresponds to the complete command for the pass in
|
322
|
+
# command.
|
323
|
+
def complete_method(cmd)
|
324
|
+
"complete_#{cmd}".intern
|
325
|
+
end
|
326
|
+
|
327
|
+
# Call back executed at the start of the cmdloop.
|
328
|
+
def preloop
|
329
|
+
end
|
330
|
+
|
331
|
+
# Call back executed at the end of the cmdloop.
|
332
|
+
def postloop
|
333
|
+
end
|
334
|
+
|
335
|
+
# Receives line submitted at prompt and passes it along to the command
|
336
|
+
# being called.
|
337
|
+
def precmd(line)
|
338
|
+
line
|
339
|
+
end
|
340
|
+
|
341
|
+
# Receives the returned value of the called command.
|
342
|
+
def postcmd(line)
|
343
|
+
line
|
344
|
+
end
|
345
|
+
|
346
|
+
# Called when an empty line is entered in response to the prompt.
|
347
|
+
def empty_line
|
348
|
+
end
|
349
|
+
|
350
|
+
define_collect_method('do')
|
351
|
+
define_collect_method('complete')
|
352
|
+
|
353
|
+
# The default completor. Looks up all do_* methods.
|
354
|
+
def complete(command)
|
355
|
+
commands = completion_grep(command_list, command)
|
356
|
+
if commands.size == 1
|
357
|
+
cmd = commands.first
|
358
|
+
set_completion_proc(complete_method(cmd)) if collect_complete.include?(cmd)
|
359
|
+
end
|
360
|
+
commands
|
361
|
+
end
|
362
|
+
|
363
|
+
# Lists of commands (i.e. do_* methods minus the 'do_' part).
|
364
|
+
def command_list
|
365
|
+
collect_do - subcommand_list
|
366
|
+
end
|
367
|
+
|
368
|
+
# Definitive list of shortcuts and abbreviations of a command.
|
369
|
+
def command_lookup_table
|
370
|
+
return @command_lookup_table if @command_lookup_table
|
371
|
+
@command_lookup_table = command_abbreviations.merge(shortcut_table)
|
372
|
+
end
|
373
|
+
|
374
|
+
# Returns lookup table of unambiguous identifiers for commands.
|
375
|
+
def command_abbreviations
|
376
|
+
return @command_abbreviations if @command_abbreviations
|
377
|
+
@command_abbreviations = Abbrev::abbrev(command_list)
|
378
|
+
end
|
379
|
+
|
380
|
+
# List of all subcommands.
|
381
|
+
def subcommand_list
|
382
|
+
with_underscore, without_underscore = collect_do.partition {|command| command.include?('_')}
|
383
|
+
with_underscore.find_all {|do_method| without_underscore.include?(do_method[/^[^_]+/])}
|
384
|
+
end
|
385
|
+
|
386
|
+
# Lists all subcommands of a given command.
|
387
|
+
def subcommands(command)
|
388
|
+
completion_grep(subcommand_list, translate_shortcut(command).to_s + '_')
|
389
|
+
end
|
390
|
+
|
391
|
+
# Indicates whether a given command has any subcommands.
|
392
|
+
def has_subcommands?(command)
|
393
|
+
!subcommands(command).empty?
|
394
|
+
end
|
395
|
+
|
396
|
+
# List of commands which are documented.
|
397
|
+
def documented_commands
|
398
|
+
docs.keys.sort
|
399
|
+
end
|
400
|
+
|
401
|
+
# Indicates whether undocummented commands will be listed by the help
|
402
|
+
# command (they are listed by default).
|
403
|
+
def undocumented_commands_hidden?
|
404
|
+
self.class.hide_undocumented_commands
|
405
|
+
end
|
406
|
+
|
407
|
+
def print_undocumented_commands
|
408
|
+
return if undocumented_commands_hidden?
|
409
|
+
# TODO perhaps do some fancy stuff so that if the number of undocumented
|
410
|
+
# commands is greater than 80 cols or some such passed in number it
|
411
|
+
# presents them in a columnar fashion much the way readline does by default
|
412
|
+
write ' '
|
413
|
+
write 'Undocumented commands'
|
414
|
+
write '====================='
|
415
|
+
write undocumented_commands.join(' ' * 4)
|
416
|
+
end
|
417
|
+
|
418
|
+
# Returns list of undocumented commands.
|
419
|
+
def undocumented_commands
|
420
|
+
command_list - documented_commands
|
421
|
+
end
|
422
|
+
|
423
|
+
# Indicates if any commands are undocumeted.
|
424
|
+
def undocumented_commands?
|
425
|
+
!undocumented_commands.empty?
|
426
|
+
end
|
427
|
+
|
428
|
+
# Completor for the help command.
|
429
|
+
def complete_help(command)
|
430
|
+
completion_grep(documented_commands, command)
|
431
|
+
end
|
432
|
+
|
433
|
+
def completion_grep(collection, pattern)
|
434
|
+
collection.grep(/^#{Regexp.escape(pattern)}/)
|
435
|
+
end
|
436
|
+
|
437
|
+
# Writes out a message with newline.
|
438
|
+
def write(*strings)
|
439
|
+
# We want newlines at the end of every line, so don't join with "\n"
|
440
|
+
strings.each do |string|
|
441
|
+
@stdout.write string
|
442
|
+
@stdout.write "\n"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
alias :puts :write
|
446
|
+
|
447
|
+
# Writes out a message without newlines appended.
|
448
|
+
def print(*strings)
|
449
|
+
strings.each {|string| @stdout.write string}
|
450
|
+
end
|
451
|
+
|
452
|
+
# shortcut '!', 'shell'
|
453
|
+
# doc :shell, 'Executes a shell.'
|
454
|
+
# # Executes a shell, perhaps should only be defined by subclasses.
|
455
|
+
# def do_shell(line)
|
456
|
+
# shell = ENV['SHELL']
|
457
|
+
# line ? write(%x(#{line}).strip) : system(shell)
|
458
|
+
# end
|
459
|
+
|
460
|
+
# Takes care of collecting the current command and its arguments if any and
|
461
|
+
# dispatching the appropriate command.
|
462
|
+
def run_command(line)
|
463
|
+
cmd, args = parse_line(line)
|
464
|
+
sanitize_readline_history(line) if line
|
465
|
+
unless cmd then empty_line; return end
|
466
|
+
|
467
|
+
cmd = translate_shortcut(cmd)
|
468
|
+
self.current_command = cmd
|
469
|
+
set_completion_proc(complete_method(cmd)) if collect_complete.include?(complete_method(cmd))
|
470
|
+
cmd_method = command(cmd)
|
471
|
+
if self.respond_to?(cmd_method)
|
472
|
+
# Perhaps just catch exceptions here (related to arity) and call a
|
473
|
+
# method that reports a generic error like 'invalid arguments'
|
474
|
+
self.method(cmd_method).arity.zero? ? self.send(cmd_method) : self.send(cmd_method, tokenize_args(args))
|
475
|
+
else
|
476
|
+
command_missing(current_command, tokenize_args(args))
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
# Receives the line as it was passed from the prompt (barring modification
|
481
|
+
# in precmd) and splits it into a command section and an args section. The
|
482
|
+
# args are by default set to nil if they are boolean false or empty then
|
483
|
+
# joined with spaces. The tokenize method can be used to further alter the
|
484
|
+
# args.
|
485
|
+
def parse_line(line)
|
486
|
+
# line will be nil if ctr-D was pressed
|
487
|
+
user_interrupt and return if line.nil?
|
488
|
+
|
489
|
+
cmd, *args = line.split
|
490
|
+
args = args.empty? ? nil : args * ' '
|
491
|
+
if args and has_subcommands?(cmd)
|
492
|
+
if cmd = find_subcommand_in_args(subcommands(cmd), line.split)
|
493
|
+
# XXX Completion proc should be passed array of subcommands somewhere
|
494
|
+
args = line.split.join('_').match(/^#{cmd}/).post_match.gsub('_', ' ').strip
|
495
|
+
args = nil if args.empty?
|
496
|
+
end
|
497
|
+
end
|
498
|
+
[cmd, args]
|
499
|
+
end
|
500
|
+
|
501
|
+
# Extracts a subcommand if there is one from the command line submitted. I guess this is a hack.
|
502
|
+
def find_subcommand_in_args(subcommands, args)
|
503
|
+
(subcommands & (1..args.size).to_a.map {|num_elems| args.first(num_elems).join('_')}).max
|
504
|
+
end
|
505
|
+
|
506
|
+
# Looks up command shortcuts (e.g. '?' is a shortcut for 'help'). Short
|
507
|
+
# cuts can be added by using the shortcut class method.
|
508
|
+
def translate_shortcut(cmd)
|
509
|
+
command_lookup_table[cmd] || cmd
|
510
|
+
end
|
511
|
+
|
512
|
+
# Indicates if the passed in command has any registerd shortcuts.
|
513
|
+
def has_shortcuts?(cmd)
|
514
|
+
command_shortcuts(cmd)
|
515
|
+
end
|
516
|
+
|
517
|
+
# Returns the set of registered shortcuts for a command, or nil if none.
|
518
|
+
def command_shortcuts(cmd)
|
519
|
+
shortcuts[cmd]
|
520
|
+
end
|
521
|
+
|
522
|
+
# Called on command arguments as they are passed into the command.
|
523
|
+
def tokenize_args(args)
|
524
|
+
args
|
525
|
+
end
|
526
|
+
|
527
|
+
# Cleans up the readline history buffer by performing tasks such as
|
528
|
+
# removing empty lines and piggy-backed duplicates. Only executed if
|
529
|
+
# running with readline support.
|
530
|
+
def sanitize_readline_history(line)
|
531
|
+
return unless readline_supported?
|
532
|
+
# Strip out empty lines
|
533
|
+
Readline::HISTORY.pop if line.match(/^\s*$/)
|
534
|
+
# Remove duplicates
|
535
|
+
Readline::HISTORY.pop if Readline::HISTORY[-2] == line rescue IndexError
|
536
|
+
end
|
537
|
+
|
538
|
+
# Readline completion uses a procedure that takes the current readline
|
539
|
+
# buffer and returns an array of possible matches against the current
|
540
|
+
# buffer. This method sets the current procedure to use. Commands can
|
541
|
+
# specify customized completion procs by defining a method following the
|
542
|
+
# naming convetion complet_{command_name}.
|
543
|
+
def set_completion_proc(cmd)
|
544
|
+
return unless readline_supported?
|
545
|
+
Readline.completion_proc = self.method(cmd)
|
546
|
+
end
|
547
|
+
|
548
|
+
# Called when the line entered at the prompt does not map to any of the
|
549
|
+
# defined commands. By default it reports that there is no such command.
|
550
|
+
def command_missing(command, args)
|
551
|
+
write "No such command '#{command}'"
|
552
|
+
end
|
553
|
+
|
554
|
+
def default_prompt
|
555
|
+
"#{self.class.name}> "
|
556
|
+
end
|
557
|
+
|
558
|
+
end
|
559
|
+
|
560
|
+
module Enumerable #:nodoc:
|
561
|
+
def longest_string_length
|
562
|
+
inject(0) {|longest, item| longest >= item.size ? longest : item.size}
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
if __FILE__ == $0
|
567
|
+
Cmd.run
|
568
|
+
end
|
data/lib/tarkin.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'arest'
|
5
|
+
|
6
|
+
class TarkinClientException < StandardError; end
|
7
|
+
|
8
|
+
class TarkinClient
|
9
|
+
SETTINGS_FILES = ["#{Dir.home}/.tarkin", ".tarkin", "/etc/tarkin"]
|
10
|
+
API = "_api/v1"
|
11
|
+
|
12
|
+
attr_accessor :settings
|
13
|
+
attr_reader :api_client
|
14
|
+
|
15
|
+
# Constructor. Needs to know the Tarkin Server parameters.
|
16
|
+
# They can be passed with three differen ways::
|
17
|
+
#
|
18
|
+
# * the options hash. Notice that in this case the token will not be stored in the ~/.tarkin file
|
19
|
+
# >> tc = TarkinClient.new email: 'user@example.com', password: 'password0', tarkin_url: 'http://tarkin.tg.pl'
|
20
|
+
# # TarkinClient <server: http://tarkin.tg.pl, authorized: true>
|
21
|
+
# >> tc = TarkinClient.new tarkin_url: 'http://tarkin.tg.pl', token: 'Ahoishwqbn32ldhw......'
|
22
|
+
# # TarkinClient <server: http://tarkin.tg.pl, authorized: true>
|
23
|
+
#
|
24
|
+
# * by reading the stored parameters in settings file (like ~/.tarkin)
|
25
|
+
# >> tc = TarkinClient.new
|
26
|
+
# # TarkinClient <server: http://localhost:3000, authorized: true>
|
27
|
+
#
|
28
|
+
# * by asking user via command line (when file not found)
|
29
|
+
# >> tc = TarkinClient.new
|
30
|
+
# # Your Tarkin server URL: |http://tarkin.tg.pl| http://localhost:3000
|
31
|
+
# # Your Tarkin account email: |user@example.com| grychu@gmail.com
|
32
|
+
# # Password for grychu@gmail.com: ********
|
33
|
+
# # TarkinClient <server: http://localhost:3000, authorized: true>
|
34
|
+
def initialize(**options)
|
35
|
+
@authorized = false
|
36
|
+
@settings = {}
|
37
|
+
if options[:email] && options[:password] && options[:tarkin_url]
|
38
|
+
@settings = options.select { |k,v| [:email, :tarkin_url].include? k }
|
39
|
+
@settings[:token] = get_token(@settings[:email], options[:password])
|
40
|
+
elsif options[:token] && options[:tarkin_url]
|
41
|
+
@settings[:tarkin_url] = options[:tarkin_url]
|
42
|
+
@settings[:token] = options[:token]
|
43
|
+
else
|
44
|
+
get_settings
|
45
|
+
save_settings
|
46
|
+
end
|
47
|
+
@api_client = ARest.new(api_url, token: @settings[:token])
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns Hash containing :id, :username and :password
|
51
|
+
# Gets Item.id or full path to the Item as a parameter
|
52
|
+
#
|
53
|
+
# >> tc.password(107)
|
54
|
+
# # {
|
55
|
+
# # "id" => 110,
|
56
|
+
# # "username" => "sysdba",
|
57
|
+
# # "password" => "secret_top"
|
58
|
+
# # }
|
59
|
+
#
|
60
|
+
# >> tc.password('/db/oracle/C84PCPY/sysdba')
|
61
|
+
# # {
|
62
|
+
# # "id" => 110,
|
63
|
+
# # "username" => "sysdba",
|
64
|
+
# # "password" => "secret_top"
|
65
|
+
# # }
|
66
|
+
def password(path_or_id)
|
67
|
+
case path_or_id
|
68
|
+
when String
|
69
|
+
# full path given
|
70
|
+
u = "#{path_or_id}.json"
|
71
|
+
when Fixnum
|
72
|
+
# Item ID given
|
73
|
+
u = "_password/#{path_or_id}.json"
|
74
|
+
end
|
75
|
+
response = @api_client.get(u)
|
76
|
+
if response.ok?
|
77
|
+
response.deserialize
|
78
|
+
else
|
79
|
+
raise TarkinClientException, "Can't get password, server returns #{response.code}: #{response.message}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the content of given directory
|
84
|
+
# Output is a hash containing:
|
85
|
+
# * directories => [ {:name, :id, :created_at, :updated_at, :description} ]
|
86
|
+
# * items => [ {:username, :id, :created_at, :updated_at, :description} ]
|
87
|
+
#
|
88
|
+
# > tc.ls '/db/prod/oracle'
|
89
|
+
# #> {:directories=>
|
90
|
+
# [{:name=>"C84PROD", :id=>14, :created_at=>"2015-06-07T10:36:56.463Z", :updated_at=>"2015-06-07T10:36:56.463Z", :description=>"Production"}],
|
91
|
+
# :items=>
|
92
|
+
# [{:id=>6,
|
93
|
+
# :username=>"scott",
|
94
|
+
# :created_at=>"2015-06-07T10:38:27.981Z",
|
95
|
+
# :updated_at=>"2015-06-07T10:38:27.981Z",
|
96
|
+
# :description=>"The same user in all production databases"}]}
|
97
|
+
def ls(path = '/')
|
98
|
+
u = path.strip.chomp.sub(/(\/)+$/,'') # remove trailing slashes
|
99
|
+
u = if u == '' then '_dir.json' else "_dir/#{u}.json" end
|
100
|
+
response = @api_client.get(u)
|
101
|
+
if response.ok?
|
102
|
+
response.deserialize
|
103
|
+
else
|
104
|
+
if response.code == "404"
|
105
|
+
raise TarkinClientException, "No such file or directory"
|
106
|
+
else
|
107
|
+
raise TarkinClientException, "Can't list directory, server returns #{response.code}: #{response.message}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Search for a username or directory
|
113
|
+
# Input: a String to search, may contain wildcard (*)
|
114
|
+
# Output: array of hashes: [ { :label, :redirect_to }]
|
115
|
+
#
|
116
|
+
# > tc.find 'sys'
|
117
|
+
# #> [{:label=>"/db/prod/oracle/C84PROD/sys", :redirect_to=>"/db/prod/oracle/C84PROD#4"},
|
118
|
+
# {:label=>"/db/prod/oracle/C84PROD/sysadm", :redirect_to=>"/db/prod/oracle/C84PROD#5"}]
|
119
|
+
def find(term)
|
120
|
+
response = @api_client.get("_find.json", form_data: {term: term})
|
121
|
+
if response.ok?
|
122
|
+
response.deserialize
|
123
|
+
else
|
124
|
+
raise TarkinClientException, "Can't search for '#{term}', server returns #{response.code}: #{response.message}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns string with valid token
|
129
|
+
# > tc.token
|
130
|
+
# #> "zvaY5...sds="
|
131
|
+
def token
|
132
|
+
@settings[:token]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns true, if there is a connectivity to the server
|
136
|
+
# and you are authorized
|
137
|
+
def authorized?
|
138
|
+
@authorized || check_connectivity
|
139
|
+
end
|
140
|
+
|
141
|
+
def inspect
|
142
|
+
"TarkinClient <server: #{@settings[:tarkin_url]}, authorized: #{authorized?}>"
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
def api_url
|
147
|
+
"#{@settings[:tarkin_url]}/#{API}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_settings
|
151
|
+
settings_file = SETTINGS_FILES.find {|file| File.exists? file}
|
152
|
+
if settings_file
|
153
|
+
@settings = YAML::load_file settings_file
|
154
|
+
else
|
155
|
+
new_settings
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def new_settings
|
160
|
+
@settings = Hash.new
|
161
|
+
until @settings[:token]
|
162
|
+
@settings[:tarkin_url] = ask("Your <%= color('Tarkin', BOLD) %> server URL: ") do |q|
|
163
|
+
q.default = @settings[:tarkin_url] || "http://tarkin.tg.pl"
|
164
|
+
q.validate = /^(http|https):\/\/.*/ix
|
165
|
+
end
|
166
|
+
@settings[:email] = ask("Your <%= color('Tarkin', BOLD) %> account email: ") do |q|
|
167
|
+
q.default = @settings[:email] || "user@example.com"
|
168
|
+
q.validate = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
|
169
|
+
end
|
170
|
+
password = ask("Password for #{@settings[:email]}: ") { |q| q.echo = "*" }
|
171
|
+
@settings[:token] = get_token(@settings[:email], password)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def save_settings
|
176
|
+
File.open(SETTINGS_FILES.first, 'w') { |f| f.puts @settings.to_yaml }
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_token(email, password)
|
180
|
+
begin
|
181
|
+
client = ARest.new(api_url, username: email, password: password)
|
182
|
+
response = client.get('_authorize.json')
|
183
|
+
rescue SocketError
|
184
|
+
say "<%= color('Cannot connect to server.', BOLD) %> Please retry."
|
185
|
+
return nil
|
186
|
+
end
|
187
|
+
unless response.ok?
|
188
|
+
say "<%= color('#{response.message}', BOLD) %>. Please retry."
|
189
|
+
nil
|
190
|
+
else
|
191
|
+
@authorized = true
|
192
|
+
response.deserialize[:token]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def check_connectivity
|
197
|
+
# @authorized = api_uri['_ping'].get.ok? unless @authorized
|
198
|
+
# @authorized = "#{api_url}/_ping".to_uri.get("", "Authorization" => "Token token=#{@settings[:token]}").ok? unless @authorized
|
199
|
+
@api_client.get("_ping").ok? unless @authorized
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class String
|
2
|
+
# taken from rails
|
3
|
+
def truncate(truncate_at, options = {})
|
4
|
+
s = self.gsub(/\r\n/, ' ')
|
5
|
+
return s unless length > truncate_at
|
6
|
+
|
7
|
+
omission = options[:omission] || '...'
|
8
|
+
length_with_room_for_omission = truncate_at - omission.length
|
9
|
+
stop = if options[:separator]
|
10
|
+
rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
|
11
|
+
else
|
12
|
+
length_with_room_for_omission
|
13
|
+
end
|
14
|
+
|
15
|
+
"#{s[0, stop]}#{omission}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TarkinCommands
|
20
|
+
def initialize(client)
|
21
|
+
@client = client
|
22
|
+
end
|
23
|
+
|
24
|
+
def cat(pwd)
|
25
|
+
puts @client.password(pwd)[:password]
|
26
|
+
end
|
27
|
+
|
28
|
+
def ls(path, long)
|
29
|
+
list = @client.ls(URI::encode(path))
|
30
|
+
if long
|
31
|
+
all = (list[:directories].collect{|dir| ["#{dir[:name]}/", 'blue', dir[:created_at], dir[:updated_at], dir[:description]]} +
|
32
|
+
list[:items].collect{|item| [item[:username], 'white', item[:created_at], item[:updated_at], item[:description]]}).sort
|
33
|
+
unless all.empty?
|
34
|
+
cols = 3
|
35
|
+
len = max_len(all)
|
36
|
+
table border: false do
|
37
|
+
all.each do |thing|
|
38
|
+
row do
|
39
|
+
column thing[2].to_time.strftime('%Y-%m-%d %T'), width: 22
|
40
|
+
column thing[3].to_time.strftime('%Y-%m-%d %T'), width: 22
|
41
|
+
column thing[0], width: len+2, color: thing[1]
|
42
|
+
chars_to_end = HighLine::SystemExtensions.terminal_size.first - 22*2 - (len+6)
|
43
|
+
column thing[4].truncate(chars_to_end), width: chars_to_end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else
|
49
|
+
# all contains directories and users, a list of list - item and display color: [[dir1, 'blue'], [user1, 'white']]
|
50
|
+
# reversed because we will be poping the table
|
51
|
+
all = (list[:directories].collect{|dir| ["#{dir[:name]}/", 'blue']} + list[:items].collect{|item| [item[:username], 'white']}).sort.reverse
|
52
|
+
unless all.empty?
|
53
|
+
cols = columns(all)
|
54
|
+
rows = all.count / cols + ( all.count % cols == 0 ? 0 : 1 )
|
55
|
+
len = max_len(all)
|
56
|
+
|
57
|
+
table border: false do
|
58
|
+
rows.times do
|
59
|
+
row do
|
60
|
+
cols.times do
|
61
|
+
item = all.pop
|
62
|
+
column(item && item.first , width: len, color: item && item.last)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find(term)
|
72
|
+
list = @client.find(term).sort_by {|x| x[:label]}
|
73
|
+
list.each do |thing|
|
74
|
+
unless thing[:redirect_to].include?('#')
|
75
|
+
puts thing[:label].blue
|
76
|
+
else
|
77
|
+
puts thing[:label].white
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns items only in given directory
|
83
|
+
def items(dir)
|
84
|
+
@client.ls(URI::encode(dir))[:items]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns only dirs
|
88
|
+
def dirs(dir)
|
89
|
+
@client.ls(URI::encode(dir))[:directories]
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def max_len(array_or_arrays)
|
94
|
+
if array_or_arrays.empty?
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
array_or_arrays.max_by{|x| x.first.length}.first.length
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def columns(array_or_arrays)
|
102
|
+
screen_x = HighLine::SystemExtensions.terminal_size.first
|
103
|
+
(screen_x / (max_len(array_or_arrays) + 1.0)).floor
|
104
|
+
end
|
105
|
+
end
|
data/lib/tarkin_sh.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'cmd'
|
2
|
+
|
3
|
+
class TarkinSh < Cmd
|
4
|
+
SETTINGS_FILE = "#{Dir.home}/.tarkin_sh"
|
5
|
+
HIST_LINES = 100 # how many history lines to save
|
6
|
+
|
7
|
+
prompt_with :prompt_command
|
8
|
+
|
9
|
+
handle TarkinClientException, :handle_client_exception
|
10
|
+
|
11
|
+
doc :ls, 'List contants of directory
|
12
|
+
-l -- verbose (long) view'
|
13
|
+
def do_ls(args)
|
14
|
+
if args[:args].empty?
|
15
|
+
commands.ls @cd, args[:options].include?('l')
|
16
|
+
else
|
17
|
+
args[:args].each do |dir|
|
18
|
+
commands.ls full_dir(dir), args[:options].include?('l')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
doc :pwd, 'Print working directory'
|
24
|
+
def do_pwd
|
25
|
+
write @cd
|
26
|
+
end
|
27
|
+
|
28
|
+
doc :cd, 'Change directory'
|
29
|
+
def do_cd(args)
|
30
|
+
if args[:args].empty?
|
31
|
+
@cd = '/'
|
32
|
+
else
|
33
|
+
# TODO: warn if change to non-existing directory
|
34
|
+
dir = args[:args].first
|
35
|
+
@cd = full_dir dir
|
36
|
+
end
|
37
|
+
end
|
38
|
+
def complete_cd(args)
|
39
|
+
commands.dirs(full_dir(@cd)).collect {|x| x[:name]}.select {|x| x.start_with? args}
|
40
|
+
end
|
41
|
+
|
42
|
+
shortcut 'less', :cat
|
43
|
+
shortcut 'more', :cat
|
44
|
+
shortcut 'get', :cat
|
45
|
+
doc :cat, 'Shows password'
|
46
|
+
def do_cat(args)
|
47
|
+
args[:args].each do |item|
|
48
|
+
commands.cat(full_dir(item))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def complete_cat(args)
|
52
|
+
commands.items(full_dir(@cd)).collect {|x| x[:username]}.select {|x| x.start_with? args}
|
53
|
+
end
|
54
|
+
|
55
|
+
shortcut 'search', :find
|
56
|
+
doc :find, 'Find for item or directory'
|
57
|
+
def do_find(args)
|
58
|
+
if args[:args].empty?
|
59
|
+
commands.ls @cd, false
|
60
|
+
else
|
61
|
+
commands.find(args[:args])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def do_help(command = nil)
|
66
|
+
command = command[:args].first
|
67
|
+
if command
|
68
|
+
command = translate_shortcut(command)
|
69
|
+
docs.include?(command) ? print_help(command) : no_help(command)
|
70
|
+
else
|
71
|
+
documented_commands.each {|cmd| print_help cmd}
|
72
|
+
print_undocumented_commands if undocumented_commands?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
shortcut '!', 'shell'
|
77
|
+
doc :shell, 'Executes a shell.'
|
78
|
+
# Executes a shell, perhaps should only be defined by subclasses.
|
79
|
+
def do_shell(line)
|
80
|
+
line = line[:original]
|
81
|
+
shell = ENV['SHELL']
|
82
|
+
write shell
|
83
|
+
write "**#{line}**"
|
84
|
+
line.empty? ? system(shell) : write(%x(#{line}).strip)
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
def self.start(client, commands)
|
89
|
+
@@client = client
|
90
|
+
@@commands = commands
|
91
|
+
run
|
92
|
+
end
|
93
|
+
|
94
|
+
def setup
|
95
|
+
if File.exists? SETTINGS_FILE
|
96
|
+
settings = YAML::load_file SETTINGS_FILE
|
97
|
+
@cd = settings[:cd]
|
98
|
+
settings[:history].each { |h| Readline::HISTORY.push h }
|
99
|
+
else
|
100
|
+
@cd = '/'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def prompt_command
|
105
|
+
"#{client.settings[:tarkin_url]}#{@cd}> "
|
106
|
+
end
|
107
|
+
|
108
|
+
def command_missing(command, args)
|
109
|
+
write "tarkin: command not found: #{command}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_client_exception(exception)
|
113
|
+
write "tarkin: #{exception.message}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def postloop
|
117
|
+
settings = { cd: @cd, history: Readline::HISTORY.to_a.last(HIST_LINES) }
|
118
|
+
File.open(SETTINGS_FILE, 'w') { |f| f.puts settings.to_yaml }
|
119
|
+
end
|
120
|
+
|
121
|
+
def tokenize_args(args)
|
122
|
+
a = if args then args.split else [] end
|
123
|
+
{ args: a.select {|x| !x.start_with? '-'},
|
124
|
+
options: a.select {|x| x.start_with? '-'}.map {|x| x.sub(/^-/, '')}.join.split('').uniq,
|
125
|
+
original: args}
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def commands
|
130
|
+
@@commands
|
131
|
+
end
|
132
|
+
|
133
|
+
def client
|
134
|
+
@@client
|
135
|
+
end
|
136
|
+
|
137
|
+
def full_dir(dir)
|
138
|
+
if dir.start_with? '/'
|
139
|
+
File.absolute_path(dir)
|
140
|
+
else
|
141
|
+
File.absolute_path(dir, @cd)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tarkin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomek Gryszkiewicz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: highline
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.7.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.7'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.7.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: arest
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.9'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.9.1.1
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.9'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.9.1.1
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: command_line_reporter
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '3.3'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 3.3.5
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.3'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 3.3.5
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: rspec
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '3.2'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.2.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.2'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 3.2.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: bundler
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.8'
|
100
|
+
type: :development
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - "~>"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '1.8'
|
107
|
+
description: Tarkin Team Password Manager client, command line and shell
|
108
|
+
email: grych@tg.pl
|
109
|
+
executables:
|
110
|
+
- tarkin
|
111
|
+
extensions: []
|
112
|
+
extra_rdoc_files: []
|
113
|
+
files:
|
114
|
+
- bin/tarkin
|
115
|
+
- lib/cmd.rb
|
116
|
+
- lib/tarkin.rb
|
117
|
+
- lib/tarkin_commands.rb
|
118
|
+
- lib/tarkin_sh.rb
|
119
|
+
homepage: https://github.com/grych/tarkin-client
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
metadata: {}
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 2.4.5
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: Tarkin client
|
143
|
+
test_files: []
|