really-confy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/really_confy.rb +235 -0
  3. metadata +73 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c4a5653e5cc890738cf6afce11f7da262f814e78
4
+ data.tar.gz: 38f63ff32abbff0de095ebe6335d909ad8d935a1
5
+ SHA512:
6
+ metadata.gz: 5af1c19f0ca561a5fe9dcd52fe019ad5e2bc5a20611934156f60ccc3b078daf3e8f5af37cc451b85b9d11c5d93e762f1d899c9f1ce012e78b736ee72856baa50
7
+ data.tar.gz: 99c36e189f874ede4a7226943a7fcd1530cc3235d3b08ac1234b8a81644fdca1af16d4766e7b987c61c7bc31d7b41febad54c3f9cef8560525a77f8a6ba5160c
@@ -0,0 +1,235 @@
1
+ require 'yaml'
2
+ require 'rainbow'
3
+ require 'active_support/core_ext/hash/deep_merge'
4
+
5
+ class ReallyConfy
6
+
7
+ DEFAULT_OPTIONS = {
8
+ # look for config files under this directory
9
+ config_path: './config',
10
+ # load and reucrisvely merge the config from these files, in the given order (duplicate keys
11
+ # in later files will override those in earlier files)
12
+ config_files: [
13
+ 'config.yml',
14
+ 'config.secret.yml',
15
+ 'config.local.yml'
16
+ ],
17
+ # load will raise a ConfigError if these files are in the git repo
18
+ local_config_files: [
19
+ 'config.secret.yml',
20
+ 'config.local.yml'
21
+ ],
22
+ # load will raise a ConfigError if these files are missing
23
+ required_config_files: [
24
+ 'config.yml',
25
+ 'config.secret.yml'
26
+ ],
27
+ # load will print a warning if these files are missing
28
+ suggested_config_files: [
29
+ 'config.secret.yml'
30
+ ],
31
+ # the environment key will be selected based on this ENV variable
32
+ env_var_name: 'CONFY_ENV',
33
+ # use Symbols instead of Strings for all keys in the config Hash
34
+ symbol_keys: false,
35
+ # load will return an ActiveSupport::HashWithIndifferentAccess instead of a Hash
36
+ indifferent_keys: false,
37
+ # suppress output to stdout/stderr
38
+ quiet: false,
39
+ # enable colorized output; nil means 'auto', which enables color by default unless the
40
+ # terminal doesn't support it
41
+ color: nil,
42
+ # force the ruby interpreter to exit if ReallyConfy encounters an error during load
43
+ # ... not a good idea to use this with quiet:true unless you know exactly what you're doing
44
+ exit_on_error: false
45
+ }
46
+
47
+ attr_accessor :config_files
48
+ attr_accessor :config_path
49
+ attr_accessor :local_config_files
50
+ attr_accessor :required_config_files
51
+ attr_accessor :suggested_config_files
52
+ attr_accessor :env_var_name
53
+ attr_accessor :env
54
+
55
+ def initialize(opts = {})
56
+ setup(opts)
57
+ end
58
+
59
+ def setup(opts)
60
+ read_opts_into_instance_vars(opts, DEFAULT_OPTIONS.keys)
61
+ @env_var_name = @env_var_name.to_s if @env_var_name # ENV keys are always strings
62
+ @env = opts[:env] if opts.has_key? :env
63
+
64
+ @rainbow = Rainbow.new
65
+ @rainbow.enabled = @color unless @color.nil?
66
+
67
+ ensure_required_config_files_exist
68
+ check_suggested_config_files_exist
69
+ ensure_local_config_files_are_not_in_git
70
+
71
+ if @symbol_keys && @indifferent_keys
72
+ fail ArgumentError,
73
+ ":symbol_keys and :indifferent_keys options cannot be used together!"
74
+ end
75
+
76
+ require 'active_support/core_ext/hash/keys' if @symbol_keys
77
+ require 'active_support/core_ext/hash/indifferent_access' if @indifferent_keys
78
+ end
79
+
80
+ def load
81
+ existing_config_files =
82
+ config_files.select{|file| File.exists? full_path_to_config_file(file) }
83
+
84
+ print_info "Loading config from #{existing_config_files.inspect} for #{env.inspect}"+
85
+ " environment..."
86
+
87
+ multi_env_configs =
88
+ existing_config_files.map{|file| load_config_file(file) }
89
+
90
+ unless multi_env_configs.any?{|config| config.is_a?(Hash) && config.has_key?(env) }
91
+ fail ConfigError, "#{env.inspect} is not a valid environment! None of the loaded configs"+
92
+ " had a top-level #{env.inspect} key. All configurations should be nested under top"+
93
+ " level keys corresponding to environment names (e.g. 'test', 'development', ...)"
94
+ end
95
+
96
+ env_configs = multi_env_configs.map{|multi_env_config|
97
+ multi_env_config.fetch('DEFAULTS', {}).deep_merge multi_env_config.fetch(env, {})
98
+ }
99
+ merged_config = env_configs.reduce{|merged_config, config| merged_config.deep_merge(config) }
100
+
101
+ merged_config['env'] ||= env
102
+
103
+ merged_config.deep_symbolize_keys! if @symbol_keys
104
+ merged_config = merged_config.with_indifferent_access if @indifferent_keys
105
+
106
+ merged_config
107
+ rescue => e
108
+ header = "!!! Couldn't load config for #{env.inspect} environment! !!!"
109
+ print_error ""
110
+ print_error "!"*header.length
111
+ print_error "#{header}"
112
+ print_error "!"*header.length
113
+ print_error ""
114
+ print_error "#{e}"
115
+ print_error ""
116
+ print_error "!"*header.length
117
+ print_error ""
118
+ if @exit_on_error
119
+ print_error "Aborting because the :exit_on_error option is true!"
120
+ exit 666
121
+ end
122
+ raise e
123
+ end
124
+
125
+ def load_config_file(file)
126
+ full_path = full_path_to_config_file(file)
127
+ multi_env_config = (YAML.load_file full_path)
128
+
129
+ # YAML.load_file will return false if given an empty file to load
130
+ return {} if multi_env_config == false
131
+
132
+ unless multi_env_config.is_a? Hash
133
+ fail ConfigError, "Config file #{file.inspect} must contain a YAML-encoded Hash, but"+
134
+ " it seems to contain a #{multi_env_config.class}"
135
+ end
136
+
137
+ multi_env_config
138
+ end
139
+
140
+ private
141
+
142
+ def full_path_to_config_file(file)
143
+ File.absolute_path(relative_path_to_config_file(file))
144
+ end
145
+
146
+ def relative_path_to_config_file(file)
147
+ File.join(config_path,file)
148
+ end
149
+
150
+ def ensure_required_config_files_exist
151
+ required_config_files.each do |file|
152
+ full_path = full_path_to_config_file(file)
153
+ unless File.exists? full_path
154
+ fail ConfigError, "Required config file #{file.inspect} does not exist under"+
155
+ " #{full_path.inspect}!"
156
+ end
157
+ end
158
+ end
159
+
160
+ def check_suggested_config_files_exist
161
+ (suggested_config_files - required_config_files).each do |file|
162
+ full_path = full_path_to_config_file(file)
163
+ unless File.exists? full_path
164
+ print_warning "WARNING: Config file #{file.inspect} does not exist!"
165
+ end
166
+ end
167
+ end
168
+
169
+ def ensure_local_config_files_are_not_in_git
170
+ return unless git_available?
171
+ return unless we_are_in_a_git_repo?
172
+
173
+ local_config_files.each do |file|
174
+ relative_path = relative_path_to_config_file(file)
175
+ if file_is_in_git? relative_path
176
+ fail ConfigError, "Local config file #{relative_path.inspect} exists in the git repo!"
177
+ " Remove this file from your git repo and add it to your .gitignore"
178
+ end
179
+ end
180
+ end
181
+
182
+ def git_available?
183
+ begin
184
+ `git`
185
+ true
186
+ rescue Errno::ENOENT => e
187
+ false
188
+ end
189
+ end
190
+
191
+ def we_are_in_a_git_repo?
192
+ File.exists? '.git'
193
+ end
194
+
195
+ def file_is_in_git?(file)
196
+ git_cmd = "git ls-tree HEAD #{file}"
197
+ git_output = `#{git_cmd}`.strip
198
+
199
+ not git_output.empty?
200
+ end
201
+
202
+ def print_error(err)
203
+ $stderr.puts @rainbow.wrap(err).red.bright
204
+ end
205
+
206
+ def print_warning(warning)
207
+ $stderr.puts @rainbow.wrap(warning).yellow.bright
208
+ end
209
+
210
+ def print_info(info)
211
+ $stdout.puts @rainbow.wrap(info).cyan
212
+ end
213
+
214
+ def env
215
+ if @env
216
+ return @env
217
+ elsif ENV.has_key? env_var_name
218
+ ENV.fetch(env_var_name)
219
+ else
220
+ fail ConfigError, "Configuration environment couldn't be determined --"+
221
+ " ENV[#{env_var_name.inspect}] is not set! Try running with"+
222
+ " `#{env_var_name.inspect}=yourenvname ...`"
223
+ end
224
+ end
225
+
226
+ def read_opts_into_instance_vars(opts, instance_var_keys)
227
+ instance_var_keys.each do |key|
228
+ instance_variable_set(:"@#{key}", opts.fetch(key, ReallyConfy::DEFAULT_OPTIONS.fetch(key)))
229
+ end
230
+ end
231
+
232
+ class ConfigError < StandardError
233
+ end
234
+
235
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: really-confy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Zukowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rainbow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ description:
42
+ email: mzukowski@adknowledge.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/really_confy.rb
48
+ homepage:
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.4.8
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Simple YAML configuration loader
72
+ test_files: []
73
+ has_rdoc: