stairs 0.3.0 → 0.4.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.
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