nexus_cli 1.0.2 → 2.0.0
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/.gitignore +4 -1
- data/.travis.yml +5 -0
- data/Gemfile +40 -0
- data/Guardfile +27 -0
- data/README.md +20 -23
- data/Thorfile +66 -0
- data/VERSION +1 -1
- data/{pro → features/pro}/nexus_custom_metadata.feature +0 -0
- data/{pro → features/pro}/nexus_pro.feature +0 -0
- data/features/support/env.rb +41 -32
- data/lib/nexus_cli.rb +26 -10
- data/lib/nexus_cli/base_remote.rb +32 -0
- data/lib/nexus_cli/configuration.rb +24 -5
- data/lib/nexus_cli/connection.rb +81 -0
- data/lib/nexus_cli/mixins/artifact_actions.rb +186 -0
- data/lib/nexus_cli/mixins/global_settings_actions.rb +64 -0
- data/lib/nexus_cli/mixins/logging_actions.rb +45 -0
- data/lib/nexus_cli/{nexus_pro_remote.rb → mixins/pro/custom_metadata_actions.rb} +5 -199
- data/lib/nexus_cli/mixins/pro/smart_proxy_actions.rb +214 -0
- data/lib/nexus_cli/mixins/repository_actions.rb +245 -0
- data/lib/nexus_cli/mixins/user_actions.rb +125 -0
- data/lib/nexus_cli/remote/oss_remote.rb +11 -0
- data/lib/nexus_cli/remote/pro_remote.rb +59 -0
- data/lib/nexus_cli/remote_factory.rb +24 -0
- data/lib/nexus_cli/tasks.rb +3 -3
- data/spec/fixtures/nexus.config +4 -0
- data/spec/spec_helper.rb +14 -2
- data/spec/{configuration_spec.rb → unit/nexus_cli/configuration_spec.rb} +0 -0
- data/spec/{oss_remote_spec.rb → unit/nexus_cli/oss_remote_spec.rb} +15 -5
- data/spec/{pro_remote_spec.rb → unit/nexus_cli/pro_remote_spec.rb} +6 -1
- metadata +32 -17
- data/Gemfile.lock +0 -65
- data/lib/nexus_cli/kernel.rb +0 -33
- data/lib/nexus_cli/nexus_oss_remote.rb +0 -636
- data/lib/nexus_cli/nexus_remote_factory.rb +0 -65
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'jsonpath'
|
3
|
+
|
4
|
+
module NexusCli
|
5
|
+
# @author Kyle Allan <kallan@riotgames.com>
|
6
|
+
module UserActions
|
7
|
+
|
8
|
+
|
9
|
+
# Gets information about the current Nexus users.
|
10
|
+
#
|
11
|
+
# @return [String] a String of XML with data about Nexus users
|
12
|
+
def get_users
|
13
|
+
response = nexus.get(nexus_url("service/local/users"))
|
14
|
+
case response.status
|
15
|
+
when 200
|
16
|
+
return response.content
|
17
|
+
else
|
18
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a User.
|
23
|
+
#
|
24
|
+
# @param params [Hash] a Hash of parameters to use during user creation
|
25
|
+
#
|
26
|
+
# @return [Boolean] true if the user is created, false otherwise
|
27
|
+
def create_user(params)
|
28
|
+
response = nexus.post(nexus_url("service/local/users"), :body => create_user_json(params), :header => DEFAULT_CONTENT_TYPE_HEADER)
|
29
|
+
case response.status
|
30
|
+
when 201
|
31
|
+
return true
|
32
|
+
when 400
|
33
|
+
raise CreateUserException.new(response.content)
|
34
|
+
else
|
35
|
+
raise UnexpectedStatusCodeException.new(reponse.code)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Updates a user by changing parts of that user's data.
|
40
|
+
#
|
41
|
+
# @param params [Hash] a Hash of parameters to update
|
42
|
+
#
|
43
|
+
# @return [Boolean] true if the user is updated, false otherwise
|
44
|
+
def update_user(params)
|
45
|
+
params[:roles] = [] if params[:roles] == [""]
|
46
|
+
user_json = get_user(params[:userId])
|
47
|
+
|
48
|
+
modified_json = JsonPath.for(user_json)
|
49
|
+
params.each do |key, value|
|
50
|
+
modified_json.gsub!("$..#{key}"){|v| value} unless key == "userId" || value.blank?
|
51
|
+
end
|
52
|
+
|
53
|
+
response = nexus.put(nexus_url("service/local/users/#{params[:userId]}"), :body => JSON.dump(modified_json.to_hash), :header => DEFAULT_CONTENT_TYPE_HEADER)
|
54
|
+
case response.status
|
55
|
+
when 200
|
56
|
+
return true
|
57
|
+
when 400
|
58
|
+
raise UpdateUserException.new(response.content)
|
59
|
+
else
|
60
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Gets a user
|
65
|
+
#
|
66
|
+
# @param user [String] the name of the user to get
|
67
|
+
#
|
68
|
+
# @return [Hash] a parsed Ruby object representing the user's JSON
|
69
|
+
def get_user(user)
|
70
|
+
response = nexus.get(nexus_url("service/local/users/#{user}"), :header => DEFAULT_ACCEPT_HEADER)
|
71
|
+
case response.status
|
72
|
+
when 200
|
73
|
+
return JSON.parse(response.content)
|
74
|
+
when 404
|
75
|
+
raise UserNotFoundException.new(user)
|
76
|
+
else
|
77
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Changes the password of a user
|
82
|
+
#
|
83
|
+
# @param params [Hash] a hash given to update the users password
|
84
|
+
#
|
85
|
+
# @return [type] [description]
|
86
|
+
def change_password(params)
|
87
|
+
response = nexus.post(nexus_url("service/local/users_changepw"), :body => create_change_password_json(params), :header => DEFAULT_CONTENT_TYPE_HEADER)
|
88
|
+
case response.status
|
89
|
+
when 202
|
90
|
+
return true
|
91
|
+
when 400
|
92
|
+
raise InvalidCredentialsException
|
93
|
+
else
|
94
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Deletes the Nexus user with the given id.
|
99
|
+
#
|
100
|
+
# @param user_id [String] the Nexus user to delete
|
101
|
+
#
|
102
|
+
# @return [Boolean] true if the user is deleted, false otherwise
|
103
|
+
def delete_user(user_id)
|
104
|
+
response = nexus.delete(nexus_url("service/local/users/#{user_id}"))
|
105
|
+
case response.status
|
106
|
+
when 204
|
107
|
+
return true
|
108
|
+
when 404
|
109
|
+
raise UserNotFoundException.new(user_id)
|
110
|
+
else
|
111
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def create_user_json(params)
|
118
|
+
JSON.dump(:data => params)
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_change_password_json(params)
|
122
|
+
JSON.dump(:data => params)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module NexusCli
|
2
|
+
class ProRemote < BaseRemote
|
3
|
+
|
4
|
+
include ArtifactActions
|
5
|
+
include CustomMetadataActions
|
6
|
+
include GlobalSettingsActions
|
7
|
+
include LoggingActions
|
8
|
+
include RepositoryActions
|
9
|
+
include SmartProxyActions
|
10
|
+
include UserActions
|
11
|
+
|
12
|
+
def get_license_info
|
13
|
+
response = nexus.get(nexus_url("service/local/licensing"), :header => DEFAULT_ACCEPT_HEADER)
|
14
|
+
case response.status
|
15
|
+
when 200
|
16
|
+
return response.content
|
17
|
+
else
|
18
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def install_license(license_file)
|
23
|
+
file = File.read(File.expand_path(license_file))
|
24
|
+
response = nexus.post(nexus_url("service/local/licensing/upload"), :body => file, :header => {"Content-Type" => "application/octet-stream"})
|
25
|
+
case response.status
|
26
|
+
when 201
|
27
|
+
return true
|
28
|
+
when 403
|
29
|
+
raise LicenseInstallFailure
|
30
|
+
else
|
31
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def install_license_bytes(bytes)
|
36
|
+
response = nexus.post(nexus_url("service/local/licensing/upload"), :body => bytes, :header => {"Content-Type" => "application/octet-stream"})
|
37
|
+
case response.status
|
38
|
+
when 201
|
39
|
+
return true
|
40
|
+
when 403
|
41
|
+
raise LicenseInstallFailure
|
42
|
+
else
|
43
|
+
raise UnexpectedStatusCodeException.new(response.status)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def transfer_artifact(artifact, from_repository, to_repository)
|
48
|
+
do_transfer_artifact(artifact, from_repository, to_repository)
|
49
|
+
|
50
|
+
configuration["repository"] = sanitize_for_id(from_repository)
|
51
|
+
from_artifact_metadata = get_custom_metadata_hash(artifact)
|
52
|
+
|
53
|
+
configuration["repository"] = sanitize_for_id(to_repository)
|
54
|
+
to_artifact_metadata = get_custom_metadata_hash(artifact)
|
55
|
+
|
56
|
+
do_update_custom_metadata(artifact, from_artifact_metadata, to_artifact_metadata)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module NexusCli
|
6
|
+
class RemoteFactory
|
7
|
+
class << self
|
8
|
+
attr_reader :configuration
|
9
|
+
attr_reader :connection
|
10
|
+
|
11
|
+
def create(overrides, ssl_verify=true)
|
12
|
+
@configuration = Configuration::parse(overrides)
|
13
|
+
@connection = Connection.new(configuration, ssl_verify)
|
14
|
+
running_nexus_pro? ? ProRemote.new(overrides, ssl_verify) : OSSRemote.new(overrides, ssl_verify)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def running_nexus_pro?
|
20
|
+
return connection.status['edition_long'] == "Professional" ? true : false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/nexus_cli/tasks.rb
CHANGED
@@ -33,8 +33,8 @@ module NexusCli
|
|
33
33
|
:desc => "A different folder other than the current working directory."
|
34
34
|
desc "pull_artifact artifact", "Pulls an artifact from Nexus and places it on your machine."
|
35
35
|
def pull_artifact(artifact)
|
36
|
-
|
37
|
-
say "Artifact has been retrived and can be found at path: #{
|
36
|
+
pull_artifact_response = nexus_remote.pull_artifact(artifact, options[:destination])
|
37
|
+
say "Artifact has been retrived and can be found at path: #{pull_artifact_response[:file_path]}", :green
|
38
38
|
end
|
39
39
|
|
40
40
|
desc "push_artifact artifact file", "Pushes an artifact from your machine onto the Nexus."
|
@@ -437,7 +437,7 @@ module NexusCli
|
|
437
437
|
|
438
438
|
def nexus_remote
|
439
439
|
begin
|
440
|
-
nexus_remote ||=
|
440
|
+
nexus_remote ||= RemoteFactory.create(options[:overrides], options[:ssl_verify])
|
441
441
|
rescue NexusCliError => e
|
442
442
|
say e.message, :red
|
443
443
|
exit e.status_code
|
data/spec/spec_helper.rb
CHANGED
@@ -1,2 +1,14 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'spork'
|
4
|
+
|
5
|
+
Spork.prefork do
|
6
|
+
require 'webmock/rspec'
|
7
|
+
|
8
|
+
APP_ROOT = File.expand_path('../../', __FILE__)
|
9
|
+
ENV["NEXUS_CONFIG"] = File.join(APP_ROOT, "spec", "fixtures", "nexus.config")
|
10
|
+
end
|
11
|
+
|
12
|
+
Spork.each_run do
|
13
|
+
require 'nexus_cli'
|
14
|
+
end
|
File without changes
|
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
remote = NexusCli::OSSRemote.new(
|
3
|
+
remote = NexusCli::OSSRemote.new(
|
4
|
+
'url' => 'http://localhost:8081/nexus',
|
5
|
+
'repository' => 'releases',
|
6
|
+
'username' => 'admin',
|
7
|
+
'password' => 'admin123'
|
8
|
+
)
|
4
9
|
|
5
10
|
describe NexusCli do
|
6
11
|
it "gives you errors when you attempt to pull an artifact don't give a valid artifact name" do
|
@@ -23,12 +28,17 @@ describe NexusCli do
|
|
23
28
|
|
24
29
|
it "gives you an error when you try to update a user that doesnt exist" do
|
25
30
|
stub_request(:get, "http://admin:admin123@localhost:8081/nexus/service/local/users/qwertyasdf").
|
26
|
-
|
27
|
-
|
28
|
-
|
31
|
+
with(:headers => {
|
32
|
+
'Accept' => 'application/json',
|
33
|
+
'Authorization' => 'Basic YWRtaW46YWRtaW4xMjM='
|
34
|
+
}).to_return(:status => 404, :body => "", :headers => {})
|
35
|
+
|
36
|
+
expect {
|
37
|
+
remote.update_user(:userId => "qwertyasdf")
|
38
|
+
}.to raise_error(NexusCli::UserNotFoundException)
|
29
39
|
end
|
30
40
|
|
31
41
|
it "gives you an error when you try to set the logging level to something weird" do
|
32
42
|
expect {remote.set_logger_level("weird")}.to raise_error(NexusCli::InvalidLoggingLevelException)
|
33
43
|
end
|
34
|
-
end
|
44
|
+
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'nexus_cli'
|
2
2
|
|
3
|
-
remote = NexusCli::ProRemote.new(
|
3
|
+
remote = NexusCli::ProRemote.new(
|
4
|
+
'url' => 'http://localhost:8081/nexus',
|
5
|
+
'repository' => 'releases',
|
6
|
+
'username' => 'admin',
|
7
|
+
'password' => 'admin123'
|
8
|
+
)
|
4
9
|
|
5
10
|
describe NexusCli do
|
6
11
|
it "gives you errors when you attempt to get an artifact's custom info and don't give a valid artifact name" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexus_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
@@ -212,35 +212,46 @@ extensions: []
|
|
212
212
|
extra_rdoc_files: []
|
213
213
|
files:
|
214
214
|
- .gitignore
|
215
|
+
- .travis.yml
|
215
216
|
- Gemfile
|
216
|
-
-
|
217
|
+
- Guardfile
|
217
218
|
- LICENSE
|
218
219
|
- README.md
|
219
220
|
- Rakefile
|
221
|
+
- Thorfile
|
220
222
|
- VERSION
|
221
223
|
- bin/nexus-cli
|
222
224
|
- data/pom.xml.erb
|
223
225
|
- features/nexus_oss.feature
|
226
|
+
- features/pro/nexus_custom_metadata.feature
|
227
|
+
- features/pro/nexus_pro.feature
|
224
228
|
- features/step_definitions/cli_steps.rb
|
225
229
|
- features/support/env.rb
|
226
230
|
- lib/nexus_cli.rb
|
231
|
+
- lib/nexus_cli/base_remote.rb
|
227
232
|
- lib/nexus_cli/cli.rb
|
228
233
|
- lib/nexus_cli/configuration.rb
|
234
|
+
- lib/nexus_cli/connection.rb
|
229
235
|
- lib/nexus_cli/errors.rb
|
230
|
-
- lib/nexus_cli/
|
236
|
+
- lib/nexus_cli/mixins/artifact_actions.rb
|
237
|
+
- lib/nexus_cli/mixins/global_settings_actions.rb
|
238
|
+
- lib/nexus_cli/mixins/logging_actions.rb
|
239
|
+
- lib/nexus_cli/mixins/pro/custom_metadata_actions.rb
|
240
|
+
- lib/nexus_cli/mixins/pro/smart_proxy_actions.rb
|
241
|
+
- lib/nexus_cli/mixins/repository_actions.rb
|
242
|
+
- lib/nexus_cli/mixins/user_actions.rb
|
231
243
|
- lib/nexus_cli/n3_metadata.rb
|
232
|
-
- lib/nexus_cli/
|
233
|
-
- lib/nexus_cli/
|
234
|
-
- lib/nexus_cli/
|
244
|
+
- lib/nexus_cli/remote/oss_remote.rb
|
245
|
+
- lib/nexus_cli/remote/pro_remote.rb
|
246
|
+
- lib/nexus_cli/remote_factory.rb
|
235
247
|
- lib/nexus_cli/tasks.rb
|
236
248
|
- lib/nexus_cli/version.rb
|
237
249
|
- nexus_cli.gemspec
|
238
|
-
-
|
239
|
-
- pro/nexus_pro.feature
|
240
|
-
- spec/configuration_spec.rb
|
241
|
-
- spec/oss_remote_spec.rb
|
242
|
-
- spec/pro_remote_spec.rb
|
250
|
+
- spec/fixtures/nexus.config
|
243
251
|
- spec/spec_helper.rb
|
252
|
+
- spec/unit/nexus_cli/configuration_spec.rb
|
253
|
+
- spec/unit/nexus_cli/oss_remote_spec.rb
|
254
|
+
- spec/unit/nexus_cli/pro_remote_spec.rb
|
244
255
|
homepage: https://github.com/RiotGames/nexus_cli
|
245
256
|
licenses: []
|
246
257
|
post_install_message:
|
@@ -255,7 +266,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
255
266
|
version: '0'
|
256
267
|
segments:
|
257
268
|
- 0
|
258
|
-
hash: -
|
269
|
+
hash: -2641463560755439892
|
259
270
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
260
271
|
none: false
|
261
272
|
requirements:
|
@@ -264,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
264
275
|
version: '0'
|
265
276
|
segments:
|
266
277
|
- 0
|
267
|
-
hash: -
|
278
|
+
hash: -2641463560755439892
|
268
279
|
requirements: []
|
269
280
|
rubyforge_project:
|
270
281
|
rubygems_version: 1.8.21
|
@@ -273,9 +284,13 @@ specification_version: 3
|
|
273
284
|
summary: A command-line wrapper for making REST calls to Sonatype Nexus.
|
274
285
|
test_files:
|
275
286
|
- features/nexus_oss.feature
|
287
|
+
- features/pro/nexus_custom_metadata.feature
|
288
|
+
- features/pro/nexus_pro.feature
|
276
289
|
- features/step_definitions/cli_steps.rb
|
277
290
|
- features/support/env.rb
|
278
|
-
- spec/
|
279
|
-
- spec/oss_remote_spec.rb
|
280
|
-
- spec/pro_remote_spec.rb
|
291
|
+
- spec/fixtures/nexus.config
|
281
292
|
- spec/spec_helper.rb
|
293
|
+
- spec/unit/nexus_cli/configuration_spec.rb
|
294
|
+
- spec/unit/nexus_cli/oss_remote_spec.rb
|
295
|
+
- spec/unit/nexus_cli/pro_remote_spec.rb
|
296
|
+
has_rdoc:
|
data/Gemfile.lock
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
nexus_cli (1.0.2)
|
5
|
-
extlib
|
6
|
-
highline
|
7
|
-
httpclient (= 2.2.5)
|
8
|
-
json
|
9
|
-
jsonpath
|
10
|
-
nokogiri
|
11
|
-
thor
|
12
|
-
|
13
|
-
GEM
|
14
|
-
specs:
|
15
|
-
addressable (2.3.2)
|
16
|
-
aruba (0.4.11)
|
17
|
-
childprocess (>= 0.2.3)
|
18
|
-
cucumber (>= 1.1.1)
|
19
|
-
ffi (>= 1.0.11)
|
20
|
-
rspec (>= 2.7.0)
|
21
|
-
builder (3.0.4)
|
22
|
-
childprocess (0.3.6)
|
23
|
-
ffi (~> 1.0, >= 1.0.6)
|
24
|
-
crack (0.3.1)
|
25
|
-
cucumber (1.2.1)
|
26
|
-
builder (>= 2.1.2)
|
27
|
-
diff-lcs (>= 1.1.3)
|
28
|
-
gherkin (~> 2.11.0)
|
29
|
-
json (>= 1.4.6)
|
30
|
-
diff-lcs (1.1.3)
|
31
|
-
extlib (0.9.15)
|
32
|
-
ffi (1.1.5)
|
33
|
-
gherkin (2.11.5)
|
34
|
-
json (>= 1.4.6)
|
35
|
-
highline (1.6.15)
|
36
|
-
httpclient (2.2.5)
|
37
|
-
json (1.7.5)
|
38
|
-
jsonpath (0.5.0)
|
39
|
-
multi_json
|
40
|
-
multi_json (1.3.6)
|
41
|
-
nokogiri (1.5.5)
|
42
|
-
rake (0.9.2.2)
|
43
|
-
rspec (2.11.0)
|
44
|
-
rspec-core (~> 2.11.0)
|
45
|
-
rspec-expectations (~> 2.11.0)
|
46
|
-
rspec-mocks (~> 2.11.0)
|
47
|
-
rspec-core (2.11.1)
|
48
|
-
rspec-expectations (2.11.3)
|
49
|
-
diff-lcs (~> 1.1.3)
|
50
|
-
rspec-mocks (2.11.3)
|
51
|
-
thor (0.16.0)
|
52
|
-
webmock (1.8.10)
|
53
|
-
addressable (>= 2.2.7)
|
54
|
-
crack (>= 0.1.7)
|
55
|
-
|
56
|
-
PLATFORMS
|
57
|
-
ruby
|
58
|
-
|
59
|
-
DEPENDENCIES
|
60
|
-
aruba
|
61
|
-
cucumber
|
62
|
-
nexus_cli!
|
63
|
-
rake
|
64
|
-
rspec
|
65
|
-
webmock
|