envied 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 501198780ddc5bf4762c604f90938baad9258ee4
4
- data.tar.gz: 3027a30c7815e8925246e7f14d3e7ffcc7a3bfef
3
+ metadata.gz: edcedc4d8c20ce9daebc7a4850197705ea88e0d1
4
+ data.tar.gz: 76db142dc7ae98934704145a788617b2c62948f0
5
5
  SHA512:
6
- metadata.gz: 826a2dfaa0ac7730c2fcaa14e14ea997b3c5b665774eed3ed91110e5950a29d982c6eb761e783a7eee4be6b3d1629483b068e5a1971b0d9eccabb9dc329c41c2
7
- data.tar.gz: ad53e3a25cb8a9c817eb14fb0a0f0a8d815fd8dd87b54dfdf0653f920dd6e8f963014d2efdb9fd56405b8ec67d40f5df36930752c6d7794bdce41d61df8c2556
6
+ metadata.gz: cd8ca76d1dda0a489be8b5c97ddf2d4c75a3e10a8eafe9d64a14c00432b40025948f62f782069228b64029494fb8544ea9b9c9e11ac02c96f032feb51132949d
7
+ data.tar.gz: 3e523db66a464e6b3a236908f5439d38d877aa6cf867ad5754e7555b1e20edb99e2f6f15f3757026c999d8cc7e43b550212a4ea0fa940bb1b9734fc1ea7cfdb7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # 0.7.2 / 2014-9-7
2
+
3
+ ## Added:
4
+ * extract-task: see all ENV-variables used in your project.
5
+
6
+ ```bash
7
+ $ bin/envied extract
8
+ Found 63 occurrences of 45 variables:
9
+ BUNDLE_GEMFILE
10
+ * config/boot.rb:4
11
+ * config/boot.rb:6
12
+ ...
13
+ ```
14
+
15
+ * version-task (i.e. bin/envied --version)
16
+
1
17
  # 0.7.1 / 2014-08-29
2
18
 
3
19
  * Total refactor (TM).
data/Gemfile CHANGED
@@ -4,3 +4,4 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'pry'
7
+ gem 'benchmark-ips'
data/README.md CHANGED
@@ -184,9 +184,11 @@ end
184
184
  ```bash
185
185
  $ envied help
186
186
  Commands:
187
+ envied --version # Shows version number
187
188
  envied check # Checks whether you environment contains the defined variables
188
189
  envied check:heroku # Checks whether a Heroku config contains the defined variables
189
190
  envied check:heroku:binstub # Generates a shell script for the check:heroku-task
191
+ envied extract # Shows candidate variables (i.e. occurences of ENV['X'])
190
192
  envied help [COMMAND] # Describe available commands or one specific command
191
193
  envied init # Generates a default Envfile in the current working directory
192
194
  envied init:rails # Generate all files needed for a Rails project
data/lib/envied.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'envied/version'
2
2
  require 'envied/cli'
3
+ require 'envied/env_proxy'
3
4
  require 'envied/coercer'
4
5
  require 'envied/variable'
5
6
  require 'envied/configuration'
@@ -9,14 +10,21 @@ class ENVied
9
10
  attr_reader :env, :config
10
11
  end
11
12
 
12
- def self.require(*groups)
13
- @config ||= Configuration.load
14
- @env ||= EnvProxy.new(@config, groups: required_groups(*groups))
15
-
13
+ def self.require(*args)
14
+ env!(*args)
16
15
  error_on_missing_variables!
17
16
  error_on_uncoercible_variables!
18
17
  end
19
18
 
19
+ def self.env!(*args)
20
+ @env = begin
21
+ options = args.last.is_a?(Hash) ? args.pop : {}
22
+ config = options.fetch(:config) { Configuration.load }
23
+ groups = required_groups(*args)
24
+ EnvProxy.new(config, groups: groups)
25
+ end
26
+ end
27
+
20
28
  def self.error_on_missing_variables!
21
29
  names = env.missing_variables.map(&:name)
22
30
  raise "The following environment variables should be set: #{names * ', '}" if names.any?
data/lib/envied/cli.rb CHANGED
@@ -1,10 +1,32 @@
1
1
  require 'thor'
2
+ require 'envied/env_var_extractor'
2
3
 
3
4
  class ENVied
4
5
  class Cli < Thor
5
6
  include Thor::Actions
6
7
  source_root File.expand_path('../templates', __FILE__)
7
8
 
9
+ desc "--version", "Shows version number"
10
+ def version
11
+ puts ENVied::VERSION
12
+ end
13
+ map %w(-v --version) => :version
14
+
15
+ desc "extract", "Shows candidate variables (i.e. occurences of ENV['X'])"
16
+ option :globs, type: :array, default: ENVied::EnvVarExtractor.defaults[:globs], banner: "*.* lib/*"
17
+ def extract
18
+ var_occurences = ENVied::EnvVarExtractor.new(globs: options[:globs]).extract
19
+
20
+ puts "Found %d occurrences of %d variables:" % [var_occurences.values.flatten.size, var_occurences.size]
21
+ var_occurences.sort.each do |var, occs|
22
+ puts var
23
+ occs.sort_by{|i| i[:path].size }.each do |occ|
24
+ puts "* %s:%s" % occ.values_at(:path, :line)
25
+ end
26
+ puts
27
+ end
28
+ end
29
+
8
30
  desc "init", "Generates a default Envfile in the current working directory"
9
31
  def init
10
32
  puts "Writing new Envfile to #{File.expand_path('Envfile')}"
@@ -17,9 +39,9 @@ class ENVied
17
39
  template("rails-initializer.tt", 'config/initializers/envied.rb')
18
40
  end
19
41
 
20
- desc "check", "Checks whether you environment contains the defined variables"
42
+ desc "check", "Checks whether you environment contains required variables"
21
43
  long_desc <<-LONG
22
- Checks whether defined variables are present and valid in your shell.
44
+ Checks whether required variables are present and valid in your shell.
23
45
 
24
46
  On success the process will exit with status 0.
25
47
  Else the missing/invalid variables will be shown, and the process will exit with status 1.
@@ -30,7 +52,7 @@ class ENVied
30
52
  puts "All variables for group(s) #{options[:groups]} are present and valid"
31
53
  end
32
54
 
33
- desc "check:heroku", "Checks whether a Heroku config contains the defined variables"
55
+ desc "check:heroku", "Checks whether a Heroku config contains required variables"
34
56
 
35
57
  long_desc <<-LONG
36
58
  Checks the config of your Heroku app against the local Envfile.
@@ -38,7 +38,7 @@ class ENVied::Coercer
38
38
 
39
39
  def self.supported_types
40
40
  @supported_types ||= begin
41
- [:hash, :array, :time, :date, :symbol, :boolean, :integer, :string]
41
+ [:hash, :array, :time, :date, :symbol, :boolean, :integer, :string].sort
42
42
  end
43
43
  end
44
44
 
@@ -59,6 +59,10 @@ class ENVied::Coercer
59
59
  self.class.supported_type?(type)
60
60
  end
61
61
 
62
+ def supported_types
63
+ self.class.supported_types
64
+ end
65
+
62
66
  def coercer
63
67
  @coercer ||= Coercible::Coercer.new[String]
64
68
  end
@@ -1,14 +1,17 @@
1
1
  class ENVied
2
2
  class Configuration
3
- attr_reader :current_group, :defaults_enabled
3
+ attr_reader :current_group, :defaults_enabled, :coercer
4
4
 
5
- def initialize(options = {})
5
+ def initialize(options = {}, &block)
6
6
  @defaults_enabled = options.fetch(:enable_defaults, false)
7
+ @coercer = options.fetch(:coercer, Coercer.new)
8
+ instance_eval(&block) if block_given?
7
9
  end
8
10
 
9
- def self.load
10
- new.tap do |v|
11
- v.instance_eval(File.read(File.expand_path('Envfile')))
11
+ def self.load(options = {})
12
+ envfile = File.expand_path('Envfile')
13
+ new(options).tap do |v|
14
+ v.instance_eval(File.read(envfile), envfile)
12
15
  end
13
16
  end
14
17
 
@@ -23,6 +26,10 @@ class ENVied
23
26
  end
24
27
 
25
28
  def variable(name, type = :String, options = {})
29
+ unless coercer.supported_type?(type)
30
+ raise ArgumentError,
31
+ "Variable type (of #{name}) should be one of #{coercer.supported_types}"
32
+ end
26
33
  options[:group] = current_group if current_group
27
34
  variables << ENVied::Variable.new(name, type, options)
28
35
  end
@@ -39,71 +46,4 @@ class ENVied
39
46
  end
40
47
  end
41
48
 
42
- # Responsible for anything related to the ENV.
43
- class EnvProxy
44
- attr_reader :config, :coercer, :groups
45
-
46
- def initialize(config, options = {})
47
- @config = config
48
- @coercer = options.fetch(:coercer, ENVied::Coercer.new)
49
- @groups = options.fetch(:groups, [])
50
- end
51
-
52
- def missing_variables
53
- variables.select(&method(:missing?))
54
- end
55
-
56
- def uncoercible_variables
57
- variables.reject(&method(:coerced?)).reject(&method(:coercible?))
58
- end
59
-
60
- def variables
61
- @variables ||= begin
62
- config.variables.select {|v| groups.include?(v.group) }
63
- end
64
- end
65
-
66
- def variables_by_name
67
- Hash[variables.map {|v| [v.name, v] }]
68
- end
69
-
70
- def [](name)
71
- coerce(variables_by_name[name.to_sym])
72
- end
73
-
74
- def has_key?(name)
75
- variables_by_name[name.to_sym]
76
- end
77
-
78
- def env_value_of(var)
79
- ENV[var.name.to_s]
80
- end
81
-
82
- def default_value_of(var)
83
- var.default_value(ENVied, var)
84
- end
85
-
86
- def value_to_coerce(var)
87
- return env_value_of(var) unless env_value_of(var).nil?
88
- config.defaults_enabled? ? default_value_of(var) : nil
89
- end
90
-
91
- def coerce(var)
92
- coerced?(var) ?
93
- value_to_coerce(var) :
94
- coercer.coerce(value_to_coerce(var), var.type)
95
- end
96
-
97
- def coercible?(var)
98
- coercer.coercible?(value_to_coerce(var), var.type)
99
- end
100
-
101
- def missing?(var)
102
- value_to_coerce(var).nil?
103
- end
104
-
105
- def coerced?(var)
106
- coercer.coerced?(value_to_coerce(var))
107
- end
108
- end
109
49
  end
@@ -0,0 +1,69 @@
1
+ class ENVied
2
+ # Responsible for anything related to the ENV.
3
+ class EnvProxy
4
+ attr_reader :config, :coercer, :groups
5
+
6
+ def initialize(config, options = {})
7
+ @config = config
8
+ @coercer = options.fetch(:coercer, ENVied::Coercer.new)
9
+ @groups = options.fetch(:groups, [])
10
+ end
11
+
12
+ def missing_variables
13
+ variables.select(&method(:missing?))
14
+ end
15
+
16
+ def uncoercible_variables
17
+ variables.reject(&method(:coerced?)).reject(&method(:coercible?))
18
+ end
19
+
20
+ def variables
21
+ @variables ||= begin
22
+ config.variables.select {|v| groups.include?(v.group) }
23
+ end
24
+ end
25
+
26
+ def variables_by_name
27
+ Hash[variables.map {|v| [v.name, v] }]
28
+ end
29
+
30
+ def [](name)
31
+ coerce(variables_by_name[name.to_sym])
32
+ end
33
+
34
+ def has_key?(name)
35
+ variables_by_name[name.to_sym]
36
+ end
37
+
38
+ def env_value_of(var)
39
+ ENV[var.name.to_s]
40
+ end
41
+
42
+ def default_value_of(var)
43
+ var.default_value(ENVied, var)
44
+ end
45
+
46
+ def value_to_coerce(var)
47
+ return env_value_of(var) unless env_value_of(var).nil?
48
+ config.defaults_enabled? ? default_value_of(var) : nil
49
+ end
50
+
51
+ def coerce(var)
52
+ coerced?(var) ?
53
+ value_to_coerce(var) :
54
+ coercer.coerce(value_to_coerce(var), var.type)
55
+ end
56
+
57
+ def coercible?(var)
58
+ coercer.coercible?(value_to_coerce(var), var.type)
59
+ end
60
+
61
+ def missing?(var)
62
+ value_to_coerce(var).nil?
63
+ end
64
+
65
+ def coerced?(var)
66
+ coercer.coerced?(value_to_coerce(var))
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,76 @@
1
+ class ENVied
2
+ class EnvVarExtractor
3
+ def self.defaults
4
+ @defaults ||= begin
5
+ {
6
+ extensions: %w(ru thor rake rb yml ruby yaml erb builder markerb haml),
7
+ globs: %w(*.* Thorfile Rakefile {app,config,db,lib,script,test,spec}/*)
8
+ }
9
+ end
10
+ end
11
+
12
+ def defaults
13
+ self.class.defaults
14
+ end
15
+
16
+ def self.env_var_re
17
+ @env_var_re ||= begin
18
+ /^[^\#]* # not matching comments
19
+ ENV
20
+ (?: # non-capture...
21
+ \[['"] | # either ENV['
22
+ \.fetch\(['"] # or ENV.fetch('
23
+ )
24
+ ([a-zA-Z_]+) # capture variable name
25
+ /x
26
+ end
27
+ end
28
+
29
+ attr_reader :globs, :extensions
30
+
31
+ def initialize(options = {})
32
+ @globs = options.fetch(:globs, self.defaults[:globs])
33
+ @extensions = options.fetch(:extensions, self.defaults[:extensions])
34
+ end
35
+
36
+ def self.extract_from(globs, options = {})
37
+ new(options.merge(globs: Array(globs))).extract
38
+ end
39
+
40
+
41
+ # Extract all keys recursively from files found via `globs`.
42
+ # Any occurence of `ENV['A']` or `ENV.fetch('A')` in code (not in comments), will result
43
+ # in 'A' being extracted.
44
+ #
45
+ # @param globs [Array<String>] the collection of globs
46
+ #
47
+ # @example
48
+ # EnvVarExtractor.new.extract(*%w(app lib))
49
+ # # => {'A' => [{:path => 'app/models/user.rb', :line => 2}, {:path => ..., :line => ...}],
50
+ # 'B' => [{:path => 'config/application.rb', :line => 12}]}
51
+ #
52
+ # @return [<Hash{String => Array<String => Array>}>] the list of items.
53
+ def extract(globs = self.globs)
54
+ results = Hash.new { |hash, key| hash[key] = [] }
55
+
56
+ Array(globs).each do |glob|
57
+ Dir.glob(glob).each do |item|
58
+ next if File.basename(item)[0] == ?.
59
+
60
+ if File.directory?(item)
61
+ results.merge!(extract("#{item}/*"))
62
+ else
63
+ next unless extensions.detect {|ext| File.extname(item)[ext] }
64
+ File.readlines(item, :encoding=>"UTF-8").each_with_index do |line, ix|
65
+ if variable = line[self.class.env_var_re, 1]
66
+ results[variable] << { :path => item, :line => ix.succ }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ results
74
+ end
75
+ end
76
+ end
@@ -1,3 +1,3 @@
1
1
  class ENVied
2
- VERSION = '0.7.1'
2
+ VERSION = '0.7.2'
3
3
  end
data/spec/envied_spec.rb CHANGED
@@ -16,7 +16,7 @@ describe ENVied do
16
16
  end
17
17
 
18
18
  def reset_configuration
19
- ENVied.instance_eval { @config = nil }
19
+ @config = ENVied::Configuration.new
20
20
  end
21
21
 
22
22
  def reset_env
@@ -30,20 +30,21 @@ describe ENVied do
30
30
  self
31
31
  end
32
32
 
33
+ def config
34
+ @config
35
+ end
36
+
33
37
  def configure(options = {}, &block)
34
- ENVied.instance_eval do
35
- @config = ENVied::Configuration.new(options).tap{|c| c.instance_eval(&block)}
36
- end
38
+ @config = ENVied::Configuration.new(options, &block)
37
39
  self
38
40
  end
39
41
 
40
42
  def configured_with(hash = {})
41
- config = ENVied::Configuration.new.tap do |c|
43
+ @config = ENVied::Configuration.new.tap do |c|
42
44
  hash.each do |name, type|
43
45
  c.variable(name, *type)
44
46
  end
45
47
  end
46
- ENVied.instance_eval{ @config = config }
47
48
  self
48
49
  end
49
50
 
@@ -56,16 +57,23 @@ describe ENVied do
56
57
  and_ENV
57
58
  end
58
59
 
60
+ def envied_require(*args)
61
+ options = args.last.is_a?(Hash) ? args.pop : {}
62
+ options[:config] = options[:config] || config
63
+
64
+ ENVied.require(*args, options)
65
+ end
66
+
59
67
  it 'responds to configured variables' do
60
68
  configured_with(a: :Integer).and_ENV({'a' => '1'})
61
- described_class.require
69
+ envied_require
62
70
 
63
71
  expect(described_class).to respond_to :a
64
72
  end
65
73
 
66
74
  it 'responds not to unconfigured variables' do
67
75
  unconfigured.and_ENV({'A' => '1'})
68
- described_class.require
76
+ envied_require
69
77
 
70
78
  expect(described_class).to_not respond_to :B
71
79
  end
@@ -75,7 +83,7 @@ describe ENVied do
75
83
 
76
84
  specify do
77
85
  expect {
78
- ENVied.require
86
+ envied_require
79
87
  }.to raise_error(/The following environment variables should be set: a/)
80
88
  end
81
89
  end
@@ -85,11 +93,19 @@ describe ENVied do
85
93
 
86
94
  specify do
87
95
  expect {
88
- ENVied.require
96
+ envied_require
89
97
  }.to raise_error(/A \('NaN' can't be coerced to Integer/)
90
98
  end
91
99
  end
92
100
 
101
+ context 'configuring' do
102
+ it 'raises error when configuring variable of unknown type' do
103
+ expect {
104
+ configured_with(A: :Fixnum)
105
+ }.to raise_error
106
+ end
107
+ end
108
+
93
109
  context 'bug: default value "false" is not coercible' do
94
110
  before {
95
111
  configure(enable_defaults: true) do
@@ -99,19 +115,18 @@ describe ENVied do
99
115
 
100
116
  specify do
101
117
  expect {
102
- ENVied.require
118
+ envied_require
103
119
  }.not_to raise_error
104
120
  end
105
121
  end
106
122
 
107
123
  describe 'defaults' do
108
124
  describe 'setting' do
109
- subject { described_class.config }
110
- #subject { ENVied::Configuration.new }
125
+ subject { config }
111
126
 
112
- #it 'is disabled by default' do
113
- # expect(subject.defaults_enabled?).to_not be
114
- #end
127
+ it 'is disabled by default' do
128
+ expect(subject.defaults_enabled?).to_not be
129
+ end
115
130
 
116
131
  it 'can be enabled via #configure' do
117
132
  configure(enable_defaults: true){ }
@@ -137,7 +152,7 @@ describe ENVied do
137
152
  configure(enable_defaults: true) do
138
153
  variable :A, :Integer, default: '1'
139
154
  end
140
- described_class.require
155
+ envied_require
141
156
 
142
157
  expect(described_class.A).to eq 1
143
158
  end
@@ -146,7 +161,7 @@ describe ENVied do
146
161
  configure(enable_defaults: true) do
147
162
  variable :A, :Integer, default: proc { "1" }
148
163
  end
149
- described_class.require
164
+ envied_require
150
165
 
151
166
  expect(described_class.A).to eq 1
152
167
  end
@@ -157,7 +172,7 @@ describe ENVied do
157
172
  end.and_no_ENV
158
173
 
159
174
  expect {
160
- described_class.require
175
+ envied_require
161
176
  }.to raise_error
162
177
  end
163
178
 
@@ -165,7 +180,7 @@ describe ENVied do
165
180
  configure(enable_defaults: true) do
166
181
  variable :A, :Integer, default: "1"
167
182
  end.and_ENV('A' => '2')
168
- described_class.require
183
+ envied_require
169
184
 
170
185
  expect(described_class.A).to eq 2
171
186
  end
@@ -175,7 +190,7 @@ describe ENVied do
175
190
  variable :A, :Integer
176
191
  variable :B, :Integer, default: proc {|env| env.A * 2 }
177
192
  end.and_ENV('A' => '1')
178
- described_class.require
193
+ envied_require
179
194
 
180
195
  expect(described_class.B).to eq 2
181
196
  end
@@ -196,19 +211,19 @@ describe ENVied do
196
211
 
197
212
  it 'is required when requiring the group' do
198
213
  expect {
199
- described_class.require(:foo)
214
+ envied_require(:foo)
200
215
  }.to raise_error(/bar/)
201
216
  end
202
217
 
203
218
  it 'is not required when requiring another group' do
204
219
  expect {
205
- described_class.require(:bat)
220
+ envied_require(:bat)
206
221
  }.to_not raise_error
207
222
  end
208
223
 
209
224
  it 'wont define non-required variables on ENVied' do
210
225
  stub_const("ENV", {'moar' => 'yes'})
211
- described_class.require(:default)
226
+ envied_require(:default)
212
227
 
213
228
  expect {
214
229
  described_class.bar
@@ -218,7 +233,7 @@ describe ENVied do
218
233
  it 'requires variables without a group when requiring the default group' do
219
234
  [:default, 'default'].each do |groups|
220
235
  expect {
221
- described_class.require(*groups)
236
+ envied_require(*groups)
222
237
  }.to raise_error(/moar/)
223
238
  end
224
239
  end
@@ -230,7 +245,7 @@ describe ENVied do
230
245
  variable :foo, :Hash
231
246
  variable :bar, :Hash
232
247
  end.and_ENV('foo' => 'a=1&b=&c', 'bar' => '')
233
- ENVied.require
248
+ envied_require
234
249
  end
235
250
 
236
251
  it 'yields hash from string' do
@@ -251,7 +266,7 @@ describe ENVied do
251
266
  it 'has no default by default' do
252
267
  # fixes a bug where variables of type :Hash had a default even
253
268
  # when none was configured.
254
- expect { ENVied.require(:default) }.to raise_error
269
+ expect { envied_require }.to raise_error
255
270
  end
256
271
  end
257
272
  end
@@ -261,7 +276,7 @@ describe ENVied do
261
276
  configure do
262
277
  variable :moar, :Array
263
278
  end.and_ENV('moar' => 'a, b, and\, c')
264
- ENVied.require
279
+ envied_require
265
280
  end
266
281
 
267
282
  it 'yields array from string' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: envied
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gert Goet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-28 00:00:00.000000000 Z
11
+ date: 2014-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coercible
@@ -117,6 +117,8 @@ files:
117
117
  - lib/envied/cli.rb
118
118
  - lib/envied/coercer.rb
119
119
  - lib/envied/configuration.rb
120
+ - lib/envied/env_proxy.rb
121
+ - lib/envied/env_var_extractor.rb
120
122
  - lib/envied/templates/Envfile.tt
121
123
  - lib/envied/templates/heroku-env-check.tt
122
124
  - lib/envied/templates/rails-initializer.tt