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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7065dce0f1a914961380b69ee67aeab6ca1a3633
4
- data.tar.gz: b9adf5378fc447b10dd8a4cb9e349b2d56faff3d
3
+ metadata.gz: 5757c8d2dd7a6f123729557b5ccf99f292f75a9f
4
+ data.tar.gz: 1a208a280bc33d113efc36aadc00e07213931887
5
5
  SHA512:
6
- metadata.gz: 8838ad2217b046c6fcc40fa578df6ce8e6b282d1752224d76aab26ee437e08e72dc0b5d02af85cfddca0c9f103eedc4aebf907429928dd6c05ac94bc7d8b5cfc
7
- data.tar.gz: f6ba56450cf0aafb9156f0ca625eadf3d3c4fa8374b6a25e3ea8a23489b5a932c6949b5c18a48062eb666c7df25a35e0e7218fd0429d7b863149ebb91881e281
6
+ metadata.gz: 52a26152b30d4289ac3e73b49dd33ade9ccf9270df0e6b2802f230966a223b3df34902d6cfe07efd9b9962d6b1d33c7aec51fbc13fa89874ea14c204ed5f3ddd
7
+ data.tar.gz: 0f39e7ab93a8335c98c7a4e1cc186882c6ba49c8c92ad5698c97aac57eeb9bc75e9c622196e97af35cf864a5428eb387873c8392e25f8f313f97c020aca86978
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rubocop.yml ADDED
@@ -0,0 +1,38 @@
1
+ StringLiterals:
2
+ EnforcedStyle: "double_quotes"
3
+
4
+ LineLength:
5
+ Max: 80
6
+
7
+ # This offends when not tabbed way out while using case in assignment
8
+ CaseIndentation:
9
+ Enabled: false
10
+
11
+ # This was offending when it shouldn't have been, like in util/file_mutation:21
12
+ UselessAssignment:
13
+ Enabled: false
14
+
15
+ # Seems unnecessary?
16
+ ReduceArguments:
17
+ Enabled: false
18
+
19
+ # Not a part of our coding conventions
20
+ Documentation:
21
+ Enabled: false
22
+
23
+ # Also just not something I've ever done or want to do...
24
+ SpaceAroundEqualsInParameterDefault:
25
+ Enabled: false
26
+
27
+ # I don't see why... It's shorthand, just know your ruby!
28
+ SpecialGlobalVars:
29
+ Enabled: false
30
+
31
+ # This was offending for a custom method called `collect` not on an `Enumerable`
32
+ CollectionMethods:
33
+ Enabled: false
34
+
35
+ # I just don't care so much about clean specs.. Like short methods and short lines
36
+ AllCops:
37
+ Excludes:
38
+ - spec/**
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard "rspec" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch("spec/spec_helper.rb") { "spec" }
5
+ end
6
+
7
+ guard :rubocop do
8
+ watch(%r{.+\.rb$})
9
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
10
+ end
data/README.md CHANGED
@@ -1,20 +1,37 @@
1
1
  # Stairs
2
2
 
3
- A DSL and collection of plugins for easy setup of projects on new development
4
- environments. Write a script that new devs can run for an interactive setup.
5
- For environment variables, Stairs supports rbenv-vars, RVM, and dotenv.
3
+ It's a pain to setup new developers. Stairs is a utility and framework from
4
+ which to write scripts for faster and easier setup of apps in new development
5
+ environments. Scripts try to automate as much as possible and provide
6
+ interactive prompts for everything else.
7
+
8
+ Stairs currently supports writing environment variables for rbenv-vars, RVM,
9
+ and dotenv.
10
+
11
+ [![Build Status](https://travis-ci.org/patbenatar/stairs.png?branch=master)](https://travis-ci.org/patbenatar/stairs)
12
+ [![Code Climate](https://codeclimate.com/github/patbenatar/stairs.png)](https://codeclimate.com/github/patbenatar/stairs)
6
13
 
7
14
  ## Setup
8
15
 
9
- 1. Install gem `stairs`
16
+ ### Rails
17
+
18
+ Add Stairs to your `Gemfile`:
10
19
 
11
- 1. Require tasks in `Rakefile`
12
20
  ```ruby
13
- require "stairs/tasks"
21
+ gem "stairs"
14
22
  ```
15
23
 
16
- 1. [Define your script](#defining-scripts) in `setup.rb` at the root of your
17
- project
24
+ [Define your script](#defining-scripts) in `setup.rb` at the root of your
25
+ project.
26
+
27
+ ### Not Rails
28
+
29
+ Same as above, but you'll have to manually add the Stairs Rake tasks to your
30
+ `Rakefile`.
31
+
32
+ ```ruby
33
+ require "stairs/tasks"
34
+ ```
18
35
 
19
36
  ## Usage
20
37
 
@@ -172,4 +189,17 @@ extension gems for examples.
172
189
 
173
190
  [s3]: http://github.com/patbenatar/stairs-steps-s3
174
191
  [balanced]: http://github.com/patbenatar/stairs-steps-balanced
175
- [facebook]: http://github.com/patbenatar/stairs-steps-facebook
192
+ [facebook]: http://github.com/patbenatar/stairs-steps-facebook
193
+
194
+ ## Credits
195
+
196
+ ### Contributors
197
+
198
+ * [Nick Giancola](https://github.com/patbenatar)
199
+ * [Brendan Loudermilk](https://github.com/bloudermilk)
200
+
201
+ ### Sponsor
202
+
203
+ [![philosophie](http://patbenatar.github.io/showoff/images/philosophie.png)](http://gophilosophie.com)
204
+
205
+ This gem is maintained partially during my open source time at [philosophie](http://gophilosophie.com).
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task default: :spec
@@ -2,4 +2,4 @@ module Stairs
2
2
  class Configuration
3
3
  attr_accessor :env_adapter
4
4
  end
5
- end
5
+ end
@@ -6,7 +6,7 @@ module Stairs
6
6
  end
7
7
 
8
8
  def set(name, value)
9
- Util::FileUtils.replace_or_append(
9
+ Util::FileMutation.replace_or_append(
10
10
  Regexp.new("^#{name}=(.*)$"),
11
11
  "#{name}=#{value}",
12
12
  ".env",
@@ -14,4 +14,4 @@ module Stairs
14
14
  end
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -7,7 +7,7 @@ module Stairs
7
7
  end
8
8
 
9
9
  def set(name, value)
10
- Util::FileUtils.replace_or_append(
10
+ Util::FileMutation.replace_or_append(
11
11
  Regexp.new("^#{name}=(.*)$"),
12
12
  "#{name}=#{value}",
13
13
  ".rbenv-vars",
@@ -15,4 +15,4 @@ module Stairs
15
15
  end
16
16
  end
17
17
  end
18
- end
18
+ end
@@ -7,7 +7,7 @@ module Stairs
7
7
  end
8
8
 
9
9
  def set(name, value)
10
- Util::FileUtils.replace_or_append(
10
+ Util::FileMutation.replace_or_append(
11
11
  Regexp.new("^export #{name}=(.*)$"),
12
12
  "export #{name}=#{value}",
13
13
  ".rvmrc",
@@ -15,4 +15,4 @@ module Stairs
15
15
  end
16
16
  end
17
17
  end
18
- end
18
+ end
@@ -15,7 +15,7 @@ module Stairs
15
15
  end
16
16
 
17
17
  def self.name_for_adapter_class(adapter)
18
- ADAPTERS.find { |_n,a| a == adapter }.first
18
+ ADAPTERS.find { |_n, a| a == adapter }.first
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -4,19 +4,37 @@ module Stairs
4
4
  description "Interactive prompt for configuring Stairs"
5
5
 
6
6
  def run!
7
- adapter_class = Stairs::EnvAdapters.recommended_adapter
8
- adapter_name = Stairs::EnvAdapters.name_for_adapter_class(adapter_class)
9
-
10
- choice "Looks like you're using #{adapter_name} to manage environment variables. Is this correct?" do |yes|
7
+ choice prompt do |yes|
11
8
  if yes
12
- Stairs.configuration.env_adapter = adapter_class.new
9
+ Stairs.configuration.env_adapter = recommended_adapter.new
13
10
  else
14
- choice "Which would you prefer?", Stairs::EnvAdapters::ADAPTERS.map { |n,_a| n.to_s } do |name|
11
+ choice "Which would you prefer?", adapter_names do |name|
15
12
  adapter_class = Stairs::EnvAdapters::ADAPTERS[name.to_sym]
16
13
  Stairs.configuration.env_adapter = adapter_class.new
17
14
  end
18
15
  end
19
16
  end
20
17
  end
18
+
19
+ private
20
+
21
+ def recommended_adapter
22
+ @recommended_adapter ||= Stairs::EnvAdapters.recommended_adapter
23
+ end
24
+
25
+ def recommended_adapter_name
26
+ Stairs::EnvAdapters.name_for_adapter_class(recommended_adapter)
27
+ end
28
+
29
+ def prompt
30
+ "".tap do |message|
31
+ message << "Looks like you're using #{recommended_adapter_name} to "
32
+ message << "manage environment variables. Is this correct?"
33
+ end
34
+ end
35
+
36
+ def adapter_names
37
+ Stairs::EnvAdapters::ADAPTERS.map { |n, _a| n.to_s }
38
+ end
21
39
  end
22
- end
40
+ end
@@ -0,0 +1,7 @@
1
+ module Stairs
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load "stairs/tasks.rb"
5
+ end
6
+ end
7
+ end
data/lib/stairs/script.rb CHANGED
@@ -10,12 +10,12 @@ module Stairs
10
10
  run
11
11
  end
12
12
 
13
+ private
14
+
13
15
  def run
14
16
  Step.new.instance_eval(script)
15
17
  end
16
18
 
17
- private
18
-
19
19
  attr_reader :script, :filename
20
20
  end
21
- end
21
+ end
data/lib/stairs/step.rb CHANGED
@@ -1,5 +1,3 @@
1
- require "highline/import"
2
-
3
1
  module Stairs
4
2
  class Step
5
3
  def run!
@@ -10,8 +8,6 @@ module Stairs
10
8
 
11
9
  attr_writer :step_title, :step_description
12
10
 
13
- private
14
-
15
11
  class_attribute :step_title, :step_description
16
12
 
17
13
  def self.title(title)
@@ -38,25 +34,13 @@ module Stairs
38
34
  prompt << " (leave blank for #{options[:default]})" if options[:default]
39
35
  prompt << ": "
40
36
 
41
- response = ask(prompt.blue) { |q| q.validate = /\S+/ if required }
42
- response.present? ? response : options[:default]
37
+ Stairs::Util::CLI.collect(prompt.blue, required: required) ||
38
+ options[:default]
43
39
  end
44
40
 
45
41
  # Prompt user to make a choice
46
- # TODO shouldn't care about case
47
- def choice(question, choices=["Y", "N"])
48
- prompt = "#{question} (#{choices.join("/")}): "
49
- response = ask(prompt.blue) { |q| q.in = choices }
50
-
51
- case response
52
- when "Y"
53
- response = true
54
- when "N"
55
- response = false
56
- end
57
-
58
- yield response if block_given?
59
- response
42
+ def choice(*args, &block)
43
+ Choice.new(*args, &block).run
60
44
  end
61
45
 
62
46
  def bundle
@@ -73,17 +57,18 @@ module Stairs
73
57
 
74
58
  # Set or update env var
75
59
  def env(name, value)
60
+ ENV[name] = value
76
61
  Stairs.configuration.env_adapter.set name, value
77
62
  end
78
63
 
79
64
  # Replace contents of file
80
65
  def write(string, filename)
81
- Util::FileUtils.write(string, filename)
66
+ Util::FileMutation.write(string, filename)
82
67
  end
83
68
 
84
69
  # Append line to file
85
70
  def write_line(string, filename)
86
- Util::FileUtils.write_line(string, filename)
71
+ Util::FileMutation.write_line(string, filename)
87
72
  end
88
73
 
89
74
  # Embed a step where step_name is a symbol that can be resolved to a class
@@ -109,5 +94,46 @@ module Stairs
109
94
  def stairs_info(message)
110
95
  puts message.light_black
111
96
  end
97
+
98
+ private
99
+
100
+ class Choice
101
+ # TODO: shouldn't care about case?
102
+ def initialize(question, choices=%w[Y N], &block)
103
+ @question = question
104
+ @choices = choices
105
+ @block = block
106
+ end
107
+
108
+ def run
109
+ block.call processed_response if block
110
+ processed_response
111
+ end
112
+
113
+ private
114
+
115
+ attr_reader :question, :choices, :block
116
+
117
+ def prompt
118
+ "#{question} (#{choices.join("/")}): "
119
+ end
120
+
121
+ def processed_response
122
+ @processed_response ||= case response
123
+ when "Y"
124
+ true
125
+ when "N"
126
+ false
127
+ else
128
+ response
129
+ end
130
+ end
131
+
132
+ def response
133
+ @reponse ||= Stairs::Util::CLI.collect prompt.blue do |value, i|
134
+ choices.include? value
135
+ end
136
+ end
137
+ end
112
138
  end
113
- end
139
+ end
@@ -11,4 +11,4 @@ module Stairs
11
11
  end
12
12
  end
13
13
  end
14
- end
14
+ end
data/lib/stairs/steps.rb CHANGED
@@ -2,4 +2,4 @@ module Stairs
2
2
  module Steps
3
3
  autoload :SecretToken, "stairs/steps/secret_token"
4
4
  end
5
- end
5
+ end
data/lib/stairs/tasks.rb CHANGED
@@ -14,4 +14,4 @@ module Stairs
14
14
  end
15
15
  end
16
16
 
17
- Stairs::Tasks.new.install!
17
+ Stairs::Tasks.new.install!
@@ -0,0 +1,50 @@
1
+ module Stairs
2
+ module Util
3
+ module CLI
4
+ class << self
5
+ def get(prompt)
6
+ print prompt
7
+ response = $stdin.gets.strip
8
+ response.present? ? response : nil
9
+ end
10
+
11
+ def collect(*args, &block)
12
+ Collector.new(*args, &block).run
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ class Collector
19
+ def initialize(prompt, options={}, &block)
20
+ @prompt = prompt
21
+ @options = options.reverse_merge required: true
22
+ @validator = block
23
+ end
24
+
25
+ def run
26
+ times, value = 0, nil
27
+
28
+ until valid?(value, times)
29
+ value = CLI.get(prompt.blue)
30
+ times += 1
31
+ end
32
+
33
+ value
34
+ end
35
+
36
+ private
37
+
38
+ def valid?(value, times)
39
+ if validator
40
+ validator.call(value, times)
41
+ else
42
+ !!value || (!options[:required] && times > 0)
43
+ end
44
+ end
45
+
46
+ attr_reader :prompt, :options, :validator
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,6 @@
1
1
  module Stairs
2
2
  module Util
3
- module FileUtils
3
+ module FileMutation
4
4
  class << self
5
5
  def replace_or_append(pattern, string, filename)
6
6
  if File.exists? filename
@@ -16,16 +16,21 @@ module Stairs
16
16
  end
17
17
 
18
18
  def write_line(string, filename)
19
- File.open filename, "a" do |file|
19
+ File.open filename, "a+" do |file|
20
+ # ensure file ends with newline before appending
21
+ last_line = file.each_line.reduce("") { |m, l| m = l }
22
+ file.puts "" unless last_line.index /(.*)\n/
23
+
20
24
  file.puts string
21
25
  end
22
26
  end
23
27
 
24
28
  def write(string, filename)
25
- File.truncate filename, 0 if File.exists? filename
26
- write_line string, filename
29
+ File.open filename, "w+" do |file|
30
+ file.puts string
31
+ end
27
32
  end
28
33
  end
29
34
  end
30
35
  end
31
- end
36
+ end
data/lib/stairs/util.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Stairs
2
2
  module Util
3
- autoload :FileUtils, "stairs/util/file_utils"
3
+ autoload :FileMutation, "stairs/util/file_mutation"
4
+ autoload :CLI, "stairs/util/cli"
4
5
  end
5
- end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module Stairs
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/stairs.rb CHANGED
@@ -20,4 +20,6 @@ module Stairs
20
20
  @configuration ||= Configuration.new
21
21
  end
22
22
  end
23
- end
23
+ end
24
+
25
+ require "stairs/railtie" if defined?(Rails)
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::Configuration do
4
+ subject { described_class.new }
5
+
6
+ describe "attributes" do
7
+ it "allows for configuration of env_adapter" do
8
+ subject.env_adapter = "test"
9
+ expect(subject.env_adapter).to eq "test"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::EnvAdapters::Dotenv do
4
+ subject { described_class.new }
5
+
6
+ describe ".present?" do
7
+ context "when rvm is installed" do
8
+ before { stub_const "Dotenv", double("dotenv") }
9
+
10
+ it "returns true" do
11
+ expect(described_class.present?).to be_true
12
+ end
13
+ end
14
+
15
+ context "when rvm is not installed" do
16
+ before { Object.send(:remove_const, :Dotenv) if defined? ::Dotenv }
17
+
18
+ it "returns true" do
19
+ expect(described_class.present?).to be_false
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#set" do
25
+ it "delegates to the well tested FileMutation util" do
26
+ name = "VAR_NAME"
27
+ value = "the_value"
28
+
29
+ Stairs::Util::FileMutation.should_receive(:replace_or_append).with(
30
+ Regexp.new("^#{name}=(.*)$"),
31
+ "#{name}=#{value}",
32
+ ".env",
33
+ )
34
+
35
+ subject.set(name, value)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::EnvAdapters::Rbenv do
4
+ subject { described_class.new }
5
+
6
+ describe ".present?" do
7
+ before { described_class.should_receive(:`).with("which rbenv-vars") }
8
+
9
+ context "when rbenv-vars is installed" do
10
+ before { $?.stub success?: true }
11
+
12
+ it "returns true" do
13
+ expect(described_class.present?).to be_true
14
+ end
15
+ end
16
+
17
+ context "when rbenv-vars is not installed" do
18
+ before { $?.stub success?: false }
19
+
20
+ it "returns true" do
21
+ expect(described_class.present?).to be_false
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#set" do
27
+ it "delegates to the well tested FileMutation util" do
28
+ name = "VAR_NAME"
29
+ value = "the_value"
30
+
31
+ Stairs::Util::FileMutation.should_receive(:replace_or_append).with(
32
+ Regexp.new("^#{name}=(.*)$"),
33
+ "#{name}=#{value}",
34
+ ".rbenv-vars",
35
+ )
36
+
37
+ subject.set(name, value)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe Stairs::EnvAdapters::RVM do
4
+ subject { described_class.new }
5
+
6
+ describe ".present?" do
7
+ before { described_class.should_receive(:`).with("which rvm") }
8
+
9
+ context "when rvm is installed" do
10
+ before { $?.stub success?: true }
11
+
12
+ it "returns true" do
13
+ expect(described_class.present?).to be_true
14
+ end
15
+ end
16
+
17
+ context "when rvm is not installed" do
18
+ before { $?.stub success?: false }
19
+
20
+ it "returns true" do
21
+ expect(described_class.present?).to be_false
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#set" do
27
+ it "delegates to the well tested FileMutation util" do
28
+ name = "VAR_NAME"
29
+ value = "the_value"
30
+
31
+ Stairs::Util::FileMutation.should_receive(:replace_or_append).with(
32
+ Regexp.new("^export #{name}=(.*)$"),
33
+ "export #{name}=#{value}",
34
+ ".rvmrc",
35
+ )
36
+
37
+ subject.set(name, value)
38
+ end
39
+ end
40
+ end