s3cp 1.1.23 → 1.1.24
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/History.txt +5 -0
- data/lib/s3cp/s3buckets.rb +14 -3
- data/lib/s3cp/s3cat.rb +57 -49
- data/lib/s3cp/s3cp.rb +18 -12
- data/lib/s3cp/s3du.rb +9 -0
- data/lib/s3cp/s3ls.rb +11 -2
- data/lib/s3cp/s3mod.rb +27 -16
- data/lib/s3cp/s3rm.rb +44 -32
- data/lib/s3cp/s3tree.rb +9 -0
- data/lib/s3cp/s3up.rb +36 -24
- data/lib/s3cp/version.rb +1 -1
- metadata +3 -3
data/History.txt
CHANGED
data/lib/s3cp/s3buckets.rb
CHANGED
@@ -27,6 +27,10 @@ op = OptionParser.new do |opts|
|
|
27
27
|
options[:verbose] = true
|
28
28
|
end
|
29
29
|
|
30
|
+
opts.on("--debug", "Verbose mode") do
|
31
|
+
options[:debug] = true
|
32
|
+
end
|
33
|
+
|
30
34
|
opts.on_tail("-h", "--help", "Show this message") do
|
31
35
|
puts op
|
32
36
|
exit
|
@@ -36,8 +40,15 @@ op.parse!(ARGV)
|
|
36
40
|
|
37
41
|
S3CP.load_config()
|
38
42
|
|
39
|
-
|
40
|
-
s3
|
41
|
-
|
43
|
+
begin
|
44
|
+
s3 = S3CP.connect()
|
45
|
+
s3.buckets.each do |bucket|
|
46
|
+
puts bucket.name
|
47
|
+
end
|
48
|
+
rescue => e
|
49
|
+
$stderr.print "s3buckets: [#{e.class}] #{e.message}\n"
|
50
|
+
if options[:debug]
|
51
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
52
|
+
end
|
42
53
|
end
|
43
54
|
|
data/lib/s3cp/s3cat.rb
CHANGED
@@ -73,70 +73,78 @@ if options[:debug]
|
|
73
73
|
puts "Options: \n#{options.inspect}"
|
74
74
|
end
|
75
75
|
|
76
|
-
|
77
|
-
|
76
|
+
begin
|
77
|
+
@bucket, @prefix = S3CP.bucket_and_key(url)
|
78
|
+
fail "Your URL looks funny, doesn't it?" unless @bucket
|
78
79
|
|
79
|
-
S3CP.load_config()
|
80
|
+
S3CP.load_config()
|
80
81
|
|
81
|
-
read_options = {}
|
82
|
-
read_options[:debug] = options[:debug]
|
83
|
-
read_options[:head] = options[:head] if options[:head]
|
84
|
-
read_options[:tail] = options[:tail] if options[:tail]
|
85
|
-
read_options[:range_start] = options[:range_start] if options[:range_start]
|
86
|
-
read_options[:range_end] = options[:range_end] if options[:range_end]
|
82
|
+
read_options = {}
|
83
|
+
read_options[:debug] = options[:debug]
|
84
|
+
read_options[:head] = options[:head] if options[:head]
|
85
|
+
read_options[:tail] = options[:tail] if options[:tail]
|
86
|
+
read_options[:range_start] = options[:range_start] if options[:range_start]
|
87
|
+
read_options[:range_end] = options[:range_end] if options[:range_end]
|
87
88
|
|
88
|
-
@s3 = S3CP.connect().buckets[@bucket]
|
89
|
+
@s3 = S3CP.connect().buckets[@bucket]
|
89
90
|
|
90
91
|
|
91
|
-
if options[:edit] && (options[:head] || options[:tail] || options[:range_start] || options[:range_end])
|
92
|
-
|
93
|
-
end
|
92
|
+
if options[:edit] && (options[:head] || options[:tail] || options[:range_start] || options[:range_end])
|
93
|
+
fail "--edit option is not intended to be used with --head, --tail nor --range"
|
94
|
+
end
|
94
95
|
|
95
|
-
if options[:tty] || options[:edit]
|
96
|
-
|
97
|
-
|
96
|
+
if options[:tty] || options[:edit]
|
97
|
+
# store contents to file to display with PAGER
|
98
|
+
size = @s3.objects[@prefix].content_length
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
progress_bar = ProgressBar.new(File.basename(@prefix), size).tap do |p|
|
101
|
+
p.file_transfer_mode
|
102
|
+
end
|
102
103
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
104
|
+
file = Tempfile.new(File.basename(@prefix) + '_')
|
105
|
+
out = File.new(file.path, "wb")
|
106
|
+
begin
|
107
|
+
@s3.objects[@prefix].read_as_stream(read_options) do |chunk|
|
108
|
+
out.write(chunk)
|
109
|
+
progress_bar.inc chunk.size
|
110
|
+
end
|
111
|
+
progress_bar.finish
|
112
|
+
ensure
|
113
|
+
out.close()
|
109
114
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
load "s3cp/s3cp.rb"
|
115
|
+
if options[:edit]
|
116
|
+
before_md5 = S3CP.md5(file.path)
|
117
|
+
system "#{ENV['EDITOR'] || 'vi'} #{file.path}"
|
118
|
+
if ($? == 0)
|
119
|
+
if (S3CP.md5(file.path) != before_md5)
|
120
|
+
ARGV.clear
|
121
|
+
ARGV << file.path
|
122
|
+
ARGV << url
|
123
|
+
load "s3cp/s3cp.rb"
|
124
|
+
else
|
125
|
+
puts "File unchanged."
|
126
|
+
end
|
123
127
|
else
|
124
|
-
puts "
|
128
|
+
puts "Edit aborted (result code #{$?})."
|
125
129
|
end
|
126
130
|
else
|
127
|
-
|
131
|
+
system "#{ENV['PAGER'] || 'less'} #{file.path}"
|
128
132
|
end
|
133
|
+
file.delete()
|
129
134
|
else
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
STDOUT.print(chunk)
|
137
|
-
rescue Errno::EPIPE
|
138
|
-
break
|
135
|
+
@s3.objects[@prefix].read_as_stream(read_options) do |chunk|
|
136
|
+
begin
|
137
|
+
STDOUT.print(chunk)
|
138
|
+
rescue Errno::EPIPE
|
139
|
+
break
|
140
|
+
end
|
139
141
|
end
|
140
142
|
end
|
143
|
+
|
144
|
+
rescue => e
|
145
|
+
$stderr.print "s3cat: [#{e.class}] #{e.message}\n"
|
146
|
+
if options[:debug]
|
147
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
148
|
+
end
|
141
149
|
end
|
142
150
|
|
data/lib/s3cp/s3cp.rb
CHANGED
@@ -148,12 +148,12 @@ if ARGV.size < 2
|
|
148
148
|
end
|
149
149
|
|
150
150
|
if options[:include_regex].any? && !options[:recursive]
|
151
|
-
puts "-i (--include regex) option requires -r (recursive) option."
|
151
|
+
STDERR.puts "-i (--include regex) option requires -r (recursive) option."
|
152
152
|
exit(1)
|
153
153
|
end
|
154
154
|
|
155
155
|
if options[:exclude_regex].any? && !options[:recursive]
|
156
|
-
puts "-x (--exclude regex) option requires -r (recursive) option."
|
156
|
+
STDERR.puts "-x (--exclude regex) option requires -r (recursive) option."
|
157
157
|
exit(1)
|
158
158
|
end
|
159
159
|
|
@@ -161,9 +161,9 @@ destination = ARGV.last
|
|
161
161
|
sources = ARGV[0..-2]
|
162
162
|
|
163
163
|
if options[:debug]
|
164
|
-
puts "sources: #{sources.inspect}"
|
165
|
-
puts "destination: #{destination}"
|
166
|
-
puts "Options: \n#{options.inspect}"
|
164
|
+
STDERR.puts "sources: #{sources.inspect}"
|
165
|
+
STDERR.puts "destination: #{destination}"
|
166
|
+
STDERR.puts "Options: \n#{options.inspect}"
|
167
167
|
end
|
168
168
|
|
169
169
|
class ProxyIO
|
@@ -347,7 +347,7 @@ def local_to_s3(bucket_to, key, file, options = {})
|
|
347
347
|
actual_md5 = "bad"
|
348
348
|
if progress_bar
|
349
349
|
progress_bar.clear
|
350
|
-
puts "Error copying #{file} to s3://#{bucket_to}/#{key}"
|
350
|
+
STDERR.puts "Error copying #{file} to s3://#{bucket_to}/#{key}"
|
351
351
|
end
|
352
352
|
raise e if !options[:checksum] || e.is_a?(AWS::S3::Errors::AccessDenied)
|
353
353
|
STDERR.puts e
|
@@ -496,9 +496,9 @@ def copy(from, to, options)
|
|
496
496
|
files = Dir[from + "/**/*"]
|
497
497
|
files.each do |f|
|
498
498
|
if File.file?(f) && match(f)
|
499
|
-
puts "bucket_to #{bucket_to}"
|
500
|
-
puts "no_slash(key_to) #{no_slash(key_to)}"
|
501
|
-
puts "relative(from, f) #{relative(from, f)}"
|
499
|
+
#puts "bucket_to #{bucket_to}"
|
500
|
+
#puts "no_slash(key_to) #{no_slash(key_to)}"
|
501
|
+
#puts "relative(from, f) #{relative(from, f)}"
|
502
502
|
key = key_path key_to, relative(from, f)
|
503
503
|
local_to_s3(bucket_to, key, File.expand_path(f), options) unless !options[:overwrite] && s3_exist?(bucket_to, key)
|
504
504
|
end
|
@@ -542,7 +542,13 @@ def copy(from, to, options)
|
|
542
542
|
end
|
543
543
|
end
|
544
544
|
|
545
|
-
|
546
|
-
|
545
|
+
begin
|
546
|
+
sources.each do |source|
|
547
|
+
copy(source, destination, options)
|
548
|
+
end
|
549
|
+
rescue => e
|
550
|
+
$stderr.print "s3cp: [#{e.class}] #{e.message}\n"
|
551
|
+
if options[:debug]
|
552
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
553
|
+
end
|
547
554
|
end
|
548
|
-
|
data/lib/s3cp/s3du.rb
CHANGED
@@ -44,6 +44,10 @@ op = OptionParser.new do |opts|
|
|
44
44
|
options[:regex] = Regexp.new(regex)
|
45
45
|
end
|
46
46
|
|
47
|
+
opts.on("--debug", "Debug mode") do
|
48
|
+
options[:debug] = true
|
49
|
+
end
|
50
|
+
|
47
51
|
opts.on_tail("-h", "--help", "Show this message") do
|
48
52
|
puts op
|
49
53
|
exit
|
@@ -140,5 +144,10 @@ begin
|
|
140
144
|
end
|
141
145
|
rescue Errno::EPIPE
|
142
146
|
# ignore
|
147
|
+
rescue => e
|
148
|
+
$stderr.print "s3du: [#{e.class}] #{e.message}\n"
|
149
|
+
if options[:debug]
|
150
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
151
|
+
end
|
143
152
|
end
|
144
153
|
|
data/lib/s3cp/s3ls.rb
CHANGED
@@ -39,6 +39,10 @@ op = OptionParser.new do |opts|
|
|
39
39
|
options[:verbose] = true
|
40
40
|
end
|
41
41
|
|
42
|
+
opts.on("--debug", "Debug mode") do
|
43
|
+
options[:debug] = true
|
44
|
+
end
|
45
|
+
|
42
46
|
opts.on("--rows ROWS", "Rows per page") do |rows|
|
43
47
|
options[:rows_per_page] = rows.to_i
|
44
48
|
end
|
@@ -73,7 +77,7 @@ end
|
|
73
77
|
|
74
78
|
url = ARGV[0]
|
75
79
|
|
76
|
-
if options[:
|
80
|
+
if options[:debug]
|
77
81
|
puts "URL: #{url}"
|
78
82
|
puts "Options: #{options.inspect}"
|
79
83
|
end
|
@@ -81,7 +85,7 @@ end
|
|
81
85
|
@bucket, @key = S3CP.bucket_and_key(url)
|
82
86
|
fail "Your URL looks funny, doesn't it?" unless @bucket
|
83
87
|
|
84
|
-
if options[:
|
88
|
+
if options[:debug]
|
85
89
|
puts "bucket #{@bucket}"
|
86
90
|
puts "key #{@key}"
|
87
91
|
end
|
@@ -151,5 +155,10 @@ begin
|
|
151
155
|
end
|
152
156
|
rescue Errno::EPIPE
|
153
157
|
# ignore
|
158
|
+
rescue => e
|
159
|
+
$stderr.print "s3ls: [#{e.class}] #{e.message}\n"
|
160
|
+
if options[:debug]
|
161
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
162
|
+
end
|
154
163
|
end
|
155
164
|
|
data/lib/s3cp/s3mod.rb
CHANGED
@@ -55,6 +55,10 @@ op = OptionParser.new do |opts|
|
|
55
55
|
options[:verbose] = true
|
56
56
|
end
|
57
57
|
|
58
|
+
opts.on("--debug", "Debug mode") do
|
59
|
+
options[:debug] = true
|
60
|
+
end
|
61
|
+
|
58
62
|
opts.on('--headers \'Header1: Header1Value\',\'Header2: Header2Value\'', Array, "Headers to set on the item in S3." ) do |h|
|
59
63
|
options[:headers] += h
|
60
64
|
end
|
@@ -87,23 +91,30 @@ if !options[:acl] && paths.size > 1 && S3CP::LEGAL_MODS.include?(paths.last)
|
|
87
91
|
options[:acl] = S3CP.validate_acl(paths.pop);
|
88
92
|
end
|
89
93
|
|
90
|
-
|
91
|
-
|
94
|
+
begin
|
95
|
+
S3CP.load_config()
|
96
|
+
@s3 = S3CP.connect()
|
92
97
|
|
93
|
-
paths.each do |path|
|
94
|
-
|
95
|
-
|
98
|
+
paths.each do |path|
|
99
|
+
bucket,key = S3CP.bucket_and_key(path)
|
100
|
+
fail "Invalid bucket/key: #{path}" unless key
|
96
101
|
|
97
|
-
|
98
|
-
|
102
|
+
S3CP.objects_by_wildcard(@s3.buckets[bucket], key) { | obj |
|
103
|
+
puts "s3://#{bucket}/#{obj.key}"
|
99
104
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
105
|
+
if options[:headers].size > 0
|
106
|
+
current_medata = obj.metadata
|
107
|
+
object_metadata = S3CP.set_header_options(current_medata, S3CP.headers_array_to_hash(options[:headers]))
|
108
|
+
end
|
104
109
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
+
if options[:acl]
|
111
|
+
obj.acl = options[:acl]
|
112
|
+
end
|
113
|
+
}
|
114
|
+
end
|
115
|
+
rescue => e
|
116
|
+
$stderr.print "s3mod: [#{e.class}] #{e.message}\n"
|
117
|
+
if options[:debug]
|
118
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
119
|
+
end
|
120
|
+
end
|
data/lib/s3cp/s3rm.rb
CHANGED
@@ -58,6 +58,10 @@ op = OptionParser.new do |opts|
|
|
58
58
|
options[:verbose] = true
|
59
59
|
end
|
60
60
|
|
61
|
+
opts.on("--debug", "Debug mode") do
|
62
|
+
options[:debug] = true
|
63
|
+
end
|
64
|
+
|
61
65
|
opts.on_tail("-h", "--help", "Show this message") do
|
62
66
|
puts op
|
63
67
|
exit
|
@@ -98,47 +102,55 @@ end
|
|
98
102
|
include_regex = options[:include_regex] ? Regexp.new(options[:include_regex]) : nil
|
99
103
|
exclude_regex = options[:exclude_regex] ? Regexp.new(options[:exclude_regex]) : nil
|
100
104
|
|
101
|
-
|
105
|
+
begin
|
106
|
+
S3CP.load_config()
|
102
107
|
|
103
|
-
@s3 = S3CP.connect()
|
108
|
+
@s3 = S3CP.connect()
|
104
109
|
|
105
|
-
if options[:recursive]
|
106
|
-
|
110
|
+
if options[:recursive]
|
111
|
+
matching_keys = []
|
107
112
|
|
108
|
-
|
109
|
-
|
113
|
+
@s3.buckets[@bucket].objects.with_prefix(@key).each do |entry|
|
114
|
+
key = "s3://#{@bucket}/#{entry.key}"
|
110
115
|
|
111
|
-
|
112
|
-
|
113
|
-
|
116
|
+
matching = true
|
117
|
+
matching = false if include_regex && !include_regex.match(entry.key)
|
118
|
+
matching = false if exclude_regex && exclude_regex.match(entry.key)
|
114
119
|
|
115
|
-
|
120
|
+
puts "#{key} => #{matching}" if options[:verbose]
|
116
121
|
|
117
|
-
|
118
|
-
|
119
|
-
|
122
|
+
if matching
|
123
|
+
matching_keys << entry.key
|
124
|
+
puts key unless options[:silent] || options[:verbose]
|
125
|
+
end
|
120
126
|
end
|
121
|
-
end
|
122
127
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
128
|
+
if options[:fail_if_not_exist] && matching_keys.length == 0
|
129
|
+
puts "No matching keys."
|
130
|
+
exit(1)
|
131
|
+
end
|
127
132
|
|
128
|
-
|
129
|
-
|
130
|
-
else
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
133
|
+
# if any of the objects failed to delete, a BatchDeleteError will be raised with a summary of the errors
|
134
|
+
@s3.buckets[@bucket].objects.delete(matching_keys) unless options[:test]
|
135
|
+
else
|
136
|
+
# delete a single file; check if it exists
|
137
|
+
if options[:fail_if_not_exist] && !@s3.buckets[@bucket].objects[@key].exist?
|
138
|
+
key = "s3://#{@bucket}/#{@key}"
|
139
|
+
puts "#{key} does not exist."
|
140
|
+
exit(1)
|
141
|
+
end
|
137
142
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
+
begin
|
144
|
+
@s3.buckets[@bucket].objects[@key].delete() unless options[:test]
|
145
|
+
rescue => e
|
146
|
+
puts e.to_s
|
147
|
+
raise e unless e.is_a? AWS::S3::Errors::NoSuchKey
|
148
|
+
end
|
149
|
+
end
|
150
|
+
rescue => e
|
151
|
+
$stderr.print "s3rm: [#{e.class}] #{e.message}\n"
|
152
|
+
if options[:debug]
|
153
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
143
154
|
end
|
144
155
|
end
|
156
|
+
|
data/lib/s3cp/s3tree.rb
CHANGED
@@ -47,6 +47,10 @@ op = OptionParser.new do |opts|
|
|
47
47
|
options[:delimiter] = delimiter
|
48
48
|
end
|
49
49
|
|
50
|
+
opts.on("--debug", "Debug mode") do
|
51
|
+
options[:debug] = true
|
52
|
+
end
|
53
|
+
|
50
54
|
opts.on_tail("-h", "--help", "Show this message") do
|
51
55
|
puts op
|
52
56
|
exit
|
@@ -158,5 +162,10 @@ begin
|
|
158
162
|
display_tree.call(prefix, root.children, depth)
|
159
163
|
rescue Errno::EPIPE
|
160
164
|
# ignore
|
165
|
+
rescue => e
|
166
|
+
$stderr.print "s3tree: [#{e.class}] #{e.message}\n"
|
167
|
+
if options[:debug]
|
168
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
169
|
+
end
|
161
170
|
end
|
162
171
|
|
data/lib/s3cp/s3up.rb
CHANGED
@@ -41,6 +41,10 @@ op = OptionParser.new do |opts|
|
|
41
41
|
options[:acl] = S3CP.validate_acl(permission)
|
42
42
|
end
|
43
43
|
|
44
|
+
opts.on("--debug", "Debug mode") do
|
45
|
+
options[:debug] = true
|
46
|
+
end
|
47
|
+
|
44
48
|
opts.separator " e.g.,"
|
45
49
|
opts.separator " HTTP headers: \'Content-Type: image/jpg\'"
|
46
50
|
opts.separator " AMZ headers: \'x-amz-acl: public-read\'"
|
@@ -65,33 +69,41 @@ url = ARGV[0]
|
|
65
69
|
bucket, key = S3CP.bucket_and_key(url)
|
66
70
|
fail "Your URL looks funny, doesn't it?" unless bucket
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
72
|
+
begin
|
73
|
+
S3CP.load_config()
|
74
|
+
|
75
|
+
@s3 = S3CP.connect()
|
76
|
+
|
77
|
+
# copy all of STDIN to a temp file
|
78
|
+
temp = Tempfile.new('s3cp')
|
79
|
+
while true
|
80
|
+
begin
|
81
|
+
data = STDIN.sysread(4 * 1024)
|
82
|
+
temp.syswrite(data)
|
83
|
+
rescue EOFError => e
|
84
|
+
break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
temp.close
|
88
|
+
temp.open
|
71
89
|
|
72
|
-
#
|
73
|
-
temp = Tempfile.new('s3cp')
|
74
|
-
while true
|
90
|
+
# upload temp file
|
75
91
|
begin
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
92
|
+
s3_options = {}
|
93
|
+
S3CP.set_header_options(s3_options, @headers)
|
94
|
+
s3_options[:acl] = options[:acl]
|
95
|
+
@s3.buckets[bucket].objects[key].write(temp, s3_options)
|
96
|
+
STDERR.puts "s3://#{bucket}/#{key} => #{S3CP.format_filesize(temp.size)} "
|
97
|
+
ensure
|
98
|
+
# cleanup
|
99
|
+
temp.close
|
100
|
+
temp.delete
|
101
|
+
end
|
102
|
+
rescue => e
|
103
|
+
$stderr.print "s3up: [#{e.class}] #{e.message}\n"
|
104
|
+
if options[:debug]
|
105
|
+
$stderr.print e.backtrace.join("\n") + "\n"
|
80
106
|
end
|
81
107
|
end
|
82
|
-
temp.close
|
83
|
-
temp.open
|
84
108
|
|
85
|
-
# upload temp file
|
86
|
-
begin
|
87
|
-
s3_options = {}
|
88
|
-
S3CP.set_header_options(s3_options, @headers)
|
89
|
-
s3_options[:acl] = options[:acl]
|
90
|
-
@s3.buckets[bucket].objects[key].write(temp, s3_options)
|
91
|
-
STDERR.puts "s3://#{bucket}/#{key} => #{S3CP.format_filesize(temp.size)} "
|
92
|
-
ensure
|
93
|
-
# cleanup
|
94
|
-
temp.close
|
95
|
-
temp.delete
|
96
|
-
end
|
97
109
|
|
data/lib/s3cp/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: s3cp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 35
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 1.1.
|
9
|
+
- 24
|
10
|
+
version: 1.1.24
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Boisvert
|