s3cp 1.1.23 → 1.1.24
Sign up to get free protection for your applications and to get access to all the features.
- 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
|