shuck 0.0.7 → 0.0.8
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/Gemfile.lock +1 -9
- data/Rakefile +1 -0
- data/lib/shuck/cli.rb +11 -0
- data/lib/shuck/file_store.rb +59 -3
- data/lib/shuck/rate_limitable_file.rb +21 -0
- data/lib/shuck/server.rb +96 -32
- data/lib/shuck/version.rb +1 -1
- data/lib/shuck/xml_adapter.rb +3 -3
- data/shuck.gemspec +2 -1
- data/test/local_s3_cfg +34 -0
- data/test/right_aws_commands_test.rb +10 -1
- data/test/s3cmd_test.rb +30 -0
- metadata +11 -19
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shuck (0.0.
|
4
|
+
shuck (0.0.8)
|
5
5
|
builder
|
6
6
|
thor
|
7
7
|
|
@@ -13,17 +13,10 @@ GEM
|
|
13
13
|
mime-types
|
14
14
|
xml-simple
|
15
15
|
builder (2.1.2)
|
16
|
-
columnize (0.3.2)
|
17
|
-
linecache (0.43)
|
18
16
|
mime-types (1.16)
|
19
17
|
right_aws (2.0.0)
|
20
18
|
right_http_connection (>= 1.2.1)
|
21
19
|
right_http_connection (1.2.4)
|
22
|
-
ruby-debug (0.10.4)
|
23
|
-
columnize (>= 0.1)
|
24
|
-
ruby-debug-base (~> 0.10.4.0)
|
25
|
-
ruby-debug-base (0.10.4)
|
26
|
-
linecache (>= 0.3)
|
27
20
|
thor (0.14.4)
|
28
21
|
xml-simple (1.0.12)
|
29
22
|
|
@@ -35,6 +28,5 @@ DEPENDENCIES
|
|
35
28
|
builder
|
36
29
|
bundler (>= 1.0.0)
|
37
30
|
right_aws
|
38
|
-
ruby-debug
|
39
31
|
shuck!
|
40
32
|
thor
|
data/Rakefile
CHANGED
data/lib/shuck/cli.rb
CHANGED
@@ -9,12 +9,14 @@ module Shuck
|
|
9
9
|
method_option :root, :type => :string, :aliases => '-r', :required => true
|
10
10
|
method_option :port, :type => :numeric, :aliases => '-p', :required => true
|
11
11
|
method_option :hostname, :type => :string, :aliases => '-h', :desc => "The root name of the host. Defaults to localhost."
|
12
|
+
method_option :limit, :aliases => '-l', :type => :string, :desc => 'Rate limit for serving (ie. 50K, 1.0M)'
|
12
13
|
def server
|
13
14
|
store = nil
|
14
15
|
if options[:root]
|
15
16
|
root = File.expand_path(options[:root])
|
16
17
|
store = FileStore.new(root)
|
17
18
|
end
|
19
|
+
|
18
20
|
if store.nil?
|
19
21
|
puts "You must specify a root to use a file store (the current default)"
|
20
22
|
exit(-1)
|
@@ -25,6 +27,15 @@ module Shuck
|
|
25
27
|
hostname = options[:hostname]
|
26
28
|
end
|
27
29
|
|
30
|
+
if options[:limit]
|
31
|
+
begin
|
32
|
+
store.rate_limit = options[:limit]
|
33
|
+
rescue
|
34
|
+
puts $!.message
|
35
|
+
exit(-1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
28
39
|
puts "Loading Shuck with #{root} on port #{options[:port]} with hostname #{hostname}"
|
29
40
|
server = Shuck::Server.new(options[:port],store,hostname)
|
30
41
|
server.serve
|
data/lib/shuck/file_store.rb
CHANGED
@@ -2,6 +2,7 @@ require 'fileutils'
|
|
2
2
|
require 'time'
|
3
3
|
require 'shuck/s3_object'
|
4
4
|
require 'shuck/bucket'
|
5
|
+
require 'shuck/rate_limitable_file'
|
5
6
|
require 'digest/md5'
|
6
7
|
require 'yaml'
|
7
8
|
|
@@ -21,6 +22,25 @@ module Shuck
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
# Pass a rate limit in bytes per second
|
26
|
+
def rate_limit=(rate_limit)
|
27
|
+
if rate_limit.is_a?(String)
|
28
|
+
if rate_limit =~ /^(\d+)$/
|
29
|
+
RateLimitableFile.rate_limit = rate_limit.to_i
|
30
|
+
elsif rate_limit =~ /^(.*)K$/
|
31
|
+
RateLimitableFile.rate_limit = $1.to_f * 1000
|
32
|
+
elsif rate_limit =~ /^(.*)M$/
|
33
|
+
RateLimitableFile.rate_limit = $1.to_f * 1000000
|
34
|
+
elsif rate_limit =~ /^(.*)G$/
|
35
|
+
RateLimitableFile.rate_limit = $1.to_f * 1000000000
|
36
|
+
else
|
37
|
+
raise "Invalid Rate Limit Format: Valid values include (1000,10K,1.1M)"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
RateLimitableFile.rate_limit = nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
24
44
|
def buckets
|
25
45
|
@buckets
|
26
46
|
end
|
@@ -37,14 +57,15 @@ module Shuck
|
|
37
57
|
end
|
38
58
|
|
39
59
|
def get_object(bucket,object, request)
|
40
|
-
begin
|
60
|
+
begin
|
41
61
|
real_obj = S3Object.new
|
42
62
|
obj_root = File.join(@root,bucket,object,SHUCK_METADATA_DIR)
|
43
63
|
metadata = YAML.parse(File.open(File.join(obj_root,"metadata"),'rb').read)
|
44
64
|
real_obj.name = object
|
45
65
|
real_obj.md5 = metadata[:md5].value
|
46
66
|
real_obj.content_type = metadata[:content_type] ? metadata[:content_type].value : "application/octet-stream"
|
47
|
-
real_obj.io = File.open(File.join(obj_root,"content"),'rb')
|
67
|
+
#real_obj.io = File.open(File.join(obj_root,"content"),'rb')
|
68
|
+
real_obj.io = RateLimitableFile.open(File.join(obj_root,"content"),'rb')
|
48
69
|
return real_obj
|
49
70
|
rescue
|
50
71
|
puts $!
|
@@ -55,6 +76,40 @@ module Shuck
|
|
55
76
|
def object_metadata(bucket,object)
|
56
77
|
end
|
57
78
|
|
79
|
+
def copy_object(src_bucket,src_object,dst_bucket,dst_object)
|
80
|
+
src_root = File.join(@root,src_bucket,src_object,SHUCK_METADATA_DIR)
|
81
|
+
src_obj = S3Object.new
|
82
|
+
src_metadata_filename = File.join(src_root,"metadata")
|
83
|
+
src_metadata = YAML.parse(File.open(src_metadata_filename,'rb').read)
|
84
|
+
src_content_filename = File.join(src_root,"content")
|
85
|
+
|
86
|
+
dst_filename= File.join(@root,dst_bucket,dst_object)
|
87
|
+
FileUtils.mkdir_p(dst_filename)
|
88
|
+
|
89
|
+
metadata_dir = File.join(dst_filename,SHUCK_METADATA_DIR)
|
90
|
+
FileUtils.mkdir_p(metadata_dir)
|
91
|
+
|
92
|
+
content = File.join(metadata_dir,"content")
|
93
|
+
metadata = File.join(metadata_dir,"metadata")
|
94
|
+
|
95
|
+
File.open(content,'wb') do |f|
|
96
|
+
File.open(src_content_filename,'rb') do |input|
|
97
|
+
f << input.read
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
File.open(metadata,'w') do |f|
|
102
|
+
File.open(src_metadata_filename,'r') do |input|
|
103
|
+
f << input.read
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
obj = S3Object.new
|
108
|
+
obj.md5 = src_metadata[:md5]
|
109
|
+
obj.content_type = src_metadata[:content_type]
|
110
|
+
return obj
|
111
|
+
end
|
112
|
+
|
58
113
|
def store_object(bucket,object,request)
|
59
114
|
begin
|
60
115
|
filename = File.join(@root,bucket,object)
|
@@ -67,7 +122,8 @@ module Shuck
|
|
67
122
|
metadata = File.join(filename,SHUCK_METADATA_DIR,"metadata")
|
68
123
|
|
69
124
|
md5 = Digest::MD5.new
|
70
|
-
|
125
|
+
|
126
|
+
File.open(content,'wb') do |f|
|
71
127
|
request.body do |chunk|
|
72
128
|
f << chunk
|
73
129
|
md5 << chunk
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Shuck
|
2
|
+
class RateLimitableFile < File
|
3
|
+
@@rate_limit = nil
|
4
|
+
# Specify a rate limit in bytes per second
|
5
|
+
def self.rate_limit
|
6
|
+
@@rate_limit
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.rate_limit=(rate_limit)
|
10
|
+
@@rate_limit = rate_limit
|
11
|
+
end
|
12
|
+
|
13
|
+
def read(args)
|
14
|
+
if @@rate_limit
|
15
|
+
time_to_sleep = args / @@rate_limit
|
16
|
+
sleep(time_to_sleep)
|
17
|
+
end
|
18
|
+
return super(args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/shuck/server.rb
CHANGED
@@ -3,6 +3,28 @@ require 'shuck/file_store'
|
|
3
3
|
require 'shuck/xml_adapter'
|
4
4
|
|
5
5
|
module Shuck
|
6
|
+
class Request
|
7
|
+
CREATE_BUCKET = "CREATE_BUCKET"
|
8
|
+
STORE = "STORE"
|
9
|
+
COPY = "COPY"
|
10
|
+
GET = "GET"
|
11
|
+
MOVE = "MOVE"
|
12
|
+
DELETE = "DELETE"
|
13
|
+
|
14
|
+
attr_accessor :bucket,:object,:type,:src_bucket,:src_object,:method,:webrick_request,:path
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
puts "-----Inspect Shuck Request"
|
18
|
+
puts "Type: #{@type}"
|
19
|
+
puts "Request Method: #{@method}"
|
20
|
+
puts "Bucket: #{@bucket}"
|
21
|
+
puts "Object: #{@object}"
|
22
|
+
puts "Src Bucket: #{@src_bucket}"
|
23
|
+
puts "Src Object: #{@src_object}"
|
24
|
+
puts "-----Done"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
6
28
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
7
29
|
def initialize(server,store,hostname)
|
8
30
|
super(server)
|
@@ -34,16 +56,22 @@ module Shuck
|
|
34
56
|
elems = path.split("/")
|
35
57
|
end
|
36
58
|
|
37
|
-
if elems.size ==
|
59
|
+
if elems.size == 0
|
60
|
+
# List buckets
|
61
|
+
buckets = @store.buckets
|
62
|
+
response.status = 200
|
63
|
+
response.body = XmlAdapter.buckets(buckets)
|
64
|
+
response['Content-Type'] = "application/xml"
|
65
|
+
elsif elems.size == 1
|
38
66
|
bucket_obj = @store.get_bucket(bucket)
|
39
67
|
if bucket_obj
|
40
68
|
response.status = 200
|
41
69
|
response.body = XmlAdapter.bucket(bucket_obj)
|
42
|
-
response['Content-Type'] = "application/xml"
|
70
|
+
response['Content-Type'] = "application/xml"
|
43
71
|
else
|
44
72
|
response.status = 404
|
45
73
|
response.body = XmlAdapter.error_no_such_bucket(bucket)
|
46
|
-
response['Content-Type'] = "application/xml"
|
74
|
+
response['Content-Type'] = "application/xml"
|
47
75
|
end
|
48
76
|
else
|
49
77
|
object = elems[1,elems.size].join('/')
|
@@ -88,61 +116,97 @@ module Shuck
|
|
88
116
|
end
|
89
117
|
|
90
118
|
def do_PUT(request,response)
|
91
|
-
|
119
|
+
s_req = normalize_request(request)
|
120
|
+
|
121
|
+
case s_req.type
|
122
|
+
when Request::COPY
|
123
|
+
@store.copy_object(s_req.src_bucket,s_req.src_object,s_req.bucket,s_req.object)
|
124
|
+
when Request::STORE
|
125
|
+
real_obj = @store.store_object(s_req.bucket,s_req.object,s_req.webrick_request)
|
126
|
+
response['Etag'] = real_obj.md5
|
127
|
+
when Request::CREATE_BUCKET
|
128
|
+
@store.create_bucket(s_req.bucket)
|
129
|
+
end
|
130
|
+
|
131
|
+
response.status = 200
|
132
|
+
response.body = ""
|
133
|
+
response['Content-Type'] = "text/xml"
|
134
|
+
end
|
135
|
+
|
136
|
+
def do_POST(request,response)
|
137
|
+
p request
|
138
|
+
end
|
139
|
+
|
140
|
+
def do_DELETE(request,response)
|
141
|
+
p request
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
# This method takes a webrick request and generates a normalized Shuck request
|
146
|
+
def normalize_request(webrick_r)
|
147
|
+
path = webrick_r.path
|
92
148
|
path_len = path.size
|
93
149
|
path_style_request = true
|
94
|
-
host =
|
95
|
-
puts host
|
150
|
+
host = webrick_r["Host"]
|
96
151
|
bucket = nil
|
152
|
+
object = nil
|
97
153
|
path_style_request = true
|
154
|
+
|
98
155
|
if host != @hostname
|
99
156
|
bucket = host.split(".")[0]
|
100
157
|
path_style_request = false
|
101
158
|
end
|
102
159
|
|
160
|
+
req = Request.new
|
161
|
+
req.path = webrick_r.path
|
162
|
+
|
103
163
|
if path == "/"
|
104
164
|
if bucket
|
105
|
-
|
106
|
-
else
|
107
|
-
puts "Unsure what to do"
|
165
|
+
req.type = Request::CREATE_BUCKET
|
108
166
|
end
|
109
|
-
else
|
167
|
+
else
|
110
168
|
if path_style_request
|
111
169
|
elems = path[1,path_len].split("/")
|
112
170
|
bucket = elems[0]
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
if path_style_request
|
118
|
-
if elems.size == 1
|
119
|
-
@store.create_bucket(bucket)
|
120
|
-
else
|
171
|
+
if elems.size == 1
|
172
|
+
req.type = Request::CREATE_BUCKET
|
173
|
+
else
|
174
|
+
req.type = Request::STORE
|
121
175
|
object = elems[1,elems.size].join('/')
|
122
|
-
real_obj = @store.store_object(bucket,object,request)
|
123
|
-
response['Etag'] = real_obj.md5
|
124
176
|
end
|
125
177
|
else
|
126
|
-
|
127
|
-
|
178
|
+
req.type = Request::STORE
|
179
|
+
object = webrick_r.path
|
128
180
|
end
|
129
181
|
end
|
130
|
-
response.status = 200
|
131
|
-
response.body = ""
|
132
|
-
response['Content-Type'] = "text/xml"
|
133
|
-
end
|
134
182
|
|
135
|
-
|
136
|
-
|
137
|
-
|
183
|
+
copy_source = webrick_r.header["x-amz-copy-source"]
|
184
|
+
if copy_source and copy_source.size == 1
|
185
|
+
src_elems = copy_source.first.split("/")
|
186
|
+
root_offset = src_elems[0] == "" ? 1 : 0
|
187
|
+
req.src_bucket = src_elems[root_offset]
|
188
|
+
req.src_object = src_elems[1 + root_offset,src_elems.size].join("/")
|
189
|
+
req.type = Request::COPY
|
190
|
+
end
|
191
|
+
|
192
|
+
req.bucket = bucket
|
193
|
+
req.object = object
|
194
|
+
req.webrick_request = webrick_r
|
195
|
+
return req
|
138
196
|
end
|
139
197
|
|
140
|
-
def
|
141
|
-
puts "
|
142
|
-
|
198
|
+
def dump_request(request)
|
199
|
+
puts "----------Dump Request-------------"
|
200
|
+
puts request.request_method
|
201
|
+
puts request.path
|
202
|
+
request.each do |k,v|
|
203
|
+
puts "#{k}:#{v}"
|
204
|
+
end
|
205
|
+
puts "----------End Dump -------------"
|
143
206
|
end
|
144
207
|
end
|
145
208
|
|
209
|
+
|
146
210
|
class Server
|
147
211
|
def initialize(port,store,hostname = "localhost")
|
148
212
|
@port = port
|
data/lib/shuck/version.rb
CHANGED
data/lib/shuck/xml_adapter.rb
CHANGED
@@ -3,7 +3,7 @@ require 'time'
|
|
3
3
|
|
4
4
|
module Shuck
|
5
5
|
class XmlAdapter
|
6
|
-
def self.buckets(
|
6
|
+
def self.buckets(bucket_objects)
|
7
7
|
output = ""
|
8
8
|
xml = Builder::XmlMarkup.new(:target => output)
|
9
9
|
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
@@ -13,10 +13,10 @@ module Shuck
|
|
13
13
|
owner.DisplayName("Shuck")
|
14
14
|
}
|
15
15
|
lam.Buckets { |buckets|
|
16
|
-
|
16
|
+
bucket_objects.each do |bucket|
|
17
17
|
buckets.Bucket do |b|
|
18
18
|
b.Name(bucket.name)
|
19
|
-
b.CreationDate(bucket.creation_date.
|
19
|
+
b.CreationDate(bucket.creation_date.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
|
20
20
|
end
|
21
21
|
end
|
22
22
|
}
|
data/shuck.gemspec
CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
18
|
s.add_development_dependency "aws-s3"
|
19
19
|
s.add_development_dependency "right_aws"
|
20
|
-
s.add_development_dependency "
|
20
|
+
#s.add_development_dependency "aws-sdk"
|
21
|
+
#s.add_development_dependency "ruby-debug19"
|
21
22
|
s.add_dependency "thor"
|
22
23
|
s.add_dependency "builder"
|
23
24
|
|
data/test/local_s3_cfg
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
[default]
|
2
|
+
access_key = abc
|
3
|
+
acl_public = False
|
4
|
+
bucket_location = US
|
5
|
+
cloudfront_host = cloudfront.amazonaws.com
|
6
|
+
cloudfront_resource = /2008-06-30/distribution
|
7
|
+
default_mime_type = binary/octet-stream
|
8
|
+
delete_removed = False
|
9
|
+
dry_run = False
|
10
|
+
encoding = UTF-8
|
11
|
+
encrypt = False
|
12
|
+
force = False
|
13
|
+
get_continue = False
|
14
|
+
gpg_command = None
|
15
|
+
gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
|
16
|
+
gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
|
17
|
+
gpg_passphrase =
|
18
|
+
guess_mime_type = True
|
19
|
+
host_base = s3.localhost:10453
|
20
|
+
host_bucket = %(bucket)s.s3.localhost:10453
|
21
|
+
human_readable_sizes = False
|
22
|
+
list_md5 = False
|
23
|
+
preserve_attrs = True
|
24
|
+
progress_meter = True
|
25
|
+
proxy_host =
|
26
|
+
proxy_port = 0
|
27
|
+
recursive = False
|
28
|
+
recv_chunk = 4096
|
29
|
+
secret_key = def
|
30
|
+
send_chunk = 4096
|
31
|
+
simpledb_host = sdb.amazonaws.com
|
32
|
+
skip_existing = False
|
33
|
+
use_https = False
|
34
|
+
verbosity = WARNING
|
@@ -6,7 +6,9 @@ require 'right_aws'
|
|
6
6
|
class RightAWSCommandsTest < Test::Unit::TestCase
|
7
7
|
|
8
8
|
def setup
|
9
|
-
@s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
|
9
|
+
@s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
|
10
|
+
{:multi_thread => false, :server => 'localhost',
|
11
|
+
:port => 10453, :protocol => 'http',:logger => Logger.new("/dev/null") })
|
10
12
|
end
|
11
13
|
|
12
14
|
def teardown
|
@@ -51,4 +53,11 @@ class RightAWSCommandsTest < Test::Unit::TestCase
|
|
51
53
|
assert_equal "recursive", output
|
52
54
|
end
|
53
55
|
|
56
|
+
def test_intra_bucket_copy
|
57
|
+
@s3.put("s3media","original.txt","Hello World")
|
58
|
+
@s3.copy("s3media","original.txt","s3media","copy.txt")
|
59
|
+
obj = @s3.get("s3media","copy.txt")
|
60
|
+
assert_equal "Hello World",obj[:object]
|
61
|
+
end
|
62
|
+
|
54
63
|
end
|
data/test/s3cmd_test.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shuck/server'
|
4
|
+
|
5
|
+
# You need to have s3cmd installed to use this
|
6
|
+
class S3CmdTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@s3cmd = "s3cmd --config"
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_bucket
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_store
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_large_store
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_multi_directory
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_intra_bucket_copy
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
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
|
+
- 8
|
9
|
+
version: 0.0.8
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Curtis Spencer
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2012-02-22 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -59,7 +59,7 @@ dependencies:
|
|
59
59
|
type: :development
|
60
60
|
version_requirements: *id003
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
62
|
+
name: thor
|
63
63
|
prerelease: false
|
64
64
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
@@ -69,10 +69,10 @@ dependencies:
|
|
69
69
|
segments:
|
70
70
|
- 0
|
71
71
|
version: "0"
|
72
|
-
type: :
|
72
|
+
type: :runtime
|
73
73
|
version_requirements: *id004
|
74
74
|
- !ruby/object:Gem::Dependency
|
75
|
-
name:
|
75
|
+
name: builder
|
76
76
|
prerelease: false
|
77
77
|
requirement: &id005 !ruby/object:Gem::Requirement
|
78
78
|
none: false
|
@@ -84,19 +84,6 @@ dependencies:
|
|
84
84
|
version: "0"
|
85
85
|
type: :runtime
|
86
86
|
version_requirements: *id005
|
87
|
-
- !ruby/object:Gem::Dependency
|
88
|
-
name: builder
|
89
|
-
prerelease: false
|
90
|
-
requirement: &id006 !ruby/object:Gem::Requirement
|
91
|
-
none: false
|
92
|
-
requirements:
|
93
|
-
- - ">="
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
segments:
|
96
|
-
- 0
|
97
|
-
version: "0"
|
98
|
-
type: :runtime
|
99
|
-
version_requirements: *id006
|
100
87
|
description: Use Shuck to test basic S3 functionality without actually connecting to S3
|
101
88
|
email:
|
102
89
|
- curtis@sevenforge.com
|
@@ -117,13 +104,16 @@ files:
|
|
117
104
|
- lib/shuck/bucket.rb
|
118
105
|
- lib/shuck/cli.rb
|
119
106
|
- lib/shuck/file_store.rb
|
107
|
+
- lib/shuck/rate_limitable_file.rb
|
120
108
|
- lib/shuck/s3_object.rb
|
121
109
|
- lib/shuck/server.rb
|
122
110
|
- lib/shuck/version.rb
|
123
111
|
- lib/shuck/xml_adapter.rb
|
124
112
|
- shuck.gemspec
|
113
|
+
- test/local_s3_cfg
|
125
114
|
- test/right_aws_commands_test.rb
|
126
115
|
- test/s3_commands_test.rb
|
116
|
+
- test/s3cmd_test.rb
|
127
117
|
- test/test_helper.rb
|
128
118
|
has_rdoc: true
|
129
119
|
homepage: ""
|
@@ -158,6 +148,8 @@ signing_key:
|
|
158
148
|
specification_version: 3
|
159
149
|
summary: Shuck is a phony S3 engine with minimal dependencies
|
160
150
|
test_files:
|
151
|
+
- test/local_s3_cfg
|
161
152
|
- test/right_aws_commands_test.rb
|
162
153
|
- test/s3_commands_test.rb
|
154
|
+
- test/s3cmd_test.rb
|
163
155
|
- test/test_helper.rb
|