awesome_spawn 1.2.1 → 1.3.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.
- checksums.yaml +7 -0
- data/lib/awesome_spawn.rb +19 -16
- data/lib/awesome_spawn/null_logger.rb +11 -0
- data/lib/awesome_spawn/version.rb +1 -1
- data/spec/awesome_spawn_spec.rb +52 -6
- data/spec/command_line_builder_spec.rb +17 -0
- data/spec/command_result_error_spec.rb +17 -0
- data/spec/command_result_spec.rb +28 -13
- data/spec/no_such_file_error_spec.rb +27 -0
- data/spec/spec_helper.rb +3 -3
- metadata +18 -23
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 495db35a5162aeca5013e1c52777938873956d4d
|
4
|
+
data.tar.gz: aaacd54a4c413f3f69bba0a52b52386041dc1e23
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4b491f9bdcf6a10961ed3b38e22a66f2906e639b6f0eee2340151bda4c1bba929c46765245d1b6ea38b206a328567bd4d834ce36f5d4de7aa6ec9845c428319e
|
7
|
+
data.tar.gz: c291f6d3861399c95343a9f787db4ac2ccf36415c7dda00934e4d8df44e8bd79fa45639b4d7e63eb88b3c1f5074042534b43aef8ad3051c2c0abff289ef01e0d
|
data/lib/awesome_spawn.rb
CHANGED
@@ -3,12 +3,19 @@ require "awesome_spawn/command_line_builder"
|
|
3
3
|
require "awesome_spawn/command_result"
|
4
4
|
require "awesome_spawn/command_result_error"
|
5
5
|
require "awesome_spawn/no_such_file_error"
|
6
|
+
require "awesome_spawn/null_logger"
|
6
7
|
|
7
8
|
require "open3"
|
8
9
|
|
9
10
|
module AwesomeSpawn
|
10
11
|
extend self
|
11
12
|
|
13
|
+
attr_writer :logger
|
14
|
+
|
15
|
+
def logger
|
16
|
+
@logger ||= NullLogger.new
|
17
|
+
end
|
18
|
+
|
12
19
|
# Execute `command` synchronously via Kernel.spawn and gather the output
|
13
20
|
# stream, error stream, and exit status in a {CommandResult}.
|
14
21
|
#
|
@@ -45,11 +52,10 @@ module AwesomeSpawn
|
|
45
52
|
#
|
46
53
|
# @param [String] command The command to run
|
47
54
|
# @param [Hash] options The options for running the command. Also accepts any
|
48
|
-
# option that can be passed to Kernel.spawn, except `:out` and `:err`.
|
55
|
+
# option that can be passed to Kernel.spawn, except `:in`, `:out` and `:err`.
|
49
56
|
# @option options [Hash,Array] :params The command line parameters. See
|
50
57
|
# {#build_command_line} for how to specify params.
|
51
|
-
# @option options [String] :in_data Data to be passed on stdin.
|
52
|
-
# is specified you cannot specify `:in`.
|
58
|
+
# @option options [String] :in_data Data to be passed on stdin.
|
53
59
|
#
|
54
60
|
# @raise [NoSuchFileError] if the `command` is not found
|
55
61
|
# @return [CommandResult] the output stream, error stream, and exit status
|
@@ -57,20 +63,17 @@ module AwesomeSpawn
|
|
57
63
|
def run(command, options = {})
|
58
64
|
raise ArgumentError, "options cannot contain :out" if options.include?(:out)
|
59
65
|
raise ArgumentError, "options cannot contain :err" if options.include?(:err)
|
60
|
-
raise ArgumentError, "options cannot contain :in
|
66
|
+
raise ArgumentError, "options cannot contain :in" if options.include?(:in)
|
61
67
|
options = options.dup
|
62
68
|
params = options.delete(:params)
|
63
|
-
in_data = options.delete(:in_data)
|
69
|
+
if (in_data = options.delete(:in_data))
|
70
|
+
options[:stdin_data] = in_data
|
71
|
+
end
|
64
72
|
|
65
73
|
output, error, status = "", "", nil
|
66
74
|
command_line = build_command_line(command, params)
|
67
75
|
|
68
|
-
|
69
|
-
output, error, status = launch(command_line, in_data, options)
|
70
|
-
ensure
|
71
|
-
output ||= ""
|
72
|
-
error ||= ""
|
73
|
-
end
|
76
|
+
output, error, status = launch(command_line, options)
|
74
77
|
rescue Errno::ENOENT => err
|
75
78
|
raise NoSuchFileError.new(err.message) if NoSuchFileError.detected?(err.message)
|
76
79
|
raise
|
@@ -92,8 +95,10 @@ module AwesomeSpawn
|
|
92
95
|
def run!(command, options = {})
|
93
96
|
command_result = run(command, options)
|
94
97
|
|
95
|
-
if command_result.
|
98
|
+
if command_result.failure?
|
96
99
|
message = "#{command} exit code: #{command_result.exit_status}"
|
100
|
+
logger.error("AwesomeSpawn: #{message}")
|
101
|
+
logger.error("AwesomeSpawn: #{command_result.error}")
|
97
102
|
raise CommandResultError.new(message, command_result)
|
98
103
|
end
|
99
104
|
|
@@ -107,10 +112,8 @@ module AwesomeSpawn
|
|
107
112
|
|
108
113
|
private
|
109
114
|
|
110
|
-
def launch(command,
|
111
|
-
spawn_options = spawn_options.merge(:stdin_data => in_data) if in_data
|
115
|
+
def launch(command, spawn_options = {})
|
112
116
|
output, error, status = Open3.capture3(command, spawn_options)
|
113
|
-
status
|
114
|
-
return output, error, status
|
117
|
+
return output || "", error || "", status && status.exitstatus
|
115
118
|
end
|
116
119
|
end
|
data/spec/awesome_spawn_spec.rb
CHANGED
@@ -6,24 +6,38 @@ describe AwesomeSpawn do
|
|
6
6
|
|
7
7
|
shared_examples_for "run" do
|
8
8
|
context "options" do
|
9
|
+
it "params won't be modified" do
|
10
|
+
params = {:params => {:user => "bob"}}
|
11
|
+
orig_params = params.dup
|
12
|
+
allow(subject).to receive(:launch).with("true --user bob", {}).and_return(["", "", 0])
|
13
|
+
subject.send(run_method, "true", params)
|
14
|
+
expect(orig_params).to eq(params)
|
15
|
+
end
|
16
|
+
|
9
17
|
it ":params won't be modified" do
|
10
18
|
params = {:user => "bob"}
|
11
19
|
orig_params = params.dup
|
12
|
-
subject.
|
20
|
+
allow(subject).to receive(:launch).with("true --user bob", {}).and_return(["", "", 0])
|
13
21
|
subject.send(run_method, "true", :params => params)
|
14
22
|
expect(orig_params).to eq(params)
|
15
23
|
end
|
16
24
|
|
17
|
-
it ":
|
18
|
-
expect
|
25
|
+
it ":in is not supported" do
|
26
|
+
expect do
|
27
|
+
subject.send(run_method, "true", :in => "/dev/null")
|
28
|
+
end.to raise_error(ArgumentError, "options cannot contain :in")
|
19
29
|
end
|
20
30
|
|
21
31
|
it ":out is not supported" do
|
22
|
-
expect
|
32
|
+
expect do
|
33
|
+
subject.send(run_method, "true", :out => "/dev/null")
|
34
|
+
end.to raise_error(ArgumentError, "options cannot contain :out")
|
23
35
|
end
|
24
36
|
|
25
37
|
it ":err is not supported" do
|
26
|
-
expect
|
38
|
+
expect do
|
39
|
+
subject.send(run_method, "true", :err => "/dev/null")
|
40
|
+
end.to raise_error(ArgumentError, "options cannot contain :err")
|
27
41
|
end
|
28
42
|
end
|
29
43
|
|
@@ -53,7 +67,9 @@ describe AwesomeSpawn do
|
|
53
67
|
end
|
54
68
|
|
55
69
|
it "command bad" do
|
56
|
-
expect
|
70
|
+
expect do
|
71
|
+
subject.send(run_method, "XXXXX --user=bob")
|
72
|
+
end.to raise_error(AwesomeSpawn::NoSuchFileError, "No such file or directory - XXXXX")
|
57
73
|
end
|
58
74
|
|
59
75
|
context "with option" do
|
@@ -70,6 +86,18 @@ describe AwesomeSpawn do
|
|
70
86
|
end
|
71
87
|
end
|
72
88
|
|
89
|
+
context "#exit_status" do
|
90
|
+
it "command ok exit ok" do
|
91
|
+
expect(subject.send(run_method, "echo", :params => %w(x)).command_line).to eq("echo x")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "command ok exit bad" do
|
95
|
+
if run_method == "run"
|
96
|
+
expect(subject.send(run_method, "echo x && false").command_line).to eq("echo x && false")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
73
101
|
context "#exit_status" do
|
74
102
|
it "command ok exit ok" do
|
75
103
|
expect(subject.send(run_method, "true").exit_status).to eq(0)
|
@@ -88,9 +116,17 @@ describe AwesomeSpawn do
|
|
88
116
|
it "command ok exit bad" do
|
89
117
|
expect(subject.send(run_method, "echo 'bad' && false").output).to eq("bad\n") if run_method == "run"
|
90
118
|
end
|
119
|
+
|
120
|
+
it "has output even though output redirected to stderr" do
|
121
|
+
expect(subject.send(run_method, "echo \"Hello World\" >&2").output).to eq("")
|
122
|
+
end
|
91
123
|
end
|
92
124
|
|
93
125
|
context "#error" do
|
126
|
+
it "has error even though no error" do
|
127
|
+
expect(subject.send(run_method, "echo", :params => ["Hello World"]).error).to eq("")
|
128
|
+
end
|
129
|
+
|
94
130
|
it "command ok exit ok" do
|
95
131
|
expect(subject.send(run_method, "echo \"Hello World\" >&2").error).to eq("Hello World\n")
|
96
132
|
end
|
@@ -102,6 +138,16 @@ describe AwesomeSpawn do
|
|
102
138
|
end
|
103
139
|
end
|
104
140
|
|
141
|
+
context ".build_command_line" do
|
142
|
+
it "should handle single parameter" do
|
143
|
+
expect(subject.build_command_line("cmd")).to eq("cmd")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should handle multi parameter" do
|
147
|
+
expect(subject.build_command_line("cmd", :status => true)).to eq("cmd --status true")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
105
151
|
context ".run" do
|
106
152
|
include_examples "run" do
|
107
153
|
let(:run_method) {"run"}
|
@@ -16,6 +16,14 @@ describe AwesomeSpawn::CommandLineBuilder do
|
|
16
16
|
expect(subject.build("true", nil)).to eq "true"
|
17
17
|
end
|
18
18
|
|
19
|
+
it "with empty" do
|
20
|
+
expect(subject.build("true", "")).to eq "true"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "with empty" do
|
24
|
+
expect(subject.build("true", [])).to eq "true"
|
25
|
+
end
|
26
|
+
|
19
27
|
it "with Pathname command" do
|
20
28
|
actual = subject.build(Pathname.new("/usr/bin/ruby"))
|
21
29
|
expect(actual).to eq "/usr/bin/ruby"
|
@@ -197,6 +205,10 @@ describe AwesomeSpawn::CommandLineBuilder do
|
|
197
205
|
assert_params([["--abc", ["def", "ghi"]]], "--abc def ghi")
|
198
206
|
end
|
199
207
|
|
208
|
+
it "with value as Array and extra nils" do
|
209
|
+
assert_params([["--abc", [nil, "def", nil, "ghi", nil]]], "--abc def ghi")
|
210
|
+
end
|
211
|
+
|
200
212
|
it "with value as flattened Array" do
|
201
213
|
assert_params([["--abc", "def", "ghi"]], "--abc def ghi")
|
202
214
|
end
|
@@ -250,6 +262,11 @@ describe AwesomeSpawn::CommandLineBuilder do
|
|
250
262
|
assert_params(params, expected)
|
251
263
|
end
|
252
264
|
|
265
|
+
it "as mixed Array" do
|
266
|
+
params = ["log", "feature", "-E", :oneline, :grep, "abc"]
|
267
|
+
assert_params(params, expected)
|
268
|
+
end
|
269
|
+
|
253
270
|
it "as mixed Array with nested Hashes" do
|
254
271
|
params = ["log", "feature", "-E", :oneline, {:grep => "abc"}]
|
255
272
|
assert_params(params, expected)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AwesomeSpawn::CommandResultError do
|
4
|
+
context "basic false command" do
|
5
|
+
before { enable_spawning }
|
6
|
+
subject do
|
7
|
+
begin
|
8
|
+
AwesomeSpawn.run!("false")
|
9
|
+
rescue => e
|
10
|
+
return e
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it { expect(subject.message).to eq("false exit code: 1") }
|
15
|
+
it { expect(subject.result).to be_a_failure }
|
16
|
+
end
|
17
|
+
end
|
data/spec/command_result_spec.rb
CHANGED
@@ -1,23 +1,38 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AwesomeSpawn::CommandResult do
|
4
|
-
context "
|
5
|
-
|
6
|
-
str = described_class.new("aaa", "bbb", "ccc", 0).inspect
|
4
|
+
context "succeeding object" do
|
5
|
+
subject { described_class.new("aaa", "bbb", "ccc", 0) }
|
7
6
|
|
8
|
-
|
9
|
-
expect(
|
10
|
-
expect(
|
7
|
+
it "should set attributes" do
|
8
|
+
expect(subject.command_line).to eq("aaa")
|
9
|
+
expect(subject.output).to eq("bbb")
|
10
|
+
expect(subject.error).to eq("ccc")
|
11
|
+
expect(subject.exit_status).to eq(0)
|
12
|
+
expect(subject.inspect).to match(/^#<AwesomeSpawn::CommandResult:[0-9a-fx]+ @exit_status=0>$/)
|
11
13
|
end
|
12
14
|
|
13
|
-
it "
|
14
|
-
expect(
|
15
|
-
expect(
|
15
|
+
it "should not display sensitive information" do
|
16
|
+
expect(subject.inspect).to_not include("aaa")
|
17
|
+
expect(subject.inspect).to_not include("bbb")
|
18
|
+
expect(subject.inspect).to_not include("ccc")
|
16
19
|
end
|
17
20
|
|
18
|
-
it
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
it { expect(subject).to be_a_success }
|
22
|
+
it { expect(subject).not_to be_a_failure }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "failing object" do
|
26
|
+
subject { described_class.new("aaa", "bbb", "ccc", 1) }
|
27
|
+
|
28
|
+
it { expect(subject).not_to be_a_success }
|
29
|
+
it { expect(subject).to be_a_failure }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "another failing object" do
|
33
|
+
subject { described_class.new("aaa", "bbb", "ccc", 100) }
|
34
|
+
|
35
|
+
it { expect(subject).not_to be_a_success }
|
36
|
+
it { expect(subject).to be_a_failure }
|
22
37
|
end
|
23
38
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AwesomeSpawn::NoSuchFileError do
|
4
|
+
before do
|
5
|
+
enable_spawning
|
6
|
+
end
|
7
|
+
|
8
|
+
context "single word command" do
|
9
|
+
subject { caught_exception_for("falsey") }
|
10
|
+
it { expect(subject.message).to eq("No such file or directory - falsey") }
|
11
|
+
it { expect(subject.to_s).to eq("No such file or directory - falsey") }
|
12
|
+
it { expect(subject).to be_a(described_class) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context "multi word command" do
|
16
|
+
subject { caught_exception_for(" flat --arg 1 --arg 2") }
|
17
|
+
it { expect(subject.message).to eq("No such file or directory - flat") }
|
18
|
+
it { expect(subject.to_s).to eq("No such file or directory - flat") }
|
19
|
+
it { expect(subject).to be_a(described_class) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def caught_exception_for(command)
|
23
|
+
AwesomeSpawn.run!(command)
|
24
|
+
rescue => e
|
25
|
+
return e
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,7 +5,6 @@
|
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
7
|
RSpec.configure do |config|
|
8
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
8
|
config.run_all_when_everything_filtered = true
|
10
9
|
config.filter_run :focus
|
11
10
|
|
@@ -19,11 +18,12 @@ RSpec.configure do |config|
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def disable_spawning
|
22
|
-
Open3.
|
21
|
+
allow(Open3).to receive(:capture3)
|
22
|
+
.and_raise("Spawning is not permitted in specs. Please change your spec to use expectations/stubs.")
|
23
23
|
end
|
24
24
|
|
25
25
|
def enable_spawning
|
26
|
-
Open3.
|
26
|
+
allow(Open3).to receive(:capture3).and_call_original
|
27
27
|
end
|
28
28
|
|
29
29
|
begin
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awesome_spawn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jason Frey
|
@@ -12,12 +11,11 @@ authors:
|
|
12
11
|
autorequire:
|
13
12
|
bindir: bin
|
14
13
|
cert_chain: []
|
15
|
-
date:
|
14
|
+
date: 2015-01-28 00:00:00.000000000 Z
|
16
15
|
dependencies:
|
17
16
|
- !ruby/object:Gem::Dependency
|
18
17
|
name: bundler
|
19
18
|
requirement: !ruby/object:Gem::Requirement
|
20
|
-
none: false
|
21
19
|
requirements:
|
22
20
|
- - ~>
|
23
21
|
- !ruby/object:Gem::Version
|
@@ -25,7 +23,6 @@ dependencies:
|
|
25
23
|
type: :development
|
26
24
|
prerelease: false
|
27
25
|
version_requirements: !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
26
|
requirements:
|
30
27
|
- - ~>
|
31
28
|
- !ruby/object:Gem::Version
|
@@ -33,49 +30,43 @@ dependencies:
|
|
33
30
|
- !ruby/object:Gem::Dependency
|
34
31
|
name: rake
|
35
32
|
requirement: !ruby/object:Gem::Requirement
|
36
|
-
none: false
|
37
33
|
requirements:
|
38
|
-
- -
|
34
|
+
- - '>='
|
39
35
|
- !ruby/object:Gem::Version
|
40
36
|
version: '0'
|
41
37
|
type: :development
|
42
38
|
prerelease: false
|
43
39
|
version_requirements: !ruby/object:Gem::Requirement
|
44
|
-
none: false
|
45
40
|
requirements:
|
46
|
-
- -
|
41
|
+
- - '>='
|
47
42
|
- !ruby/object:Gem::Version
|
48
43
|
version: '0'
|
49
44
|
- !ruby/object:Gem::Dependency
|
50
45
|
name: rspec
|
51
46
|
requirement: !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
47
|
requirements:
|
54
|
-
- -
|
48
|
+
- - '>='
|
55
49
|
- !ruby/object:Gem::Version
|
56
50
|
version: '0'
|
57
51
|
type: :development
|
58
52
|
prerelease: false
|
59
53
|
version_requirements: !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
54
|
requirements:
|
62
|
-
- -
|
55
|
+
- - '>='
|
63
56
|
- !ruby/object:Gem::Version
|
64
57
|
version: '0'
|
65
58
|
- !ruby/object:Gem::Dependency
|
66
59
|
name: coveralls
|
67
60
|
requirement: !ruby/object:Gem::Requirement
|
68
|
-
none: false
|
69
61
|
requirements:
|
70
|
-
- -
|
62
|
+
- - '>='
|
71
63
|
- !ruby/object:Gem::Version
|
72
64
|
version: '0'
|
73
65
|
type: :development
|
74
66
|
prerelease: false
|
75
67
|
version_requirements: !ruby/object:Gem::Requirement
|
76
|
-
none: false
|
77
68
|
requirements:
|
78
|
-
- -
|
69
|
+
- - '>='
|
79
70
|
- !ruby/object:Gem::Version
|
80
71
|
version: '0'
|
81
72
|
description: AwesomeSpawn is a module that provides some useful features over Ruby's
|
@@ -94,43 +85,47 @@ files:
|
|
94
85
|
- lib/awesome_spawn/command_result.rb
|
95
86
|
- lib/awesome_spawn/command_result_error.rb
|
96
87
|
- lib/awesome_spawn/no_such_file_error.rb
|
88
|
+
- lib/awesome_spawn/null_logger.rb
|
97
89
|
- lib/awesome_spawn/version.rb
|
98
90
|
- .yardopts
|
99
91
|
- README.md
|
100
92
|
- LICENSE.txt
|
101
93
|
- spec/awesome_spawn_spec.rb
|
102
94
|
- spec/command_line_builder_spec.rb
|
95
|
+
- spec/command_result_error_spec.rb
|
103
96
|
- spec/command_result_spec.rb
|
97
|
+
- spec/no_such_file_error_spec.rb
|
104
98
|
- spec/spec_helper.rb
|
105
99
|
- .rspec
|
106
100
|
homepage: https://github.com/ManageIQ/awesome_spawn
|
107
101
|
licenses:
|
108
102
|
- MIT
|
103
|
+
metadata: {}
|
109
104
|
post_install_message:
|
110
105
|
rdoc_options: []
|
111
106
|
require_paths:
|
112
107
|
- lib
|
113
108
|
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
109
|
requirements:
|
116
|
-
- -
|
110
|
+
- - '>='
|
117
111
|
- !ruby/object:Gem::Version
|
118
112
|
version: '0'
|
119
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
-
none: false
|
121
114
|
requirements:
|
122
|
-
- -
|
115
|
+
- - '>='
|
123
116
|
- !ruby/object:Gem::Version
|
124
117
|
version: '0'
|
125
118
|
requirements: []
|
126
119
|
rubyforge_project:
|
127
|
-
rubygems_version:
|
120
|
+
rubygems_version: 2.0.14
|
128
121
|
signing_key:
|
129
|
-
specification_version:
|
122
|
+
specification_version: 4
|
130
123
|
summary: AwesomeSpawn is a module that provides some useful features over Ruby's Kernel.spawn.
|
131
124
|
test_files:
|
132
125
|
- spec/awesome_spawn_spec.rb
|
133
126
|
- spec/command_line_builder_spec.rb
|
127
|
+
- spec/command_result_error_spec.rb
|
134
128
|
- spec/command_result_spec.rb
|
129
|
+
- spec/no_such_file_error_spec.rb
|
135
130
|
- spec/spec_helper.rb
|
136
131
|
- .rspec
|