config_layers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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