interactive_s3 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/Rakefile +6 -0
- data/bin/is3 +5 -0
- data/interactive_s3.gemspec +24 -0
- data/lib/interactive_s3.rb +11 -0
- data/lib/interactive_s3/cli.rb +49 -0
- data/lib/interactive_s3/command_builder.rb +20 -0
- data/lib/interactive_s3/commands/base.rb +24 -0
- data/lib/interactive_s3/commands/internal_command.rb +62 -0
- data/lib/interactive_s3/commands/s3_command.rb +97 -0
- data/lib/interactive_s3/errors.rb +3 -0
- data/lib/interactive_s3/history.rb +32 -0
- data/lib/interactive_s3/interpreter.rb +28 -0
- data/lib/interactive_s3/s3.rb +34 -0
- data/lib/interactive_s3/s3_path.rb +40 -0
- data/lib/interactive_s3/version.rb +3 -0
- data/spec/interactive_s3/command_builder_spec.rb +25 -0
- data/spec/interactive_s3/commands/base_spec.rb +31 -0
- data/spec/interactive_s3/commands/internal_command_spec.rb +133 -0
- data/spec/interactive_s3/commands/s3_command_spec.rb +57 -0
- data/spec/interactive_s3/history_spec.rb +52 -0
- data/spec/interactive_s3/interpreter_spec.rb +23 -0
- data/spec/interactive_s3/s3_path_spec.rb +39 -0
- data/spec/interactive_s3/s3_spec.rb +117 -0
- data/spec/spec_helper.rb +2 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 22a4ce5049308ee26440e48c45a0471bcbb6c55b
|
4
|
+
data.tar.gz: 19e81f547625f94a1af33cb8565d267f1be378cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2b434692269b5a03a1841494263344baf717b9d2e9ec4c063705aa89bd0f3cc3228bd9ce5a7a6f160fbef2c3c64861b81eeea4f8a3a4cd47df819484bf545c0
|
7
|
+
data.tar.gz: 12e44e37783b6fa98f7c169dad16548226c122d583ebe729f8c92f2e1402bd5b98fab9075d4fecdcc994b6e96c9b82950147e3b7a220fce5980d8b88aaedc5cd
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 yamayo
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# InteractiveS3 [](https://travis-ci.org/yamayo/interactive_s3)
|
2
|
+
InteractiveS3 is an interactive shell that can be easily extended with commands using a `aws s3` in the [AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/index.html).
|
3
|
+
|
4
|
+
## Requirements
|
5
|
+
* Ruby (=> 2.0.0)
|
6
|
+
* AWS CLI (~> 1.5.1)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
```sh
|
10
|
+
$ gem install interactive_s3
|
11
|
+
```
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Start
|
16
|
+
InteractiveS3 is run from the command line.
|
17
|
+
|
18
|
+
```sh
|
19
|
+
$ is3
|
20
|
+
```
|
21
|
+
|
22
|
+
### Available commands
|
23
|
+
|
24
|
+
- **cd** - Change the current path.
|
25
|
+
- **pwd** - Show the current path.
|
26
|
+
- **lls** - Show local file list.
|
27
|
+
- **exit** - Quit InteractiveS3.
|
28
|
+
|
29
|
+
And `aws s3` commands.
|
30
|
+
|
31
|
+
### Examples
|
32
|
+
|
33
|
+
```sh
|
34
|
+
s3> ls
|
35
|
+
2014-08-07 00:22:58 my-bucket
|
36
|
+
s3> cd my-bucket
|
37
|
+
s3://my-bucket> ls
|
38
|
+
2014-08-11 00:23:56 4 .
|
39
|
+
2014-08-20 01:31:34 5 foo
|
40
|
+
s3://my-bucket> lls
|
41
|
+
bar.txt
|
42
|
+
```
|
43
|
+
|
44
|
+
#### `:` - local file prefix
|
45
|
+
|
46
|
+
```sh
|
47
|
+
s3://my-bucket> cp :bar.txt .
|
48
|
+
upload: ./bar.txt to s3://mybucket/bar.txt
|
49
|
+
s3://my-bucket> ls
|
50
|
+
2014-08-11 00:23:56 4 .
|
51
|
+
2014-08-20 01:31:34 5 foo
|
52
|
+
2014-10-19 22:37:43 31 bar.txt
|
53
|
+
```
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
1. Fork it ( http://github.com/<my-github-username>/interactive_s3/fork )
|
58
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
61
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/is3
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'interactive_s3/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "interactive_s3"
|
8
|
+
spec.version = InteractiveS3::VERSION
|
9
|
+
spec.executables = ["is3"]
|
10
|
+
spec.authors = ["yamayo"]
|
11
|
+
spec.email = ["noorthaven@gmail.com"]
|
12
|
+
spec.summary = %q{An interactive shell for AWS CLI (aws s3)}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'interactive_s3/cli'
|
2
|
+
require 'interactive_s3/command_builder'
|
3
|
+
require 'interactive_s3/commands/base'
|
4
|
+
require 'interactive_s3/commands/internal_command'
|
5
|
+
require 'interactive_s3/commands/s3_command'
|
6
|
+
require 'interactive_s3/errors'
|
7
|
+
require 'interactive_s3/history'
|
8
|
+
require 'interactive_s3/interpreter'
|
9
|
+
require 'interactive_s3/s3'
|
10
|
+
require 'interactive_s3/s3_path'
|
11
|
+
require 'interactive_s3/version'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module InteractiveS3
|
4
|
+
class CLI
|
5
|
+
def start
|
6
|
+
history.load
|
7
|
+
show_version
|
8
|
+
run
|
9
|
+
ensure
|
10
|
+
history.save
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def run
|
16
|
+
while line = readline
|
17
|
+
next if line.empty?
|
18
|
+
interpreter.execute(line)
|
19
|
+
end
|
20
|
+
puts "\n"
|
21
|
+
rescue Interrupt
|
22
|
+
puts "\n"
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
|
26
|
+
def readline
|
27
|
+
if line = Readline.readline(prompt, true)
|
28
|
+
Readline::HISTORY.pop if /^\s*$/ =~ line.strip
|
29
|
+
end
|
30
|
+
line
|
31
|
+
end
|
32
|
+
|
33
|
+
def show_version
|
34
|
+
puts "InteractiveS3 #{InteractiveS3::VERSION}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def prompt
|
38
|
+
interpreter.root? ? 's3> ' : "#{interpreter.current_path}> "
|
39
|
+
end
|
40
|
+
|
41
|
+
def interpreter
|
42
|
+
@interpreter ||= Interpreter.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def history
|
46
|
+
@history ||= History.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module InteractiveS3
|
2
|
+
class CommandBuilder
|
3
|
+
def initialize(context, params)
|
4
|
+
@context = context
|
5
|
+
@name, *@arguments = params.split(/\s/)
|
6
|
+
end
|
7
|
+
|
8
|
+
def build
|
9
|
+
command_class.new(context, name, arguments)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :context, :name, :arguments
|
15
|
+
|
16
|
+
def command_class
|
17
|
+
Commands::InternalCommand.fetch(name.to_sym, Commands::S3Command)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module InteractiveS3::Commands
|
2
|
+
class Base
|
3
|
+
attr_accessor :state, :arguments
|
4
|
+
|
5
|
+
attr_reader :s3, :name
|
6
|
+
|
7
|
+
def initialize(context, name, arguments = [])
|
8
|
+
@s3 = context.s3
|
9
|
+
@state = context.state
|
10
|
+
@name = name
|
11
|
+
@arguments = arguments
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def argument_size
|
21
|
+
arguments.size
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module InteractiveS3::Commands
|
2
|
+
module InternalCommand
|
3
|
+
def self.fetch(name, default = nil)
|
4
|
+
COMMAND_CLASSES.fetch(name, default)
|
5
|
+
end
|
6
|
+
|
7
|
+
class Chdir < Base
|
8
|
+
def execute
|
9
|
+
state[:previous_stack] ||= []
|
10
|
+
|
11
|
+
if target.nil?
|
12
|
+
state[:previous_stack] = s3.stack
|
13
|
+
s3.reset
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
if target.strip == '-'
|
18
|
+
s3.stack, state[:previous_stack] = state[:previous_stack], s3.stack
|
19
|
+
else
|
20
|
+
target.sub!('s3://', '/')
|
21
|
+
state[:previous_stack] = s3.stack
|
22
|
+
s3.stack = InteractiveS3::S3Path.new(target, s3.stack).resolve
|
23
|
+
end
|
24
|
+
ensure
|
25
|
+
unless s3.exist?
|
26
|
+
s3.stack = state[:previous_stack]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def target
|
33
|
+
@target ||= arguments.first
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class LocalList < Base
|
38
|
+
def execute
|
39
|
+
puts Dir.entries('.').delete_if {|file| file =~ /^(.|..)$/ }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Pwd < Base
|
44
|
+
def execute
|
45
|
+
puts s3.root? ? '/' : s3.current_path
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Exit < Base
|
50
|
+
def execute
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
COMMAND_CLASSES = {
|
56
|
+
cd: Chdir,
|
57
|
+
lls: LocalList,
|
58
|
+
pwd: Pwd,
|
59
|
+
exit: Exit,
|
60
|
+
}.freeze
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module InteractiveS3::Commands
|
2
|
+
class S3Command < Base
|
3
|
+
S3_PATH_PREFIX = /^s3:\/\//
|
4
|
+
LOCAL_PATH_PREFIX = /^:/
|
5
|
+
OPTION_PREFIX = /^--/
|
6
|
+
HELP_SUB_COMMAND = /^help$/
|
7
|
+
TARGET_SUB_COMMANDS = %w(mv rb rm).freeze
|
8
|
+
|
9
|
+
def initialize(context, name, arguments = [])
|
10
|
+
super
|
11
|
+
parse_arguments
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
pid = Process.spawn(
|
16
|
+
*command_with_arguments,
|
17
|
+
out: $stdout,
|
18
|
+
err: $stderr
|
19
|
+
)
|
20
|
+
wait_for_process(pid)
|
21
|
+
$? && $?.success?
|
22
|
+
rescue SystemCallError => e
|
23
|
+
puts e.message
|
24
|
+
false
|
25
|
+
ensure
|
26
|
+
if target_sub_command_and_s3_not_exist?
|
27
|
+
s3.reset
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
alias sub_command name
|
34
|
+
|
35
|
+
def parse_arguments
|
36
|
+
arguments[target] = arguments[target].map {|argument|
|
37
|
+
case argument
|
38
|
+
when S3_PATH_PREFIX, HELP_SUB_COMMAND, OPTION_PREFIX
|
39
|
+
argument
|
40
|
+
when LOCAL_PATH_PREFIX
|
41
|
+
argument.sub(LOCAL_PATH_PREFIX, '')
|
42
|
+
else
|
43
|
+
stack = InteractiveS3::S3Path.new(argument, s3.stack).resolve
|
44
|
+
"s3://#{stack.join('/')}"
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def target
|
50
|
+
@target ||= if option_first?
|
51
|
+
argument_size-2..argument_size-1
|
52
|
+
else
|
53
|
+
0..1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def option_first?
|
58
|
+
!!(arguments.first =~ OPTION_PREFIX)
|
59
|
+
end
|
60
|
+
|
61
|
+
def command_with_arguments
|
62
|
+
arguments << "#{s3.current_path}/" if list_command_with_no_s3_path?
|
63
|
+
['aws', 's3', sub_command, arguments].flatten
|
64
|
+
end
|
65
|
+
|
66
|
+
def list_command_with_no_s3_path?
|
67
|
+
list_command? && !help_sub_command? && !include_s3_path?
|
68
|
+
end
|
69
|
+
|
70
|
+
def list_command?
|
71
|
+
sub_command == 'ls'
|
72
|
+
end
|
73
|
+
|
74
|
+
def help_sub_command?
|
75
|
+
arguments.include?('help')
|
76
|
+
end
|
77
|
+
|
78
|
+
def include_s3_path?
|
79
|
+
arguments.any? {|argument| argument.match(S3_PATH_PREFIX) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def target_sub_command?
|
83
|
+
TARGET_SUB_COMMANDS.include?(sub_command)
|
84
|
+
end
|
85
|
+
|
86
|
+
def target_sub_command_and_s3_not_exist?
|
87
|
+
target_sub_command? && !s3.exist?
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_for_process(pid)
|
91
|
+
Process.wait(pid)
|
92
|
+
rescue Interrupt
|
93
|
+
Process.kill('INT', pid)
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module InteractiveS3
|
2
|
+
class History
|
3
|
+
HISTORY_FILE = "#{Dir.home}/.is3_history"
|
4
|
+
HISTORY_SIZE = 500
|
5
|
+
|
6
|
+
def load
|
7
|
+
if file_exist?
|
8
|
+
File.read(file_path).each_line do |line|
|
9
|
+
Readline::HISTORY << line.chomp
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def save
|
15
|
+
File.write(file_path, Readline::HISTORY.to_a.last(history_size).join("\n"))
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def file_exist?
|
21
|
+
File.exist?(file_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def file_path
|
25
|
+
HISTORY_FILE
|
26
|
+
end
|
27
|
+
|
28
|
+
def history_size
|
29
|
+
HISTORY_SIZE.to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module InteractiveS3
|
4
|
+
class Interpreter
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :s3, :state
|
8
|
+
|
9
|
+
def_delegators :s3, :current_path, :root?
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@s3 = S3.new
|
13
|
+
@state = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input)
|
17
|
+
build_command(input).execute
|
18
|
+
rescue CommandError => e
|
19
|
+
puts e.class, e.message, e.backtrace.join("\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_command(input)
|
25
|
+
CommandBuilder.new(self, input).build
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module InteractiveS3
|
4
|
+
class S3
|
5
|
+
attr_accessor :stack
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@stack = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_path
|
12
|
+
empty? ? '' : "s3://#{stack.join('/')}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def empty?
|
16
|
+
stack.empty?
|
17
|
+
end
|
18
|
+
alias root? empty?
|
19
|
+
|
20
|
+
def reset
|
21
|
+
self.stack = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def bucket?
|
25
|
+
stack.size == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def exist?
|
29
|
+
return true if root?
|
30
|
+
output, error, status = Open3.capture3('aws', 's3', 'ls', current_path)
|
31
|
+
status.success? && (bucket? || output != '')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module InteractiveS3
|
2
|
+
class S3Path
|
3
|
+
def initialize(target_path, current_stack)
|
4
|
+
@target_path = target_path
|
5
|
+
@current_stack = current_stack
|
6
|
+
end
|
7
|
+
|
8
|
+
def resolve
|
9
|
+
scanner = StringScanner.new(target_path.strip)
|
10
|
+
stack = current_stack.dup
|
11
|
+
|
12
|
+
begin
|
13
|
+
bol = scanner.bol?
|
14
|
+
segment = scanner.scan(/[^\/]*/)
|
15
|
+
scanner.getch
|
16
|
+
|
17
|
+
case segment.chomp
|
18
|
+
when ''
|
19
|
+
stack = [] if bol
|
20
|
+
when '.'
|
21
|
+
next
|
22
|
+
when '..'
|
23
|
+
stack.pop
|
24
|
+
else
|
25
|
+
stack << segment
|
26
|
+
end
|
27
|
+
rescue => e
|
28
|
+
raise CommandError.new(e).tap {|ex|
|
29
|
+
ex.set_backtrace(e.backtrace)
|
30
|
+
}
|
31
|
+
end until scanner.eos?
|
32
|
+
|
33
|
+
stack
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :current_stack, :target_path
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::CommandBuilder do
|
4
|
+
let(:builder) { described_class.new(context, params) }
|
5
|
+
let(:context) { double('context') }
|
6
|
+
|
7
|
+
before do
|
8
|
+
allow(context).to receive(:s3)
|
9
|
+
allow(context).to receive(:state)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#build' do
|
13
|
+
let(:command_class) { double('command_class') }
|
14
|
+
let(:params) { 'cd -' }
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(InteractiveS3::Commands::InternalCommand).to receive(:fetch) { command_class }
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates a command class and initializes it' do
|
21
|
+
expect(command_class).to receive(:new).with(context, 'cd', ['-'])
|
22
|
+
builder.build
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::Commands::Base do
|
4
|
+
let(:context) { double('context') }
|
5
|
+
let(:name) { double('name') }
|
6
|
+
|
7
|
+
before do
|
8
|
+
allow(context).to receive(:s3)
|
9
|
+
allow(context).to receive(:state)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#execute' do
|
13
|
+
context 'when implements the method' do
|
14
|
+
let(:instance) do
|
15
|
+
Class.new(described_class) {
|
16
|
+
def execute; end
|
17
|
+
}.new(context, name)
|
18
|
+
end
|
19
|
+
|
20
|
+
it { expect { instance.execute }.not_to raise_error }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when does not implements the method' do
|
24
|
+
let(:instance) do
|
25
|
+
described_class.new(context, name)
|
26
|
+
end
|
27
|
+
|
28
|
+
it { expect { instance.execute }.to raise_error(NotImplementedError) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::Commands::InternalCommand do
|
4
|
+
let(:context) { double('context') }
|
5
|
+
let(:s3) { InteractiveS3::S3.new }
|
6
|
+
let(:state) { {} }
|
7
|
+
|
8
|
+
before do
|
9
|
+
allow(context).to receive(:s3).and_return(s3)
|
10
|
+
allow(context).to receive(:state).and_return(state)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.fetch' do
|
14
|
+
subject { described_class.fetch(name.to_sym, InteractiveS3::Commands::S3Command) }
|
15
|
+
|
16
|
+
context 'given the params `cd`' do
|
17
|
+
let(:name) { 'cd' }
|
18
|
+
|
19
|
+
it { is_expected.to be described_class::Chdir }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'given the params `pwd`' do
|
23
|
+
let(:name) { 'pwd' }
|
24
|
+
|
25
|
+
it { is_expected.to be described_class::Pwd }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'given the params `lls`' do
|
29
|
+
let(:name) { 'lls' }
|
30
|
+
|
31
|
+
it { is_expected.to be described_class::LocalList }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'given the params `exit`' do
|
35
|
+
let(:name) { 'exit' }
|
36
|
+
|
37
|
+
it { is_expected.to be described_class::Exit }
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'given the params `ls`' do
|
41
|
+
let(:name) { 'ls' }
|
42
|
+
|
43
|
+
it { is_expected.to be InteractiveS3::Commands::S3Command }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe described_class::Chdir do
|
48
|
+
describe '#execute' do
|
49
|
+
before do
|
50
|
+
allow(s3).to receive(:exist?).and_return(true)
|
51
|
+
s3.stack << 'mybucket' << 'foo' << 'bar'
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:command) { described_class.new(context, 'cd', arguments) }
|
55
|
+
|
56
|
+
context 'target is nil' do
|
57
|
+
let(:arguments) { [] }
|
58
|
+
|
59
|
+
before { command.execute }
|
60
|
+
|
61
|
+
it { expect(s3.stack).to be_empty }
|
62
|
+
it { expect(state[:previous_stack]).to eq %w(mybucket foo bar) }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'target is `-`' do
|
66
|
+
let(:arguments) { ['-'] }
|
67
|
+
|
68
|
+
before do
|
69
|
+
state[:previous_stack] = %w(xxx yyy)
|
70
|
+
command.execute
|
71
|
+
end
|
72
|
+
|
73
|
+
it { expect(s3.stack).to eq %w(xxx yyy) }
|
74
|
+
it { expect(state[:previous_stack]).to eq %w(mybucket foo bar) }
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'target is `xxx/yyy`' do
|
78
|
+
let(:arguments) { ['xxx', 'yyy'] }
|
79
|
+
|
80
|
+
before do
|
81
|
+
allow_any_instance_of(InteractiveS3::S3Path).to receive(:resolve).and_return(['foo'])
|
82
|
+
command.execute
|
83
|
+
end
|
84
|
+
|
85
|
+
it { expect(s3.stack).to eq %w(foo) }
|
86
|
+
it { expect(state[:previous_stack]).to eq %w(mybucket foo bar) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe described_class::LocalList do
|
92
|
+
describe '#execute' do
|
93
|
+
let(:command) { described_class.new(context, 'lls') }
|
94
|
+
|
95
|
+
before do
|
96
|
+
allow(Dir).to receive(:entries).with('.').and_return(['.', '..', 'foo', 'bar'])
|
97
|
+
end
|
98
|
+
|
99
|
+
it { expect { command.execute }.to output("foo\nbar\n").to_stdout }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe described_class::Pwd do
|
104
|
+
describe '#execute' do
|
105
|
+
let(:command) { described_class.new(context, 'pwd') }
|
106
|
+
|
107
|
+
context 'when the s3 path is root' do
|
108
|
+
before do
|
109
|
+
allow(s3).to receive(:root?).and_return(true)
|
110
|
+
end
|
111
|
+
|
112
|
+
it { expect { command.execute }.to output("/\n").to_stdout }
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when the s3 path isn\'t root' do
|
116
|
+
before do
|
117
|
+
allow(s3).to receive(:root?).and_return(false)
|
118
|
+
allow(s3).to receive(:current_path).and_return("s3://mybucket/foo/bar")
|
119
|
+
end
|
120
|
+
|
121
|
+
it { expect { command.execute }.to output("s3://mybucket/foo/bar\n").to_stdout }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe described_class::Exit do
|
127
|
+
describe '#execute' do
|
128
|
+
let(:command) { described_class.new(context, 'exit') }
|
129
|
+
|
130
|
+
it { expect { command.execute }.to raise_exception(SystemExit) }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::Commands::S3Command do
|
4
|
+
let(:s3_command) { described_class.new(context, name, arguments) }
|
5
|
+
let(:context) { double('context') }
|
6
|
+
let(:s3) { InteractiveS3::S3.new }
|
7
|
+
let(:state) { {} }
|
8
|
+
|
9
|
+
before do
|
10
|
+
allow(context).to receive(:s3).and_return(s3)
|
11
|
+
allow(context).to receive(:state).and_return(state)
|
12
|
+
allow(Process).to receive(:spawn)
|
13
|
+
allow(Process).to receive(:wait)
|
14
|
+
allow_any_instance_of(InteractiveS3::S3Path).to receive(:resolve).and_return(['mybucket', 'bar'])
|
15
|
+
end
|
16
|
+
|
17
|
+
shared_examples_for 'parses commands with arguments and executes it' do |command_with_arguments|
|
18
|
+
it do
|
19
|
+
expect(Process).to receive(:spawn).with(*command_with_arguments, out: $stdout, err: $stderr)
|
20
|
+
expect(s3_command.execute).to be_truthy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#execute' do
|
25
|
+
context 'ls' do
|
26
|
+
let(:name) { 'ls' }
|
27
|
+
|
28
|
+
context 'with no sub command' do
|
29
|
+
let(:arguments) { [] }
|
30
|
+
|
31
|
+
it_behaves_like 'parses commands with arguments and executes it', ['aws', 's3', 'ls', '/']
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with help sub command' do
|
35
|
+
let(:arguments) { ['help'] }
|
36
|
+
|
37
|
+
it_behaves_like 'parses commands with arguments and executes it', ['aws', 's3', 'ls', 'help']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'cp' do
|
42
|
+
let(:name) { 'cp' }
|
43
|
+
|
44
|
+
context 'with local path and s3 path and option' do
|
45
|
+
let(:arguments) { [':foo', 'bar', '--option'] }
|
46
|
+
|
47
|
+
it_behaves_like 'parses commands with arguments and executes it', ['aws', 's3', 'cp', 'foo', 's3://mybucket/bar', '--option']
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with help sub command' do
|
51
|
+
let(:arguments) { ['help'] }
|
52
|
+
|
53
|
+
it_behaves_like 'parses commands with arguments and executes it', ['aws', 's3', 'cp', 'help']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe InteractiveS3::History do
|
5
|
+
let(:history) { described_class.new }
|
6
|
+
let(:file) { Tempfile.new('history') }
|
7
|
+
|
8
|
+
before do
|
9
|
+
stub_const('Readline::HISTORY', [])
|
10
|
+
stub_const('InteractiveS3::History::HISTORY_FILE', file.path)
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
file.delete
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#load' do
|
18
|
+
context 'when the file exists' do
|
19
|
+
before do
|
20
|
+
file.write("pwd\n")
|
21
|
+
file.write("ls\n")
|
22
|
+
file.rewind
|
23
|
+
history.load
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'load into the Readline from the file' do
|
27
|
+
expect(Readline::HISTORY.to_a).to eq %w(pwd ls)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when the file is empty' do
|
32
|
+
before { history.load }
|
33
|
+
|
34
|
+
it 'does not load into the Readline' do
|
35
|
+
expect(Readline::HISTORY.to_a).to be_empty
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#save' do
|
41
|
+
context 'when the file exists' do
|
42
|
+
before do
|
43
|
+
Readline::HISTORY << 'ls' << 'exit'
|
44
|
+
history.save
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'saves the history from Readline to file' do
|
48
|
+
expect(file.each_line.to_a.each(&:chomp!)).to eq %w(ls exit)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::Interpreter do
|
4
|
+
let(:interpreter) { described_class.new }
|
5
|
+
let(:input) { double('input') }
|
6
|
+
let(:command) { double('command') }
|
7
|
+
|
8
|
+
describe '#execute' do
|
9
|
+
before do
|
10
|
+
allow(InteractiveS3::CommandBuilder).to receive_message_chain(:new, :build) { command }
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'build the command and executes it' do
|
14
|
+
expect(command).to receive(:execute)
|
15
|
+
interpreter.execute(input)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'handles command error' do
|
19
|
+
allow(command).to receive(:execute).and_raise(InteractiveS3::CommandError)
|
20
|
+
expect { interpreter.execute(input) }.to output.to_stdout
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::S3Path do
|
4
|
+
describe '#resolve' do
|
5
|
+
subject { described_class.new(path, stack).resolve }
|
6
|
+
|
7
|
+
let(:stack) { ['foo', 'bar'] }
|
8
|
+
|
9
|
+
context 'with path is `baz`' do
|
10
|
+
let(:path) { 'baz' }
|
11
|
+
|
12
|
+
it { is_expected.to eq %w(foo bar baz) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'with path is `baz/qux`' do
|
16
|
+
let(:path) { 'baz/qux' }
|
17
|
+
|
18
|
+
it { is_expected.to eq %w(foo bar baz qux) }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with path is `../..`' do
|
22
|
+
let(:path) { '../..' }
|
23
|
+
|
24
|
+
it { is_expected.to be_empty }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with path is empty' do
|
28
|
+
let(:path) { '' }
|
29
|
+
|
30
|
+
it { is_expected.to be_empty }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with path is `./baz`' do
|
34
|
+
let(:path) { './baz' }
|
35
|
+
|
36
|
+
it { is_expected.to eq %w(foo bar baz) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe InteractiveS3::S3 do
|
4
|
+
let(:s3) { described_class.new }
|
5
|
+
|
6
|
+
def stack_to_s3
|
7
|
+
s3.stack << 'mybucket' << 'foo' << 'bar'
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#current_path' do
|
11
|
+
context 'when the stack exists' do
|
12
|
+
before { stack_to_s3 }
|
13
|
+
|
14
|
+
it { expect(s3.current_path).to eq('s3://mybucket/foo/bar') }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when the stack does not exists' do
|
18
|
+
it { expect(s3.current_path).to be_empty }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#empty?' do
|
23
|
+
context 'when the stack exists' do
|
24
|
+
before { stack_to_s3 }
|
25
|
+
|
26
|
+
it { expect(s3.empty?).to be_falsey }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when the stack does not exists' do
|
30
|
+
it { expect(s3.empty?).to be_truthy }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#reset' do
|
35
|
+
before do
|
36
|
+
stack_to_s3
|
37
|
+
s3.reset
|
38
|
+
end
|
39
|
+
|
40
|
+
it { expect(s3.stack).to be_empty }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#bucket?' do
|
44
|
+
context 'when size of the stack is 1' do
|
45
|
+
before { s3.stack << 'mybucket' }
|
46
|
+
|
47
|
+
it { expect(s3.bucket?).to be_truthy }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when size of the stack is greater than 1' do
|
51
|
+
before { stack_to_s3 }
|
52
|
+
|
53
|
+
it { expect(s3.bucket?).to be_falsey }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when the stack does not exists' do
|
57
|
+
it { expect(s3.bucket?).to be_falsey }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#exist?' do
|
62
|
+
subject { s3.exist? }
|
63
|
+
|
64
|
+
context 'when the s3 path is root' do
|
65
|
+
it { is_expected.to be_truthy }
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when the s3 path isn\'t root' do
|
69
|
+
let(:error) { double('error') }
|
70
|
+
let(:status) { double('status') }
|
71
|
+
|
72
|
+
before do
|
73
|
+
allow(Open3).to receive(:capture3).and_return([output, error, status])
|
74
|
+
allow(s3).to receive(:root?).and_return(false)
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'status is success and output exists' do
|
78
|
+
let(:output) { 'output' }
|
79
|
+
|
80
|
+
before do
|
81
|
+
allow(status).to receive(:success?).and_return(true)
|
82
|
+
end
|
83
|
+
|
84
|
+
it { is_expected.to be_truthy }
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'status is success and output does not exists' do
|
88
|
+
let(:output) { '' }
|
89
|
+
|
90
|
+
before do
|
91
|
+
allow(status).to receive(:success?).and_return(true)
|
92
|
+
end
|
93
|
+
|
94
|
+
it { is_expected.to be_falsey }
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'status is success and current_path is bucket and output exists' do
|
98
|
+
let(:output) { '' }
|
99
|
+
|
100
|
+
before do
|
101
|
+
allow(status).to receive(:success?).and_return(true)
|
102
|
+
allow(s3).to receive(:bucket?).and_return(true)
|
103
|
+
end
|
104
|
+
|
105
|
+
it { is_expected.to be_truthy }
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'status is not success' do
|
109
|
+
before do
|
110
|
+
allow(status).to receive(:success?).and_return(false)
|
111
|
+
end
|
112
|
+
|
113
|
+
it { is_expected.to be_falsey }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: interactive_s3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yamayo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- noorthaven@gmail.com
|
58
|
+
executables:
|
59
|
+
- is3
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/is3
|
71
|
+
- interactive_s3.gemspec
|
72
|
+
- lib/interactive_s3.rb
|
73
|
+
- lib/interactive_s3/cli.rb
|
74
|
+
- lib/interactive_s3/command_builder.rb
|
75
|
+
- lib/interactive_s3/commands/base.rb
|
76
|
+
- lib/interactive_s3/commands/internal_command.rb
|
77
|
+
- lib/interactive_s3/commands/s3_command.rb
|
78
|
+
- lib/interactive_s3/errors.rb
|
79
|
+
- lib/interactive_s3/history.rb
|
80
|
+
- lib/interactive_s3/interpreter.rb
|
81
|
+
- lib/interactive_s3/s3.rb
|
82
|
+
- lib/interactive_s3/s3_path.rb
|
83
|
+
- lib/interactive_s3/version.rb
|
84
|
+
- spec/interactive_s3/command_builder_spec.rb
|
85
|
+
- spec/interactive_s3/commands/base_spec.rb
|
86
|
+
- spec/interactive_s3/commands/internal_command_spec.rb
|
87
|
+
- spec/interactive_s3/commands/s3_command_spec.rb
|
88
|
+
- spec/interactive_s3/history_spec.rb
|
89
|
+
- spec/interactive_s3/interpreter_spec.rb
|
90
|
+
- spec/interactive_s3/s3_path_spec.rb
|
91
|
+
- spec/interactive_s3/s3_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: ''
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.2.2
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: An interactive shell for AWS CLI (aws s3)
|
117
|
+
test_files:
|
118
|
+
- spec/interactive_s3/command_builder_spec.rb
|
119
|
+
- spec/interactive_s3/commands/base_spec.rb
|
120
|
+
- spec/interactive_s3/commands/internal_command_spec.rb
|
121
|
+
- spec/interactive_s3/commands/s3_command_spec.rb
|
122
|
+
- spec/interactive_s3/history_spec.rb
|
123
|
+
- spec/interactive_s3/interpreter_spec.rb
|
124
|
+
- spec/interactive_s3/s3_path_spec.rb
|
125
|
+
- spec/interactive_s3/s3_spec.rb
|
126
|
+
- spec/spec_helper.rb
|