layeredyamlconfig 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,36 @@
1
+ # LayeredYAMLConfig Change History
2
+
3
+ ## 1.4.3
4
+
5
+ * first release on rubygems.org
6
+ * bring test suite to 100% coverage
7
+
8
+ ## 1.4.2
9
+
10
+ * remove soft breakpoints. I make this mistake so often I wrote a Perl test module to catch them: https://metacpan.org/release/Test-NoBreakpoints. Time for a Ruby version methinks.
11
+
12
+ ## 1.4.1
13
+
14
+ * replace dependency on 'erbuis' (ERB's sophisticated European cousin) with 'erubis'
15
+
16
+ ## 1.4.0
17
+
18
+ * added ::reset and ::reset_all methods to reset per-class options to their default
19
+ * added ERB Template evaluation of leaf nodes
20
+
21
+ ## 1.3.0
22
+
23
+ * Added #to_hash
24
+
25
+ ## 1.2.0
26
+
27
+ * added #files read accessor to get a list of files the config was constructed from
28
+
29
+ ## 1.1.0
30
+
31
+ * Changes to symbolized keys
32
+ * We now return an object of our class and delegate key lookup to the underlying hash
33
+
34
+ ## 1.0.0
35
+
36
+ * Initial Release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2012, 2013 James FitzGibbon <james@nadt.net>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to
7
+ deal in the Software without restriction, including without limitation the
8
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+ sell copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,46 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ LICENSE.txt
5
+ Rakefile
6
+ layeredyamlconfig.gemspec
7
+ lib/layeredyamlconfig.rb
8
+ lib/layeredyamlconfig/array_traverse.rb
9
+ lib/layeredyamlconfig/core.rb
10
+ lib/layeredyamlconfig/hash_traverse.rb
11
+ lib/layeredyamlconfig/templates.rb
12
+ test/minitest_helper.rb
13
+ test/ex1.yaml
14
+ test/ex2.yaml
15
+ test/ex3.yaml
16
+ test/ex4.yaml
17
+ test/ex5.yaml
18
+ test/ex6.yaml
19
+ test/ex7.yaml
20
+ test/ex8.yaml
21
+ test/ex9.yaml
22
+ test/ex10.yaml
23
+ test/ex11.yaml
24
+ test/ex12.yaml
25
+ test/ex13.yaml
26
+ test/ex14.yaml
27
+ test/ex15.yaml
28
+ test/ex16.yaml
29
+ test/ex17.yaml
30
+ test/ex18.yaml
31
+ test/exbad1.yaml
32
+ test/exbad2.yaml
33
+ test/test_addlayer.rb
34
+ test/test_clear.rb
35
+ test/test_comments.rb
36
+ test/test_constructor.rb
37
+ test/test_deepmerge.rb
38
+ test/test_erb_array.rb
39
+ test/test_erb_hash.rb
40
+ test/test_erb_empty.rb
41
+ test/test_erb_multi.rb
42
+ test/test_erb.rb
43
+ test/test_files.rb
44
+ test/test_invalid.rb
45
+ test/test_multi.rb
46
+ test/test_tohash.rb
data/README.txt ADDED
@@ -0,0 +1,247 @@
1
+ # LayeredYAMLConfig
2
+
3
+ home :: https://github.com/jf647/LayeredYAMLConfig
4
+
5
+ ## SUMMARY:
6
+
7
+ Ruby configuration library that layers multiple YAML files on top of each
8
+ other with ERB evalution.
9
+
10
+ ## DESCRIPTION:
11
+
12
+ LayeredYAMLConfig provides a simple config file that supports multiple
13
+ layers. Values in the right or uppermost layers override values in lower
14
+ layers. This makes it easy to share configuration without duplication while
15
+ still allowing what needs to be different to vary.
16
+
17
+ For example:
18
+
19
+ program.default.conf
20
+ program.server_foo.conf
21
+ program.site_bar.conf
22
+ program.conf
23
+
24
+ Optionally, leaf nodes can be evaluated using as ERB templates, feeding the
25
+ configuration into itself.
26
+
27
+ ## Synopsis
28
+
29
+ ```ruby
30
+ class MyConfig < LayeredYAMLConfig
31
+ end
32
+
33
+ MyConfig.skipbad = true
34
+ cfg = MyConfig.instance( 'ex7.yaml', 'ex8.yaml', 'ex9.yaml', 'ex10.yaml' )
35
+ puts cfg[:foo]['bar']
36
+ puts cfg['foo'][:gzonk]
37
+ ```
38
+
39
+ ex7.yaml:
40
+ ```yaml
41
+ ---
42
+ foo:
43
+ bar: baz
44
+ ```
45
+
46
+ ex8.yaml:
47
+ ```yaml
48
+ ---
49
+ foo:
50
+ gzonk: quux
51
+ ```
52
+
53
+ ex10.yaml:
54
+ ```text
55
+ This is not a YAML file
56
+ ```
57
+
58
+ ex16.yaml:
59
+ ```text
60
+ ---
61
+ a: d
62
+ b: e
63
+ c: f
64
+ g:
65
+ - <%= @cfg[:a] %>
66
+ - <%= @cfg[:b] %>
67
+ - <%= @cfg[:c] %>
68
+ ```
69
+
70
+ To use LayeredYAMLConfig, create a new class that inherits from it. The new
71
+ class is a singleton that can only be constructed the first time ::instance
72
+ is called. Pass one or more YAML filenames which will be read and deep
73
+ merged on top of each other in left-to-right order.
74
+
75
+ Files that are missing are skipped by default. Files that are bad (i.e. do
76
+ not parse as valid YAML) cause an exception to be thrown. This behaviour
77
+ can be overridden by calling ::skipbad = true or ::skipmissing = true before
78
+ constructing the configuration.
79
+
80
+ The type of the returned object is your subordinate class, but #[] and #[]=
81
+ are delegated to the contained hash, which is an
82
+ ActiveSupport::HashWithIndifferentAccess. This means you can use strings or
83
+ symbols interchangeably to access elements of the hash.
84
+
85
+ ## Adding Layers after Instance Construction
86
+
87
+ Using #add, you can add one or more layers that are deep merged into the
88
+ existing config.
89
+
90
+ ## Converting to a Hash
91
+
92
+ Call #to_hash to return a symbolized hash representation of the
93
+ configuration object
94
+
95
+ ## ERB Templates
96
+
97
+ To enable template evaluation, call ::templates = true on the class:
98
+
99
+ ```ruby
100
+ class OurConfig < LayeredYAMLConfig
101
+ end
102
+ OurConfig.templates = true
103
+ OurConfig.instance('file_containing_erb.yaml')
104
+ ```
105
+
106
+ Every time #add is called (either directly or implicitly during creation),
107
+ the configuration tree is traversed. Any String leaf nodes are evaluated
108
+ using Erubis. The only context variable available is @cfg, which
109
+ representes the configuration at the start of the template pass.
110
+
111
+ The process of template evaluation is as follows:
112
+
113
+ 1. Walk the tree, keeping track of how successful and failed template evaluations we performed
114
+ 2. If there were no failures, the pass is complete
115
+ 3. If there were no successes and the previous pass (if any) had at least one success, keep going
116
+ 4. If there were no successes and the previous pass also had no successes, raise an exception
117
+ 5. Keep going
118
+
119
+ Requiring two passes with no successful template evaluations allows files in
120
+ upper layers to depend on values defined in lower layers. For example,
121
+ given these files:
122
+
123
+ ```text
124
+ ---
125
+ a:
126
+ b:
127
+ c: <%= @cfg[:d][:e][:f] %>
128
+
129
+ ---
130
+ d:
131
+ e:
132
+ f: <%= @cfg[:g][:h][:i] %>
133
+
134
+ ---
135
+ g:
136
+ h:
137
+ i: j
138
+ ```
139
+
140
+ It would take two passes to fully resolve all templates. In the first pass,
141
+ cfg[:d][:e][:f] would resolve to 'j', and in the second pass,
142
+ cfg[:a][:b][:c] would also resolve to 'j'.
143
+
144
+ ### Empty Strings
145
+
146
+ If a template evaluates to the empty string, this is by default considered a
147
+ failure. To treat empty strings as success, call ::emptyok before
148
+ constructing the configuration:
149
+
150
+ ```ruby
151
+ class OurConfig < LayeredYAMLConfig
152
+ end
153
+ OurConfig.templates = true
154
+ OurConfig.emptyok = true
155
+ OurConfig.instance('file_containing_erb.yaml')
156
+ ```
157
+
158
+ Beware of hidden gotchas though: if an intermediate node is undefined, ERB
159
+ will throw an exception trying to deference nil. But if only the last node
160
+ is defined, then ERB will generate an empty string:
161
+
162
+ ```text
163
+ ---
164
+ a:
165
+ b:
166
+ c: d
167
+ e: <%= @cfg[:a][:b][:f] %>
168
+ g: <%= @cfg[:a][:h][:i] %>
169
+ ```
170
+
171
+ In the default mode, neither template resolves successfully. With ::emptyok
172
+ enabled, cfg[:a][:b][:e] becomes the empty string.
173
+
174
+ ### What can be expanded
175
+
176
+ The result of an Erubis expansion is always a string. Therefore, you can't
177
+ expand a Hash or Array and then expect it to dereference properly. This,
178
+ for example, won't work:
179
+
180
+ ```text
181
+ a:
182
+ one: 1
183
+ two: 2
184
+ three: 3
185
+ b: <%= @cfg[:a] %>
186
+ ```
187
+
188
+ ```ruby
189
+ puts cfg[:b][:one]
190
+ ```
191
+
192
+ The expansion of cfg[:b] isn't the same as cfg[:a], it's the same as
193
+ cfg[:a].to_s.
194
+
195
+ You can however dereference through a Hash or Array to a scalar leaf:
196
+
197
+ ```text
198
+ a:
199
+ one: 1
200
+ two: 2
201
+ three: 3
202
+ b: <%= @cfg[:a][:one] %>
203
+ ```
204
+
205
+ ```ruby
206
+ puts cfg[:b]
207
+ ```
208
+
209
+ ## Resetting Per-Class Options
210
+
211
+ call ::reset on your class to reset the ::skipbad, ::skipmissing,
212
+ ::templates and ::emptyok settings to their defaults. Call ::reset_all on
213
+ the base class to reset these options to default for all derived classes.
214
+
215
+ ## LICENSE:
216
+
217
+ The MIT License (MIT)
218
+
219
+ Copyright (c) 2012, 2013 James FitzGibbon <james@nadt.net>
220
+
221
+ Permission is hereby granted, free of charge, to any person obtaining a copy
222
+ of this software and associated documentation files (the "Software"), to
223
+ deal in the Software without restriction, including without limitation the
224
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
225
+ sell copies of the Software, and to permit persons to whom the Software is
226
+ furnished to do so, subject to the following conditions:
227
+
228
+ The above copyright notice and this permission notice shall be included in
229
+ all copies or substantial portions of the Software.
230
+
231
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
232
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
233
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
234
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
235
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
236
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
237
+ IN THE SOFTWARE.
238
+
239
+ ## Contributing to LayeredYAMLConfig
240
+
241
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
242
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
243
+ * Fork the project
244
+ * Start a feature/bugfix branch
245
+ * Commit and push until you are happy with your contribution
246
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
247
+ * Please try not to mess with the version or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rake/testtask'
2
+ require 'hoe'
3
+
4
+ Hoe.spec 'layeredyamlconfig' do
5
+ developer("James FitzGibbon", "james@nadt.net")
6
+ license "MIT"
7
+ end
8
+
9
+ task :default => [:unit_tests]
10
+
11
+ desc "Run basic tests"
12
+ Rake::TestTask.new("unit_tests") { |t|
13
+ t.libs.push 'lib'
14
+ t.libs.push 'test'
15
+ t.pattern = 'test/test_*.rb'
16
+ t.verbose = true
17
+ t.warning = true
18
+ }
@@ -0,0 +1,17 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'layeredyamlconfig'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'layeredyamlconfig'
6
+ s.version = ThreadedLogger::VERSION
7
+ s.summary = 'YAML Configs with multiple layers and ERB evaluation'
8
+ s.add_dependency( 'psych', '>= 1.3.4' )
9
+ s.add_dependency( 'activesupport', '>= 3.2.12' )
10
+ s.add_dependency( 'hash-deep-merge' )
11
+ s.add_dependency( 'erubis' )
12
+ s.authors = ['James FitzGibbon']
13
+ s.email = ['james@nadt.net']
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f)
17
+ end
@@ -0,0 +1,2 @@
1
+ require 'layeredyamlconfig/core'
2
+ require 'layeredyamlconfig/templates'
@@ -0,0 +1,19 @@
1
+ # based on Hash traverse from Ruby Facets (https://github.com/rubyworks/facets)
2
+ class Array
3
+
4
+ def traverse(&blk)
5
+ na = []
6
+ inject(na) do |a, e|
7
+ case e
8
+ when Hash
9
+ e = e.traverse(&blk)
10
+ when Array
11
+ e = e.traverse(&blk)
12
+ end
13
+ ne = blk.call(e)
14
+ a.push ne
15
+ a
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,103 @@
1
+ require 'yaml'
2
+ require 'forwardable'
3
+ require 'hash_deep_merge'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+
6
+ class LayeredYAMLConfig
7
+
8
+ VERSION = '1.4.3'
9
+
10
+ extend Forwardable
11
+
12
+ private_class_method :new
13
+ attr_reader :files
14
+ def_delegators :@cfg, :[], :[]=, :to_hash
15
+
16
+ def self.instance(*files)
17
+ if @@instances[self].nil?
18
+ if ! files.empty?
19
+ @@instances[self] = new(files)
20
+ else
21
+ raise ArgumentError, "no files in initial construction of #{self}"
22
+ end
23
+ else
24
+ if ! files.empty?
25
+ raise ArgumentError, "instance for #{self} already constructed"
26
+ end
27
+ end
28
+ return @@instances[self]
29
+ end
30
+
31
+ def self.clear
32
+ @@instances[self] = nil
33
+ end
34
+
35
+ def self.clear_all
36
+ @@instances = Hash.new
37
+ end
38
+
39
+ def self.reset
40
+ %w(skipbad skipmissing templates emptyok).each do |opt|
41
+ @@opts[opt.to_sym].delete(self)
42
+ end
43
+ end
44
+
45
+ def self.reset_all
46
+ @@opts = {
47
+ :skipbad => Hash.new(false),
48
+ :skipmissing => Hash.new(true),
49
+ :templates => Hash.new(false),
50
+ :emptyok => Hash.new(false),
51
+ }
52
+ end
53
+
54
+ # create class-level option accessors
55
+ %w(skipbad skipmissing templates emptyok).each do |opt|
56
+ self.define_singleton_method(opt.to_sym) do
57
+ @@opts[opt.to_sym][self]
58
+ end
59
+ self.define_singleton_method("#{opt}=".to_sym) do |newval|
60
+ @@opts[opt.to_sym][self] = !!newval
61
+ end
62
+ end
63
+
64
+ # add files to an instance
65
+ def add(*files)
66
+ # due to the multi passing of splat args, we can get Array-in-Array situations here
67
+ files.flatten.each do |fn|
68
+ @files.push(fn)
69
+ if ! File.exists?(fn)
70
+ next if self.class.skipmissing
71
+ raise ArgumentError, "file #{fn} does not exist"
72
+ end
73
+ begin
74
+ data = YAML.load(File.open(fn))
75
+ if ! data.instance_of?(Hash)
76
+ raise ArgumentError, "file #{fn} does not contain a Hash"
77
+ end
78
+ @cfg.deep_merge!(data.deep_symbolize_keys).deep_symbolize_keys
79
+ rescue
80
+ if ! self.class.skipbad
81
+ raise
82
+ end
83
+ end
84
+ end
85
+
86
+ # resolve templates
87
+ if self.class.templates
88
+ resolve_templates
89
+ end
90
+ end
91
+
92
+ # constructor
93
+ def initialize(*files)
94
+ @cfg = Hash.new.with_indifferent_access
95
+ @files = []
96
+ add(files)
97
+ end
98
+
99
+ # create catalog of per-subclass instances and options
100
+ self.clear_all
101
+ self.reset_all
102
+
103
+ end