config_layers 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3f2439ed377f78c7280ee7eb27b2d6b077688a5
4
+ data.tar.gz: e4ddb0c54bdbfd14a249e209dabfba7aaa87cdab
5
+ SHA512:
6
+ metadata.gz: 82c7d7d882993984abe7d452df09753e87d9f63b6d68a1f12351658ea25f774e32b44fe15edf9503710d907a2d39da6d79df1da62425ee6bdac6722e8640af34
7
+ data.tar.gz: 88de4f1b5081874d8da6dbb19627f5343952b059fc5c732d3283bac3eed3a8fee1e03bfc228de641a2212cd37ce66b6ab1264d6309eaf30778b1aea2756eef4d
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.gitreview ADDED
@@ -0,0 +1,4 @@
1
+ [gerrit]
2
+ host=review.forj.forj.io
3
+ port=29418
4
+ project=forj-oss/config-layers.git
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,33 @@
1
+ # Copyright 2013 Hewlett-Packard Development Company, L.P.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # use: rubocop --show-cops
16
+ # to validate configuration.
17
+ # rubocop config
18
+ AllCops:
19
+ Include:
20
+ - '**/Rakefile'
21
+ Style/HashSyntax:
22
+ EnforcedStyle: hash_rockets
23
+
24
+ # lets start with 40, but 10 is way to small..
25
+ Metrics/MethodLength:
26
+ Max: 40
27
+ # If Method length is increased, class length need to be extended as well.
28
+ Metrics/ClassLength:
29
+ Max: 150
30
+
31
+ # allow arguments to be longer than 15
32
+ Metrics/AbcSize:
33
+ Max: 40
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in config_layers.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # ConfigLayers
2
+
3
+ ConfigLayers is a library which help to manage a complex configuration hierarchy, (Hash of Hashes/Array/...)
4
+ per layers.
5
+
6
+ * What kind of complexity are we trying to resolve?
7
+
8
+ If you are using yaml or json, you can write anything in that format in some configuration file.
9
+ Ex:
10
+
11
+ :global:
12
+ :url: http://url.example.org
13
+ :options:
14
+ :timeout: 60
15
+ :port: 4708
16
+ :application
17
+ :publish: [ help, about, main, go ]
18
+
19
+ When you load this yaml, you are going to get a Hash containing hash/array/string/... etc...
20
+ then, when you want to access :timeout, you access it like that:
21
+
22
+ timeout = data[:global][:options][:timeout]
23
+
24
+ If your data has this Hash of Hash of Hash, loaded in memory, then you get the data.
25
+ But you need to manage the exception or do some test for each Hash, otherwise you get a
26
+ nil exception.
27
+
28
+ ConfigLayers simplify this:
29
+
30
+ - access ':timeout' :
31
+
32
+ config = PRC::BaseConfig.new(data)
33
+
34
+ timeout = config[:global, :options, :timeout]
35
+
36
+ No need to add any test.
37
+
38
+ - set ':timeout' :
39
+
40
+ config[:global, :options, :timeout] = 60
41
+
42
+ No need to create the Hash structure to set the ':timeout'
43
+
44
+ ConfigLayer embed YAML, so you can load and save this data from/to a yaml file...
45
+
46
+ config.save(filename)
47
+ config.load(filename)
48
+
49
+ You can check the key existence as well!
50
+
51
+ if config.exist?(:global, :options, :timeout)
52
+ <do things>
53
+ end
54
+
55
+ * What is that layer notion in ConfigLayers?
56
+
57
+ If you have an application which has some predefine default, a system config, a user config
58
+ and an account config, you may want to get a consolidated environment that you want to use.
59
+
60
+ if we are back in the previous ':timeout' example, let's say that :
61
+ - application default for timeout is. 60
62
+ - the system config has no timeout data.
63
+ - the user config has 120
64
+ - and the user load an account file that also has a timeout to 90
65
+
66
+ If the application run, with the account data loaded, when the application wants to get the timeout data
67
+
68
+ you expect to just read it! But you need to check if found in account, use it, otherwise check in user
69
+ config, etc... until the application defaults if none of those previous configuration file has the :timeout
70
+ setting...
71
+
72
+ Here with ConfigLayers, you create a class based on CoreConfig and you define those layers.
73
+ then you just access you data!
74
+
75
+ Ex: This is a real simple example.
76
+
77
+ # We define layers
78
+ class MyConfigApp < PRC::CoreConfig
79
+ def initialize(files)
80
+ config_layers = []
81
+
82
+ # Application default layer
83
+ config_layers << define_default_layer
84
+
85
+ # runtime Config layer
86
+ config_layers << define_layer('system', files[0])
87
+
88
+ # User Config layer
89
+ config_layers << define_layer('user', files[1])
90
+
91
+ # Account config layer
92
+ config_layers << define_layer('account', files[2]))
93
+
94
+ initialize_layers(config_layers)
95
+ end
96
+
97
+ def define_layer(name, filename)
98
+ config = PRC::BaseConfig.new()
99
+ config.load(filename)
100
+ PRC::CoreConfig.define_layer(:name => name,
101
+ :config => config,
102
+ :file_set => true,
103
+ :load => true, :save => true)
104
+ end
105
+
106
+ def define_default_layer
107
+ config = PRC.BaseConfig()
108
+ config[:global, :options, :timeout] = 60
109
+ PRC::CoreConfig.define_layer(:name => 'default',
110
+ :config => config)
111
+ end
112
+ end
113
+
114
+ # Using my ConfigLayers
115
+ files = ['/etc/myapp.yaml', '~/.myapp.yaml', '~/.conf/account/myaccount.yaml']
116
+ config = MyConfigApp.new(files)
117
+
118
+ [...]
119
+
120
+ puts config[:global, :options, :timeout]
121
+
122
+
123
+ As you saw, we define the layer, load or set it, assign a name and declare in a new class
124
+ and just use that class...
125
+
126
+ You can do strongly more.
127
+
128
+ You can define you own layer, you can redefine how your access data.
129
+ For example, you can ignore some layers...
130
+
131
+ You can get a full consolidated data, with merge!
132
+
133
+ ## Installation
134
+
135
+ Add this line to your application's Gemfile:
136
+
137
+ ```ruby
138
+ gem 'config_layers'
139
+ ```
140
+
141
+ And then execute:
142
+
143
+ $ bundle
144
+
145
+ Or install it yourself as:
146
+
147
+ $ gem install config_layers
148
+
149
+ ## Development
150
+
151
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
152
+
153
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
154
+
155
+ ## Contributing
156
+
157
+ 1. Fork it ( https://github.com/[my-github-username]/config_layers/fork )
158
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
159
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
160
+ 4. Push to the branch (`git push origin my-new-feature`)
161
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+ require 'rdoc/task'
5
+
6
+ task :default => [:lint, :spec]
7
+
8
+ desc 'Run the specs.'
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = 'spec/*_spec.rb'
11
+ t.rspec_opts = '-f doc'
12
+ end
13
+
14
+ desc 'Generate lorj documentation'
15
+ RDoc::Task.new do |rdoc|
16
+ rdoc.main = 'README.md'
17
+ rdoc.rdoc_files.include('README.md', 'lib', 'example', 'bin')
18
+ end
19
+
20
+ desc 'Run RuboCop on the project'
21
+ RuboCop::RakeTask.new(:lint) do |task|
22
+ task.formatters = ['progress']
23
+ task.verbose = true
24
+ task.fail_on_error = true
25
+ end
26
+
27
+ task :build => [:lint, :spec]
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'config_layers'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require 'pry'
11
+ Pry.start
12
+
13
+ # require "irb"
14
+ # IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'config_layers/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "config_layers"
8
+ spec.version = ConfigLayers::VERSION
9
+ spec.authors = ["Christophe Larsonneur"]
10
+ spec.email = ["clarsonneur@gmail.com"]
11
+
12
+ spec.summary = %q{ConfigLayers, a simple multiple configuration management.}
13
+ spec.description = %q{Manage your application configuration files easily.}
14
+ spec.homepage = "http://github.com/forj-oss/config_layers"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "subhash", '~> 0.1.0'
22
+ spec.add_runtime_dependency "pry"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.9"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.1.0"
27
+ spec.add_development_dependency "rubocop", "~> 0.30.0"
28
+ end
@@ -0,0 +1,4 @@
1
+ # Config Layers
2
+ module ConfigLayers
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ #--
5
+ # (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ require 'config_layers/version'
21
+
22
+ # This is the config_layers library.
23
+ #
24
+ # To use it, add require 'config_layers'
25
+
26
+ require 'subhash' # recursive Hash
27
+
28
+ require 'prc_base_config' # PRC::BaseConfig class
29
+ require 'prc_section_config' # PRC::SectionConfig class
30
+ require 'prc_core_config' # PRC::CoreConfig class
31
+
32
+ # Redefine Object to add a boolean? function.
33
+ class Object
34
+ # Simplify boolean test on objects
35
+ def boolean?
36
+ self.is_a?(TrueClass) || self.is_a?(FalseClass)
37
+ end
38
+ end
@@ -0,0 +1,309 @@
1
+ # encoding: UTF-8
2
+
3
+ # (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'yaml'
18
+
19
+ module PRC
20
+ # This class is Base config system of lorj.
21
+ #
22
+ # It implements basic config features:
23
+ # * #erase - To cleanup all data in self config
24
+ # * #[] - To get a value for a key or tree of keys
25
+ # * #[]= - To set a value for a key in the tree.
26
+ # * #exist? - To check the existence of a value from a key
27
+ # * #del - To delete a key tree.
28
+ # * #save - To save all data in a yaml file
29
+ # * #load - To load data from a yaml file
30
+ # * #data_options - To influence on how exist?, [], []=, load and save will
31
+ # behave
32
+ #
33
+ # Config Data are managed as Hash of Hashes.
34
+ # It uses actively Hash.rh_* functions. See rh.rb.
35
+ class BaseConfig
36
+ # internal Hash data of this config.
37
+ # Do not use it except if you know what you are doing.
38
+ attr_reader :data
39
+
40
+ # * *set*: set the config file name. It accepts relative or absolute path to
41
+ # the file.
42
+ # * *get*: get the config file name used by #load and #save.
43
+ attr_accessor :filename
44
+
45
+ # config layer version
46
+ attr_accessor :version
47
+
48
+ # config layer latest version
49
+ attr_reader :latest_version
50
+
51
+ # initialize BaseConfig
52
+ #
53
+ # * *Args*
54
+ # - +keys+ : Array of key path to found
55
+ #
56
+ # * *Returns*
57
+ # - boolean : true if the key path was found
58
+ #
59
+ # ex:
60
+ # value = CoreConfig.New({ :test => {:titi => 'found'}})
61
+ # # => creates a CoreConfig with this Hash of Hash
62
+ def initialize(value = nil, latest_version = nil)
63
+ @data = {}
64
+ @data = value if value.is_a?(Hash)
65
+ @data_options = {} # Options for exist?/set/get/load/save
66
+ @latest_version = latest_version
67
+ @version = latest_version
68
+ end
69
+
70
+ # data_options set data options used by exist?, get, set, load and save
71
+ # functions.
72
+ #
73
+ # CoreConfig class type, call data_options to set options, before calling
74
+ # functions: exist?, get, set, load and save.
75
+ #
76
+ # Currently, data_options implements:
77
+ # - :data_readonly : The data cannot be updated. set will not update
78
+ # the value.
79
+ # - :file_readonly : The file used to load data cannot be updated.
80
+ # save will not update the file.
81
+ #
82
+ # The child class can superseed or replace data options with their own
83
+ # options.
84
+ # Ex: If your child class want to introduce notion of sections,
85
+ # you can define the following with get:
86
+ #
87
+ # class MySection < PRC::BaseConfig
88
+ # # by default, section name to use by get/set is :default
89
+ # def data_options(options = {:section => :default})
90
+ # p_data_options(options)
91
+ # end
92
+ #
93
+ # def [](*keys)
94
+ # p_get(@data_options[:section], *keys)
95
+ # end
96
+ #
97
+ # def []=(*keys, value)
98
+ # p_set(@data_options[:section], *keys, value)
99
+ # end
100
+ # end
101
+ #
102
+ # * *Args*
103
+ # - +keys+ : Array of key path to found
104
+ #
105
+ # * *Returns*
106
+ # - boolean : true if the key path was found
107
+ def data_options(options = nil)
108
+ p_data_options options
109
+ end
110
+
111
+ # exist?
112
+ #
113
+ # * *Args*
114
+ # - +keys+ : Array of key path to found
115
+ #
116
+ # * *Returns*
117
+ # - boolean : true if the key path was found
118
+ #
119
+ # ex:
120
+ # { :test => {:titi => 'found'}}
121
+ def exist?(*keys)
122
+ p_exist?(*keys)
123
+ end
124
+
125
+ # Erase the data in the object. internal version is cleared as well.
126
+ #
127
+ # * *Returns*
128
+ # - Hash : {}.
129
+ #
130
+ def erase
131
+ @version = @latest_version
132
+ @data = {}
133
+ end
134
+
135
+ # Get function
136
+ #
137
+ # * *Args*
138
+ # - +keys+ : Array of key path to found
139
+ #
140
+ # * *Returns*
141
+ # -
142
+ #
143
+ def [](*keys)
144
+ p_get(*keys)
145
+ end
146
+
147
+ # Set function
148
+ #
149
+ # * *Args*
150
+ # - +keys+ : set a value in the Array of key path.
151
+ #
152
+ # * *Returns*
153
+ # - The value set or nil
154
+ #
155
+ # ex:
156
+ # value = CoreConfig.New
157
+ #
158
+ # value[:level1, :level2] = 'value'
159
+ # # => {:level1 => {:level2 => 'value'}}
160
+
161
+ def del(*keys)
162
+ p_del(*keys)
163
+ end
164
+
165
+ # Set function
166
+ #
167
+ # * *Args*
168
+ # - +keys+ : set a value in the Array of key path.
169
+ #
170
+ # * *Returns*
171
+ # - The value set or nil
172
+ #
173
+ # ex:
174
+ # value = CoreConfig.New
175
+ #
176
+ # value[:level1, :level2] = 'value'
177
+ # # => {:level1 => {:level2 => 'value'}}
178
+ def []=(*keys, value)
179
+ p_set(*keys, value)
180
+ end
181
+
182
+ # Load from a file
183
+ #
184
+ # * *Args* :
185
+ # - +filename+ : file name to load. This file name will become the default
186
+ # file name to use next time.
187
+ # * *Returns* :
188
+ # - true if loaded.
189
+ # * *Raises* :
190
+ # - ++ ->
191
+ def load(filename = nil)
192
+ p_load(filename)
193
+ end
194
+
195
+ # Save to a file
196
+ #
197
+ # * *Args* :
198
+ # - +filename+ : file name to save. This file name will become the default
199
+ # file name to use next time.
200
+ # * *Returns* :
201
+ # - boolean if saved or not. true = saved.
202
+ def save(filename = nil)
203
+ p_save(filename)
204
+ end
205
+
206
+ # transform keys from string to symbol until deep level. Default is 1.
207
+ #
208
+ # * *Args* :
209
+ # - +level+ : Default 1. level to transform
210
+ #
211
+ # * *Returns* :
212
+ # - it self, with config updated.
213
+ def rh_key_to_symbol(level = 1)
214
+ data.rh_key_to_symbol level
215
+ end
216
+
217
+ # Check the need to transform keys from string to symbol until deep level.
218
+ # Default is 1.
219
+ #
220
+ # * *Args* :
221
+ # - +level+ : Default 1: levels to verify
222
+ #
223
+ # * *Returns* :
224
+ # - true if need to be updated.
225
+ #
226
+ def rh_key_to_symbol?(level = 1)
227
+ data.rh_key_to_symbol? level
228
+ end
229
+
230
+ # Redefine the file name attribute set.
231
+ #
232
+ # * *Args* :
233
+ # - +filename+ : default file name to use.
234
+ # * *Returns* :
235
+ # - filename
236
+ def filename=(filename) #:nodoc:
237
+ @filename = File.expand_path(filename) unless filename.nil?
238
+ end
239
+
240
+ # Print a representation of the Layer data
241
+ def to_s
242
+ msg = format("File : %s\n", @filename)
243
+ msg += data.to_yaml
244
+ msg
245
+ end
246
+
247
+ def latest_version?
248
+ (@version == @latest_version)
249
+ end
250
+
251
+ private
252
+
253
+ def p_data_options(options = nil)
254
+ @data_options = options unless options.nil?
255
+ @data_options
256
+ end
257
+
258
+ def p_exist?(*keys)
259
+ return nil if keys.length == 0
260
+
261
+ (@data.rh_exist?(*keys))
262
+ end
263
+
264
+ def p_get(*keys)
265
+ return nil if keys.length == 0
266
+
267
+ @data.rh_get(*keys)
268
+ end
269
+
270
+ def p_del(*keys)
271
+ return nil if keys.length == 0
272
+
273
+ @data.rh_del(*keys)
274
+ end
275
+
276
+ def p_set(*keys, value)
277
+ return nil if keys.length == 0
278
+ return p_get(*keys) if @data_options[:data_readonly]
279
+
280
+ @data.rh_set(value, keys)
281
+ end
282
+
283
+ def p_load(file = nil)
284
+ self.filename = file unless file.nil?
285
+
286
+ fail 'Config filename not set.' if @filename.nil?
287
+
288
+ @data = YAML.load_file(File.expand_path(@filename))
289
+
290
+ if @data.key?(:file_version)
291
+ @version = @data[:file_version]
292
+ @data.delete(:file_version)
293
+ end
294
+ true
295
+ end
296
+
297
+ def p_save(file = nil)
298
+ return false if @data_options[:file_readonly]
299
+ self.filename = file unless file.nil?
300
+
301
+ fail 'Config filename not set.' if @filename.nil?
302
+ @data_dup = @data.dup
303
+ @data_dup[:file_version] = @version unless @version.nil?
304
+
305
+ File.open(@filename, 'w+') { |out| YAML.dump(@data_dup, out) }
306
+ true
307
+ end
308
+ end
309
+ end