roku_builder 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/Gemfile.lock +1 -1
- data/config.json.example +1 -0
- data/lib/roku_builder/config_parser.rb +12 -4
- data/lib/roku_builder/controller.rb +7 -3
- data/lib/roku_builder/controller_commands.rb +8 -7
- data/lib/roku_builder/loader.rb +35 -26
- data/lib/roku_builder/manifest_manager.rb +11 -2
- data/lib/roku_builder/tester.rb +7 -5
- data/lib/roku_builder/version.rb +1 -1
- data/lib/roku_builder.rb +3 -0
- data/roku_builder.gemspec +1 -1
- data/tests/roku_builder/controller_commands_test.rb +4 -4
- data/tests/roku_builder/controller_test.rb +1 -2
- data/tests/roku_builder/loader_test.rb +24 -10
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 878733ac2055fcbfa7fb162abf1f36a3d8e200c7
|
4
|
+
data.tar.gz: ee4a6a97eb48e0befd1e9dd5ec8e542396411f9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb15a8cdf99cdf04716c9ca95c8b531a492f1eff3632f7a663d0e061f34dcc5e5951b10d182bca3fe0cec130201010f0dc32f7a8fbbbb13890666ba9c3918fbb
|
7
|
+
data.tar.gz: 5894aeed3e103c46480cce8061b3b2fbd1a469c3f2391d6e72420a6d4f8531485d4ef15692b158959a30fd3529ecf8479129e558d4a0f03700e09ae26feb0d17
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
data/config.json.example
CHANGED
@@ -140,16 +140,24 @@ module RokuBuilder
|
|
140
140
|
# @param branch [String] the branch to sideload
|
141
141
|
def self.setup_sideload_config(configs:, options:)
|
142
142
|
root_dir = configs[:project_config][:directory]
|
143
|
+
content = {
|
144
|
+
folders: configs[:project_config][:folders],
|
145
|
+
files: configs[:project_config][:files],
|
146
|
+
}
|
147
|
+
all_commands = options.keys & Controller.commands
|
148
|
+
if Controller.exclude_commands.include?(all_commands.first)
|
149
|
+
content[:excludes] = configs[:project_config][:excludes]
|
150
|
+
end
|
151
|
+
|
143
152
|
# Create Sideload Config
|
144
153
|
configs[:sideload_config] = {
|
145
154
|
update_manifest: options[:update_manifest],
|
146
|
-
|
147
|
-
|
155
|
+
infile: options[:in],
|
156
|
+
content: content
|
148
157
|
}
|
149
158
|
# Create Build Config
|
150
159
|
configs[:build_config] = {
|
151
|
-
|
152
|
-
files: configs[:project_config][:files]
|
160
|
+
content: content
|
153
161
|
}
|
154
162
|
configs[:init_params][:loader] = {
|
155
163
|
root_dir: root_dir
|
@@ -164,7 +164,6 @@ module RokuBuilder
|
|
164
164
|
:navigate, :text, :build, :monitor, :update, :screencapture, :key, :screen,
|
165
165
|
:screens]
|
166
166
|
end
|
167
|
-
private_class_method :commands
|
168
167
|
|
169
168
|
# List of depricated options
|
170
169
|
# @return [Hash] Hash of depricated options and the warning message for each
|
@@ -175,7 +174,7 @@ module RokuBuilder
|
|
175
174
|
# List of source options
|
176
175
|
# @return [Array<Symbol>] List of source symbols that can be used in the options hash
|
177
176
|
def self.sources
|
178
|
-
[:ref, :set_stage, :working, :current]
|
177
|
+
[:ref, :set_stage, :working, :current, :in]
|
179
178
|
end
|
180
179
|
|
181
180
|
# List of commands requiring a source option
|
@@ -183,7 +182,12 @@ module RokuBuilder
|
|
183
182
|
def self.source_commands
|
184
183
|
[:sideload, :package, :test, :build, :key]
|
185
184
|
end
|
186
|
-
|
185
|
+
|
186
|
+
# List of commands the activate the exclude files
|
187
|
+
# @return [Array<Symbol] List of commands the will activate the exclude files lists
|
188
|
+
def self.exclude_commands
|
189
|
+
[:build, :package]
|
190
|
+
end
|
187
191
|
|
188
192
|
|
189
193
|
# Configure the gem
|
@@ -39,15 +39,16 @@ module RokuBuilder
|
|
39
39
|
config = configs[:device_config].dup
|
40
40
|
config[:init_params] = configs[:init_params][:loader]
|
41
41
|
stager = Stager.new(**configs[:stage_config])
|
42
|
-
success =
|
42
|
+
success = nil
|
43
43
|
if stager.stage
|
44
44
|
loader = Loader.new(**config)
|
45
|
-
success = loader.sideload(**configs[:sideload_config])
|
45
|
+
success, version = loader.sideload(**configs[:sideload_config])
|
46
46
|
end
|
47
47
|
stager.unstage
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
unless success == FAILED_SIDELOAD
|
49
|
+
logger.info "App Sideloaded; staged using #{stager.method}"
|
50
|
+
end
|
51
|
+
success
|
51
52
|
end
|
52
53
|
# Run Package
|
53
54
|
# @param options [Hash] user options
|
@@ -65,8 +66,8 @@ module RokuBuilder
|
|
65
66
|
logger.warn "Packaging working directory" if options[:working]
|
66
67
|
if stager.stage
|
67
68
|
# Sideload #
|
68
|
-
build_version = loader.sideload(**configs[:sideload_config])
|
69
|
-
return
|
69
|
+
code, build_version = loader.sideload(**configs[:sideload_config])
|
70
|
+
return code unless code = SUCCESS
|
70
71
|
# Key #
|
71
72
|
success = keyer.rekey(**configs[:key])
|
72
73
|
logger.info "Key did not change" unless success
|
data/lib/roku_builder/loader.rb
CHANGED
@@ -11,19 +11,24 @@ module RokuBuilder
|
|
11
11
|
|
12
12
|
# Sideload an app onto a roku device
|
13
13
|
# @param root_dir [String] Path to the root directory of the roku app
|
14
|
-
# @param
|
15
|
-
# @param files [Array<String>] Array of files to be sideloaded. Pass nil to send all files. Default: nil
|
14
|
+
# @param content [Hash] Hash containing arrays for folder, files, and excludes. Default: nil
|
16
15
|
# @return [String] Build version on success, nil otherwise
|
17
|
-
def sideload(update_manifest: false,
|
18
|
-
result =
|
19
|
-
|
20
|
-
build_version =
|
21
|
-
if
|
22
|
-
build_version = ManifestManager.
|
16
|
+
def sideload(update_manifest: false, content: nil, infile: nil)
|
17
|
+
result = FAILED_SIDELOAD
|
18
|
+
outfile = nil
|
19
|
+
build_version = nil
|
20
|
+
if infile
|
21
|
+
build_version = ManifestManager.build_version(root_dir: infile)
|
22
|
+
outfile = infile
|
23
23
|
else
|
24
|
-
|
24
|
+
# Update manifest
|
25
|
+
if update_manifest
|
26
|
+
build_version = ManifestManager.update_build(root_dir: @root_dir)
|
27
|
+
else
|
28
|
+
build_version = ManifestManager.build_version(root_dir: @root_dir)
|
29
|
+
end
|
30
|
+
outfile = build(build_version: build_version, content: content)
|
25
31
|
end
|
26
|
-
outfile = build(build_version: build_version, folders: folders, files: files)
|
27
32
|
path = "/plugin_install"
|
28
33
|
# Connect to roku and upload file
|
29
34
|
conn = multipart_connection
|
@@ -33,9 +38,10 @@ module RokuBuilder
|
|
33
38
|
}
|
34
39
|
response = conn.post path, payload
|
35
40
|
# Cleanup
|
36
|
-
File.delete(outfile)
|
37
|
-
result =
|
38
|
-
|
41
|
+
File.delete(outfile) unless infile
|
42
|
+
result = SUCCESS if response.status==200 and response.body=~/Install Success/
|
43
|
+
result = IDENTICAL_SIDELOAD if response.status==200 and response.body=~/Identical to previous version/
|
44
|
+
[result, build_version]
|
39
45
|
end
|
40
46
|
|
41
47
|
|
@@ -43,30 +49,31 @@ module RokuBuilder
|
|
43
49
|
# @param root_dir [String] Path to the root directory of the roku app
|
44
50
|
# @param build_version [String] Version to assigne to the build. If nil will pull the build version form the manifest. Default: nil
|
45
51
|
# @param outfile [String] Path for the output file. If nil will create a file in /tmp. Default: nil
|
46
|
-
# @param
|
47
|
-
# @param files [Array<String>] Array of files to be sideloaded. Pass nil to send all files. Default: nil
|
52
|
+
# @param content [Hash] Hash containing arrays for folder, files, and excludes. Default: nil
|
48
53
|
# @return [String] Path of the build
|
49
|
-
def build(build_version: nil, outfile: nil,
|
54
|
+
def build(build_version: nil, outfile: nil, content: nil)
|
50
55
|
build_version = ManifestManager.build_version(root_dir: @root_dir) unless build_version
|
51
|
-
|
52
|
-
|
56
|
+
content ||= {}
|
57
|
+
unless content and content[:folders]
|
58
|
+
content[:folders] = Dir.entries(@root_dir).select {|entry| File.directory? File.join(@root_dir, entry) and !(entry =='.' || entry == '..') }
|
53
59
|
end
|
54
|
-
unless files
|
55
|
-
files = Dir.entries(@root_dir).select {|entry| File.file? File.join(@root_dir, entry)}
|
60
|
+
unless content and content[:files]
|
61
|
+
content[:files] = Dir.entries(@root_dir).select {|entry| File.file? File.join(@root_dir, entry)}
|
56
62
|
end
|
63
|
+
content[:excludes] = [] unless content and content[:excludes]
|
57
64
|
outfile = "/tmp/build_#{build_version}.zip" unless outfile
|
58
65
|
File.delete(outfile) if File.exist?(outfile)
|
59
66
|
io = Zip::File.open(outfile, Zip::File::CREATE)
|
60
67
|
# Add folders to zip
|
61
|
-
folders.each do |folder|
|
68
|
+
content[:folders].each do |folder|
|
62
69
|
base_folder = File.join(@root_dir, folder)
|
63
70
|
entries = Dir.entries(base_folder)
|
64
71
|
entries.delete(".")
|
65
72
|
entries.delete("..")
|
66
|
-
writeEntries(@root_dir, entries, folder, io)
|
73
|
+
writeEntries(@root_dir, entries, folder, content[:excludes], io)
|
67
74
|
end
|
68
75
|
# Add file to zip
|
69
|
-
writeEntries(@root_dir, files, "", io)
|
76
|
+
writeEntries(@root_dir, content[:files], "", content[:excludes], io)
|
70
77
|
io.close()
|
71
78
|
outfile
|
72
79
|
end
|
@@ -95,16 +102,18 @@ module RokuBuilder
|
|
95
102
|
# @param entries [Array<String>] Array of file paths of files/directories to store in the zip archive
|
96
103
|
# @param path [String] The path of the current directory starting at the root directory
|
97
104
|
# @param io [IO] zip IO object
|
98
|
-
def writeEntries(root_dir, entries, path, io)
|
105
|
+
def writeEntries(root_dir, entries, path, excludes, io)
|
99
106
|
entries.each { |e|
|
100
107
|
zipFilePath = path == "" ? e : File.join(path, e)
|
101
108
|
diskFilePath = File.join(root_dir, zipFilePath)
|
102
109
|
if File.directory?(diskFilePath)
|
103
110
|
io.mkdir(zipFilePath)
|
104
111
|
subdir =Dir.entries(diskFilePath); subdir.delete("."); subdir.delete("..")
|
105
|
-
writeEntries(root_dir, subdir, zipFilePath, io)
|
112
|
+
writeEntries(root_dir, subdir, zipFilePath, excludes, io)
|
106
113
|
else
|
107
|
-
|
114
|
+
unless excludes.include?(zipFilePath)
|
115
|
+
io.get_output_stream(zipFilePath) { |f| f.puts(File.open(diskFilePath, "rb").read()) }
|
116
|
+
end
|
108
117
|
end
|
109
118
|
}
|
110
119
|
end
|
@@ -47,14 +47,23 @@ module RokuBuilder
|
|
47
47
|
# @param root_dir [String] Path to the root directory for the app
|
48
48
|
# @return [String] Build version on success, empty string otherwise
|
49
49
|
def self.build_version(root_dir:)
|
50
|
-
path = File.join(root_dir, 'manifest')
|
51
50
|
build_version = ""
|
52
|
-
|
51
|
+
get_version = lambda { |file|
|
53
52
|
file.each_line do |line|
|
54
53
|
if line.include?("build_version")
|
55
54
|
build_version = line.split("=")[1].chomp
|
56
55
|
end
|
57
56
|
end
|
57
|
+
}
|
58
|
+
if File.directory?(root_dir)
|
59
|
+
path = File.join(root_dir, 'manifest')
|
60
|
+
build_version = ""
|
61
|
+
File.open(path, 'r', &get_version)
|
62
|
+
elsif File.extname(root_dir) == ".zip"
|
63
|
+
Zip::File.open(root_dir) do |zip_file|
|
64
|
+
entry = zip_file.glob("manifest").first
|
65
|
+
entry.get_input_stream(&get_version)
|
66
|
+
end
|
58
67
|
end
|
59
68
|
build_version
|
60
69
|
end
|
data/lib/roku_builder/tester.rb
CHANGED
@@ -21,13 +21,15 @@ module RokuBuilder
|
|
21
21
|
|
22
22
|
loader = Loader.new(**@device_config)
|
23
23
|
connection = Net::Telnet.new(telnet_config)
|
24
|
-
loader.sideload(**sideload_config)
|
24
|
+
code, _build_version = loader.sideload(**sideload_config)
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
if code = SUCCESS
|
27
|
+
in_tests = false
|
28
|
+
connection.waitfor(@end_reg) do |txt|
|
29
|
+
in_tests = handle_text(txt: txt, in_tests: in_tests)
|
30
|
+
end
|
31
|
+
connection.puts("cont\n")
|
29
32
|
end
|
30
|
-
connection.puts("cont\n")
|
31
33
|
end
|
32
34
|
|
33
35
|
private
|
data/lib/roku_builder/version.rb
CHANGED
data/lib/roku_builder.rb
CHANGED
data/roku_builder.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.required_ruby_version = "~> 2.2"
|
21
|
+
spec.required_ruby_version = "~> 2.2.3"
|
22
22
|
|
23
23
|
spec.add_dependency "rubyzip", "~> 1.2"
|
24
24
|
spec.add_dependency "faraday", "~> 0.9"
|
@@ -11,7 +11,7 @@ class ControllerCommandsTest < Minitest::Test
|
|
11
11
|
config = good_config
|
12
12
|
code, configs = RokuBuilder::ConfigParser.parse_config(options: options, config: config, logger: logger)
|
13
13
|
# Test Success
|
14
|
-
loader.expect(:sideload,
|
14
|
+
loader.expect(:sideload, [RokuBuilder::SUCCESS, "build_version"], [configs[:sideload_config]])
|
15
15
|
stager.expect(:stage, true)
|
16
16
|
stager.expect(:unstage, true)
|
17
17
|
stager.expect(:method, :git)
|
@@ -27,7 +27,7 @@ class ControllerCommandsTest < Minitest::Test
|
|
27
27
|
stager.expect(:unstage, true)
|
28
28
|
|
29
29
|
# Test Failure
|
30
|
-
loader.expect(:sideload,
|
30
|
+
loader.expect(:sideload, [RokuBuilder::FAILED_SIDELOAD, "build_version"], [configs[:sideload_config]])
|
31
31
|
RokuBuilder::Loader.stub(:new, loader) do
|
32
32
|
RokuBuilder::Stager.stub(:new, stager) do
|
33
33
|
code = RokuBuilder::Controller.send(:execute_commands, {options: options, config: config, configs: configs, logger: logger})
|
@@ -52,7 +52,7 @@ class ControllerCommandsTest < Minitest::Test
|
|
52
52
|
code, configs = RokuBuilder::ConfigParser.parse_config(options: options, config: config, logger: logger)
|
53
53
|
info = {app_name: "app", dev_id: "id", creation_date: "date", dev_zip: ""}
|
54
54
|
|
55
|
-
loader.expect(:sideload, "build_version", [configs[:sideload_config]])
|
55
|
+
loader.expect(:sideload, [RokuBuilder::SUCCESS, "build_version"], [configs[:sideload_config]])
|
56
56
|
keyer.expect(:rekey, true, [configs[:key]])
|
57
57
|
packager.expect(:package, true, [configs[:package_config]])
|
58
58
|
inspector.expect(:inspect, info, [configs[:inspect_config]])
|
@@ -94,7 +94,7 @@ class ControllerCommandsTest < Minitest::Test
|
|
94
94
|
code, configs = RokuBuilder::ConfigParser.parse_config(options: options, config: config, logger: logger)
|
95
95
|
info = {app_name: "app", dev_id: "id", creation_date: "date", dev_zip: ""}
|
96
96
|
|
97
|
-
loader.expect(:sideload, "build_version", [configs[:sideload_config]])
|
97
|
+
loader.expect(:sideload, [RokuBuilder::SUCCESS, "build_version"], [configs[:sideload_config]])
|
98
98
|
keyer.expect(:rekey, true, [configs[:key]])
|
99
99
|
packager.expect(:package, true, [configs[:package_config]])
|
100
100
|
inspector.expect(:inspect, info, [configs[:inspect_config]])
|
@@ -36,7 +36,6 @@ class ControllerTest < Minitest::Test
|
|
36
36
|
assert_equal RokuBuilder::DEPRICATED, RokuBuilder::Controller.send(:validate_options, {options: options})
|
37
37
|
options = {
|
38
38
|
sideload: true,
|
39
|
-
in: "",
|
40
39
|
current: true
|
41
40
|
}
|
42
41
|
assert_equal RokuBuilder::VALID, RokuBuilder::Controller.send(:validate_options, {options: options})
|
@@ -45,7 +44,7 @@ class ControllerTest < Minitest::Test
|
|
45
44
|
in: "",
|
46
45
|
set_stage: true
|
47
46
|
}
|
48
|
-
assert_equal RokuBuilder::
|
47
|
+
assert_equal RokuBuilder::EXTRA_SOURCES, RokuBuilder::Controller.send(:validate_options, {options: options})
|
49
48
|
end
|
50
49
|
def test_controller_configure
|
51
50
|
logger = Logger.new("/dev/null")
|
@@ -17,8 +17,10 @@ class LoaderTest < Minitest::Test
|
|
17
17
|
init_params: {root_dir: root_dir}
|
18
18
|
}
|
19
19
|
loader_config = {
|
20
|
-
|
21
|
-
|
20
|
+
content: {
|
21
|
+
folders: ["source"],
|
22
|
+
files: ["manifest"]
|
23
|
+
}
|
22
24
|
}
|
23
25
|
payload = {
|
24
26
|
mysubmit: "Replace",
|
@@ -38,22 +40,26 @@ class LoaderTest < Minitest::Test
|
|
38
40
|
end
|
39
41
|
response.expect(:status, 200)
|
40
42
|
response.expect(:body, "Install Success")
|
43
|
+
response.expect(:status, 200)
|
44
|
+
response.expect(:body, "Install Success")
|
41
45
|
|
42
46
|
loader = RokuBuilder::Loader.new(**device_config)
|
43
47
|
result = nil
|
48
|
+
build_version = nil
|
44
49
|
RokuBuilder::ManifestManager.stub(:build_version, "build_version") do
|
45
50
|
loader.stub(:build, "zip_file") do
|
46
51
|
Faraday.stub(:new, connection, faraday) do
|
47
52
|
Faraday::UploadIO.stub(:new, io) do
|
48
53
|
File.stub(:delete, nil) do
|
49
|
-
result = loader.sideload(**loader_config)
|
54
|
+
result, build_version = loader.sideload(**loader_config)
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
56
|
-
assert_equal "build_version",
|
61
|
+
assert_equal "build_version", build_version
|
62
|
+
assert_equal RokuBuilder::SUCCESS, result
|
57
63
|
|
58
64
|
connection.verify
|
59
65
|
faraday.verify
|
@@ -77,8 +83,10 @@ class LoaderTest < Minitest::Test
|
|
77
83
|
}
|
78
84
|
loader_config = {
|
79
85
|
update_manifest: true,
|
80
|
-
|
81
|
-
|
86
|
+
content: {
|
87
|
+
folders: ["source"],
|
88
|
+
files: ["manifest"]
|
89
|
+
}
|
82
90
|
}
|
83
91
|
payload = {
|
84
92
|
mysubmit: "Replace",
|
@@ -98,22 +106,26 @@ class LoaderTest < Minitest::Test
|
|
98
106
|
end
|
99
107
|
response.expect(:status, 200)
|
100
108
|
response.expect(:body, "Install Success")
|
109
|
+
response.expect(:status, 200)
|
110
|
+
response.expect(:body, "Install Success")
|
101
111
|
|
102
112
|
loader = RokuBuilder::Loader.new(**device_config)
|
103
113
|
result = nil
|
114
|
+
build_version = nil
|
104
115
|
RokuBuilder::ManifestManager.stub(:update_build, "build_version") do
|
105
116
|
loader.stub(:build, "zip_file") do
|
106
117
|
Faraday.stub(:new, connection, faraday) do
|
107
118
|
Faraday::UploadIO.stub(:new, io) do
|
108
119
|
File.stub(:delete, nil) do
|
109
|
-
result = loader.sideload(**loader_config)
|
120
|
+
result, build_version = loader.sideload(**loader_config)
|
110
121
|
end
|
111
122
|
end
|
112
123
|
end
|
113
124
|
end
|
114
125
|
end
|
115
126
|
|
116
|
-
assert_equal "build_version",
|
127
|
+
assert_equal "build_version", build_version
|
128
|
+
assert_equal RokuBuilder::SUCCESS, result
|
117
129
|
|
118
130
|
connection.verify
|
119
131
|
faraday.verify
|
@@ -132,8 +144,10 @@ class LoaderTest < Minitest::Test
|
|
132
144
|
init_params: {root_dir: root_dir}
|
133
145
|
}
|
134
146
|
build_config = {
|
135
|
-
|
136
|
-
|
147
|
+
content: {
|
148
|
+
folders: ["source"],
|
149
|
+
files: ["manifest"]
|
150
|
+
}
|
137
151
|
}
|
138
152
|
loader = RokuBuilder::Loader.new(**device_config)
|
139
153
|
outfile = nil
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roku_builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- greeneca
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -317,7 +317,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
317
317
|
requirements:
|
318
318
|
- - "~>"
|
319
319
|
- !ruby/object:Gem::Version
|
320
|
-
version:
|
320
|
+
version: 2.2.3
|
321
321
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
322
322
|
requirements:
|
323
323
|
- - ">="
|