really-confy 0.1.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 (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: