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
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