envied 0.7.1 → 0.7.2

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