stairs 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +38 -0
  4. data/.travis.yml +4 -0
  5. data/Guardfile +10 -0
  6. data/README.md +39 -9
  7. data/Rakefile +5 -0
  8. data/lib/stairs/configuration.rb +1 -1
  9. data/lib/stairs/env_adapters/dotenv.rb +2 -2
  10. data/lib/stairs/env_adapters/rbenv.rb +2 -2
  11. data/lib/stairs/env_adapters/rvm.rb +2 -2
  12. data/lib/stairs/env_adapters.rb +2 -2
  13. data/lib/stairs/interactive_configuration.rb +25 -7
  14. data/lib/stairs/railtie.rb +7 -0
  15. data/lib/stairs/script.rb +3 -3
  16. data/lib/stairs/step.rb +49 -23
  17. data/lib/stairs/steps/secret_token.rb +1 -1
  18. data/lib/stairs/steps.rb +1 -1
  19. data/lib/stairs/tasks.rb +1 -1
  20. data/lib/stairs/util/cli.rb +50 -0
  21. data/lib/stairs/util/{file_utils.rb → file_mutation.rb} +10 -5
  22. data/lib/stairs/util.rb +3 -2
  23. data/lib/stairs/version.rb +1 -1
  24. data/lib/stairs.rb +3 -1
  25. data/spec/lib/configuration_spec.rb +12 -0
  26. data/spec/lib/stairs/env_adapters/dotenv_spec.rb +38 -0
  27. data/spec/lib/stairs/env_adapters/rbenv_spec.rb +40 -0
  28. data/spec/lib/stairs/env_adapters/rvm_spec.rb +40 -0
  29. data/spec/lib/stairs/env_adapters_spec.rb +28 -0
  30. data/spec/lib/stairs/interactive_configuration_spec.rb +36 -0
  31. data/spec/lib/stairs/script_spec.rb +29 -0
  32. data/spec/lib/stairs/step_spec.rb +293 -0
  33. data/spec/lib/stairs/steps/secret_token_spec.rb +13 -0
  34. data/spec/lib/stairs/util/cli_spec.rb +57 -0
  35. data/spec/lib/stairs/util/file_mutation_spec.rb +98 -0
  36. data/spec/spec_helper.rb +17 -0
  37. data/spec/support/mock_stdout.rb +52 -0
  38. data/stairs.gemspec +8 -1
  39. metadata +138 -8
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::EnvAdapters do
4
+ subject { described_class }
5
+ let(:present_adapter) { double("adapter", present?: true) }
6
+ let(:other_adapter) { double("adapter", present?: false) }
7
+ let(:another_adapter) { double("adapter", present?: false) }
8
+
9
+ before do
10
+ stub_const "Stairs::EnvAdapters::ADAPTERS", {
11
+ one: other_adapter,
12
+ two: present_adapter,
13
+ three: another_adapter
14
+ }
15
+ end
16
+
17
+ describe ".recommended_adapter" do
18
+ it "returns the first adapter to be `present?`" do
19
+ expect(described_class.recommended_adapter).to eq present_adapter
20
+ end
21
+ end
22
+
23
+ describe ".name_for_adapter_class" do
24
+ it "returns the name" do
25
+ expect(described_class.name_for_adapter_class(present_adapter)).to eq :two
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::InteractiveConfiguration do
4
+ subject { described_class.new }
5
+
6
+ describe "metadata" do
7
+ it "has a title" do
8
+ expect(described_class.step_title).not_to be_nil
9
+ end
10
+
11
+ it "has a description" do
12
+ expect(described_class.step_description).not_to be_nil
13
+ end
14
+ end
15
+
16
+ describe "#run!" do
17
+ before do
18
+ Stairs::EnvAdapters.stub recommended_adapter: Stairs::EnvAdapters::Rbenv
19
+ end
20
+
21
+ it "recommends an adapter" do
22
+ output = follow_prompts("Y") { subject.run! }
23
+ expect(output).to include "you're using rbenv"
24
+ end
25
+
26
+ it "sets the adapter" do
27
+ follow_prompts("Y") { subject.run! }
28
+ expect(Stairs.configuration.env_adapter).to be_a Stairs::EnvAdapters::Rbenv
29
+ end
30
+
31
+ it "allows for specifying your desired adapter" do
32
+ follow_prompts("N", "dotenv") { subject.run! }
33
+ expect(Stairs.configuration.env_adapter).to be_a Stairs::EnvAdapters::Dotenv
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::Script do
4
+ let(:filename) { "setup.rb" }
5
+ subject { described_class.new(filename) }
6
+
7
+ context "with a script present" do
8
+ before do
9
+ File.open(filename, "w") do |file|
10
+ file.write("self.class")
11
+ end
12
+ end
13
+
14
+ after { File.delete(filename) }
15
+
16
+ describe "#run!" do
17
+ it "outputs running message" do
18
+ output = capture_stdout { subject.run! }
19
+ expect(output).to include "= Running script setup.rb"
20
+ end
21
+
22
+ it "evaluates the script in the context of an instance of Step" do
23
+ # because our test setup.rb only contains `self.class` we can check
24
+ # this way:
25
+ expect(subject.run!).to eq Stairs::Step
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,293 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::Step do
4
+ let(:anon_step) { Class.new(described_class) }
5
+ subject { anon_step.new }
6
+
7
+ describe "metadata" do
8
+ describe "step_title" do
9
+ it "can be set on the class using title class DSL" do
10
+ anon_step.title "Class Step Name"
11
+ expect(subject.step_title).to eq "Class Step Name"
12
+ end
13
+
14
+ it "can be set on the instance" do
15
+ subject.step_title = "Instance Step Name"
16
+ expect(subject.step_title).to eq "Instance Step Name"
17
+ end
18
+
19
+ it "prefers the value set on instance" do
20
+ anon_step.title "Class Step Name"
21
+ subject.step_title = "Instance Step Name"
22
+ expect(subject.step_title).to eq "Instance Step Name"
23
+ end
24
+ end
25
+
26
+ describe "step_description" do
27
+ it "can be set on the class using description class DSL" do
28
+ anon_step.description "Class Step Description"
29
+ expect(subject.step_description).to eq "Class Step Description"
30
+ end
31
+
32
+ it "can be set on the instance" do
33
+ subject.step_description = "Instance Step Description"
34
+ expect(subject.step_description).to eq "Instance Step Description"
35
+ end
36
+
37
+ it "prefers the value set on instance" do
38
+ anon_step.title "Class Step Description"
39
+ subject.step_description = "Instance Step Description"
40
+ expect(subject.step_description).to eq "Instance Step Description"
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "#run!" do
46
+ before { subject.stub run: true }
47
+ before { anon_step.title "Step Name" }
48
+
49
+ it "outputs lead-in message" do
50
+ output = capture_stdout { subject.run! }
51
+ expect(output).to include "== Running Step Name"
52
+ end
53
+
54
+ it "calls #run where the implementation lives" do
55
+ subject.should_receive(:run)
56
+ subject.run!
57
+ end
58
+
59
+ it "outputs completed message" do
60
+ output = capture_stdout { subject.run! }
61
+ expect(output).to include "== Completed Step Name"
62
+ end
63
+ end
64
+
65
+ describe "#provide" do
66
+ it "prompts the user to provide input" do
67
+ output = follow_prompts("here") { subject.provide "Gimme" }
68
+ expect(output).to include "Gimme: "
69
+ end
70
+
71
+ it "returns the input" do
72
+ follow_prompts("here") do
73
+ expect(subject.provide("Gimme")).to eq "here"
74
+ end
75
+ end
76
+
77
+ it "requires user input" do
78
+ follow_prompts "", "", "", "finally" do
79
+ expect(subject.provide("Gimme")).to eq "finally"
80
+ end
81
+ end
82
+
83
+ context "with a default" do
84
+ def call_method
85
+ subject.provide "Gimme", default: "adefault"
86
+ end
87
+
88
+ it "does not require input" do
89
+ output = follow_prompts("here") { call_method }
90
+ end
91
+
92
+ context "and required" do
93
+ it "does not require input" do
94
+ follow_prompts "" do
95
+ expect(subject.provide("Gimme", required: true, default: "adefault")).to eq "adefault"
96
+ end
97
+ end
98
+ end
99
+
100
+ context "with no input" do
101
+ it "returns the default" do
102
+ follow_prompts("") do
103
+ expect(call_method).to eq "adefault"
104
+ end
105
+ end
106
+ end
107
+
108
+ context "with input" do
109
+ it "returns the input" do
110
+ follow_prompts("here") do
111
+ expect(call_method).to eq "here"
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ context "optional" do
118
+ it "does not require input" do
119
+ follow_prompts "" do
120
+ expect(subject.provide("Gimme", required: false)).to eq nil
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "#choice" do
127
+ it "prompts the user to answer the question" do
128
+ output = follow_prompts("Y") { subject.choice("Should I?") }
129
+ expect(output).to include "Should I?"
130
+ end
131
+
132
+ it "defaults to a Y/N question" do
133
+ output = follow_prompts("Y") { subject.choice("Should I?") }
134
+ expect(output).to include "(Y/N)"
135
+ end
136
+
137
+ it "returns true for Y" do
138
+ follow_prompts("Y") { expect(subject.choice("Should I?")).to be_true }
139
+ end
140
+
141
+ it "returns true for Y" do
142
+ follow_prompts("N") { expect(subject.choice("Should I?")).to be_false }
143
+ end
144
+
145
+ context "with available choices provided" do
146
+ it "displays those choices" do
147
+ output = follow_prompts("Nick") { subject.choice("What's your name?", ["Nick", "Brendan"]) }
148
+ expect(output).to include "(Nick/Brendan)"
149
+ end
150
+
151
+ it "returns the user's choice" do
152
+ follow_prompts("Nick") do
153
+ expect(subject.choice("What's your name?", ["Nick", "Brendan"])).to eq "Nick"
154
+ end
155
+ end
156
+
157
+ it "prompts repeatedly until it receives input in available choices" do
158
+ follow_prompts("Sally", "Frank", "Nick") do
159
+ expect(subject.choice("What's your name?", ["Nick", "Brendan"])).to eq "Nick"
160
+ end
161
+ end
162
+ end
163
+
164
+ context "with a block" do
165
+ it "calls the block with the user's choice" do
166
+ follow_prompts("Nick") do
167
+ expect do |block|
168
+ subject.choice("What's your name?", ["Nick", "Brendan"], &block)
169
+ end.to yield_with_args("Nick")
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ describe "#bundle" do
176
+ before { subject.stub system: true }
177
+
178
+ it "outputs lead-in message" do
179
+ output = capture_stdout { subject.bundle }
180
+ expect(output).to include "== Running bundle"
181
+ end
182
+
183
+ it "runs bundle" do
184
+ subject.should_receive(:system).with("bundle")
185
+ subject.bundle
186
+ end
187
+
188
+ it "outputs completed message" do
189
+ output = capture_stdout { subject.bundle }
190
+ expect(output).to include "== Completed bundle"
191
+ end
192
+ end
193
+
194
+ describe "#rake" do
195
+ before { subject.stub system: true }
196
+
197
+ it "outputs lead-in message" do
198
+ output = capture_stdout { subject.rake "the_task" }
199
+ expect(output).to include "== Running the_task"
200
+ end
201
+
202
+ it "runs the rake task" do
203
+ subject.should_receive(:system).with("rake the_task")
204
+ subject.rake "the_task"
205
+ end
206
+
207
+ it "outputs completed message" do
208
+ output = capture_stdout { subject.rake "the_task" }
209
+ expect(output).to include "== Completed the_task"
210
+ end
211
+ end
212
+
213
+ describe "#env" do
214
+ let(:adapter) { double("adapter", set: true) }
215
+ before { Stairs.configuration.env_adapter = adapter }
216
+
217
+ it "delegates to the adapter" do
218
+ adapter.should_receive(:set).with("NAME", "value")
219
+ subject.env "NAME", "value"
220
+ end
221
+
222
+ it "writes to ENV simultaneously so Rubyland can access without a reload" do
223
+ ENV.should_receive(:[]=).with("NAME", "value")
224
+ subject.env "NAME", "value"
225
+ end
226
+ end
227
+
228
+ describe "#write" do
229
+ it "delegates to the well tested FileMutation util" do
230
+ Stairs::Util::FileMutation.should_receive(:write).with("something", "file.txt")
231
+ subject.write("something", "file.txt")
232
+ end
233
+ end
234
+
235
+ describe "#write_line" do
236
+ it "delegates to the well tested FileMutation util" do
237
+ Stairs::Util::FileMutation.should_receive(:write_line).with("something", "file.txt")
238
+ subject.write_line("something", "file.txt")
239
+ end
240
+ end
241
+
242
+ describe "#finish" do
243
+ it "outputs lead-in message" do
244
+ output = capture_stdout { subject.finish "Message" }
245
+ expect(output).to include "== All done!"
246
+ end
247
+
248
+ it "outputs supplied message" do
249
+ output = capture_stdout { subject.finish "My message" }
250
+ expect(output).to include "My message"
251
+ end
252
+ end
253
+
254
+ describe "#stairs_info" do
255
+ it "outputs the message" do
256
+ output = capture_stdout { subject.stairs_info "Ohai" }
257
+ expect(output).to include "Ohai"
258
+ end
259
+ end
260
+
261
+ describe "#setup" do
262
+ context "with an invalid step_name" do
263
+ it "raises when step_name cannot be resolved in Stairs::Steps" do
264
+ expect { subject.setup :blahblahbefkj }.to raise_error
265
+ end
266
+ end
267
+
268
+ context "with a valid step_name" do
269
+ let!(:mock_step_class) { Stairs::Steps::MockStep = Class.new(Stairs::Step) }
270
+
271
+ it "instantiates and runs the step" do
272
+ mock_step_class.any_instance.should_receive(:run!)
273
+ subject.setup :mock_step
274
+ end
275
+ end
276
+
277
+ context "with a block" do
278
+ def call_method
279
+ subject.setup(:custom_step) { puts "I'm running in #{self.class}" }
280
+ end
281
+
282
+ it "sets the new step's title to a titleized version of step_name" do
283
+ output = capture_stdout { call_method }
284
+ expect(output).to include "Custom Step"
285
+ end
286
+
287
+ it "runs the block in the context of the new step" do
288
+ output = capture_stdout { call_method }
289
+ expect(output).to include "I'm running in Stairs::Step"
290
+ end
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::Steps::SecretToken do
4
+ subject { described_class.new }
5
+
6
+ describe "#run" do
7
+ it "generates a securerandom hex and sets to SECRET_TOKEN" do
8
+ SecureRandom.stub hex: "imtotallysecurebro"
9
+ subject.should_receive(:env).with("SECRET_TOKEN", "imtotallysecurebro")
10
+ subject.run
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::Util::CLI do
4
+ subject { described_class }
5
+
6
+ describe ".get" do
7
+ it "outputs the prompt to screen" do
8
+ output = follow_prompts("test") { subject.get("itefeffe") }
9
+ expect(output).to include "itefeffe"
10
+ end
11
+
12
+ it "collects and returns trimmed input" do
13
+ follow_prompts "test" do
14
+ expect(subject.get("it")).to eq "test"
15
+ end
16
+ end
17
+
18
+ it "returns nil for empty input" do
19
+ follow_prompts "" do
20
+ expect(subject.get("it")).to eq nil
21
+ end
22
+ end
23
+ end
24
+
25
+ describe ".collect" do
26
+ it "returns the user provided input" do
27
+ follow_prompts "test" do
28
+ expect(subject.collect("itefeffe")).to eq "test"
29
+ end
30
+ end
31
+
32
+ context "required" do
33
+ it "repeatedly prompts until a non-empty value is received" do
34
+ follow_prompts "", "", "", "finally" do
35
+ expect(subject.collect("a value")).to eq "finally"
36
+ end
37
+ end
38
+ end
39
+
40
+ context "not required" do
41
+ it "returns nil for empty input" do
42
+ follow_prompts "" do
43
+ expect(subject.collect("gimme", required: false)).to eq nil
44
+ end
45
+ end
46
+ end
47
+
48
+ context "with custom validation block" do
49
+ it "repeatedly prompts until a valid value is received" do
50
+ follow_prompts "", "", "wrong", "right" do
51
+ response = subject.collect("a value") { |v, i| v == "right" }
52
+ expect(response).to eq "right"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,98 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::Util::FileMutation do
4
+ let(:filename) { "file.txt" }
5
+
6
+ describe ".replace_or_append" do
7
+ context "when the pattern exists in the file" do
8
+ before do
9
+ File.open(filename, "w") do |file|
10
+ file.write("line1\nline2\nline3\n")
11
+ end
12
+ end
13
+
14
+ after { File.delete(filename) }
15
+
16
+ it "replaces the match and writes back to the file" do
17
+ described_class.replace_or_append /line2/, "newLINE2", filename
18
+ expect(File.read(filename)).to eq "line1\nnewLINE2\nline3\n"
19
+ end
20
+ end
21
+
22
+ context "when the pattern does not exist in the file" do
23
+ before do
24
+ File.open(filename, "w") do |file|
25
+ file.write("line1\nline2\nline3\n")
26
+ end
27
+ end
28
+
29
+ after { File.delete(filename) }
30
+
31
+ it "appends to the end of the file" do
32
+ described_class.replace_or_append /line4/, "line4", filename
33
+ expect(File.read(filename)).to eq "line1\nline2\nline3\nline4\n"
34
+ end
35
+ end
36
+ end
37
+
38
+ describe ".write_line" do
39
+ before do
40
+ File.open(filename, "w") do |file|
41
+ file.write("line1\nline2\n")
42
+ end
43
+ end
44
+
45
+ after { File.delete(filename) }
46
+
47
+ it "appends a line to the bottom of the file" do
48
+ described_class.write_line "line3", filename
49
+ expect(File.read(filename)).to eq "line1\nline2\nline3\n"
50
+ end
51
+
52
+ context "when there is no newline at the bottom" do
53
+ before do
54
+ File.delete(filename) if File.exists?(filename)
55
+
56
+ File.open(filename, "w") do |file|
57
+ file.write("line1\nline2")
58
+ end
59
+ end
60
+
61
+ it "adds a newline before appending the line" do
62
+ described_class.write_line "line3", filename
63
+ expect(File.read(filename)).to eq "line1\nline2\nline3\n"
64
+ end
65
+ end
66
+ end
67
+
68
+ describe ".write" do
69
+ context "when the file exists" do
70
+ before do
71
+ File.open(filename, "w") do |file|
72
+ file.write("some content\n")
73
+ end
74
+ end
75
+
76
+ after { File.delete(filename) }
77
+
78
+ it "replaces the contents of the file and leaves trailing newline" do
79
+ described_class.write "new content", filename
80
+ expect(File.read(filename)).to eq "new content\n"
81
+ end
82
+ end
83
+
84
+ context "when the file doesn't exist" do
85
+ after { File.delete(filename) }
86
+
87
+ it "creates a file" do
88
+ described_class.write "other content", filename
89
+ expect(File.exists?(filename)).to be_true
90
+ end
91
+
92
+ it "writes the contents to the file" do
93
+ described_class.write "my favorite content", filename
94
+ expect(File.read(filename)).to eq "my favorite content\n"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ require "bundler"
2
+ Bundler.require
3
+
4
+ require "stairs"
5
+
6
+ Dir["./spec/support/**/*.rb"].each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run :focus
12
+
13
+ config.include MockStdIO
14
+
15
+ config.before(:all, &:silence_output)
16
+ config.after(:all, &:enable_output)
17
+ end
@@ -0,0 +1,52 @@
1
+ require "stringio"
2
+
3
+ module MockStdIO
4
+ def follow_prompts(*responses, &block)
5
+ old_stdin = $stdin
6
+ old_stdout = $stdout
7
+
8
+ $stdin = StringIO.new("", "r+")
9
+ fake_stdout = StringIO.new
10
+ $stdout = fake_stdout
11
+
12
+ responses.each { |r| $stdin << "#{r}\n" }
13
+ $stdin.rewind
14
+
15
+ block.call
16
+
17
+ fake_stdout.string
18
+ ensure
19
+ $stdin = old_stdin
20
+ $stdout = old_stdout
21
+ end
22
+
23
+ def capture_stdout(&block)
24
+ old_stdout = $stdout
25
+
26
+ fake_stdout = StringIO.new
27
+ $stdout = fake_stdout
28
+
29
+ block.call
30
+
31
+ fake_stdout.string
32
+ ensure
33
+ $stdout = old_stdout
34
+ end
35
+
36
+ def silence_output
37
+ @orig_stderr = $stderr
38
+ @orig_stdout = $stdout
39
+
40
+ # redirect stderr and stdout to /dev/null
41
+ $stderr = File.new("/dev/null", "w")
42
+ $stdout = File.new("/dev/null", "w")
43
+ end
44
+
45
+ # Replace stdout and stderr so anything else is output correctly.
46
+ def enable_output
47
+ $stderr = @orig_stderr
48
+ $stdout = @orig_stdout
49
+ @orig_stderr = nil
50
+ @orig_stdout = nil
51
+ end
52
+ end
data/stairs.gemspec CHANGED
@@ -20,8 +20,15 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "rspec", "~> 2.14.1"
25
+ spec.add_development_dependency "guard-rspec", "~> 4.0.3"
26
+ spec.add_development_dependency "rb-fsevent", "~> 0.9.3"
27
+ spec.add_development_dependency "awesome_print", "~> 1.2.0"
28
+ spec.add_development_dependency "fakefs", "~> 0.4.3"
29
+ spec.add_development_dependency "rubocop", "~> 0.14.1"
30
+ spec.add_development_dependency "guard-rubocop", "~> 1.0.0"
23
31
 
24
- spec.add_dependency "highline", "~> 1.6.20"
25
32
  spec.add_dependency "activesupport", ">= 3.2.0"
26
33
  spec.add_dependency "colorize", "~> 0.6.0"
27
34
  end