dotlocal 0.0.2
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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/dotlocal.gemspec +24 -0
- data/lib/dot_local.rb +28 -0
- data/lib/dot_local/configuration.rb +112 -0
- data/lib/dot_local/mapper.rb +57 -0
- data/lib/dot_local/version.rb +3 -0
- data/lib/dotlocal.rb +1 -0
- data/spec/dot_local_spec.rb +191 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/fixtures/blank_values.yml +4 -0
- data/spec/support/fixtures/error_settings.yml +10 -0
- data/spec/support/fixtures/reserved_keys.yml +2 -0
- data/spec/support/fixtures/settings.yml +14 -0
- data/spec/support/fixtures/with_local.local.yml +10 -0
- data/spec/support/fixtures/with_local.yml +10 -0
- metadata +82 -0
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/dotlocal.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "dot_local/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dotlocal"
|
7
|
+
s.version = DotLocal::VERSION
|
8
|
+
s.authors = ["Fabrizio Regini"]
|
9
|
+
s.email = ["freegenie@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{DotLocal, for versionable config files}
|
12
|
+
s.description = %q{DotLocal helps in managing config files providing ability to define a local version to override settings}
|
13
|
+
|
14
|
+
s.rubyforge_project = "."
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
data/lib/dot_local.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'dot_local/configuration'
|
3
|
+
require 'dot_local/mapper'
|
4
|
+
require 'dot_local/version'
|
5
|
+
|
6
|
+
module DotLocal
|
7
|
+
class ParsingError < Exception ; end
|
8
|
+
class MissingFile < Exception ; end
|
9
|
+
class KeyNotFound < Exception ; end
|
10
|
+
class DoubleLoad < Exception ; end
|
11
|
+
class ReservedKey < Exception ; end
|
12
|
+
class BlankValue < Exception ; end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def deep_merge!(winner, looser)
|
16
|
+
merger = proc do |key,winner,looser|
|
17
|
+
if Hash === winner && Hash === looser
|
18
|
+
winner.merge(looser, &merger)
|
19
|
+
else
|
20
|
+
winner.to_s == '' ? looser : winner
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
winner.merge!(looser, &merger)
|
25
|
+
winner
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module DotLocal
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
SettingsFileName = 'settings.yml'
|
5
|
+
LocalSuffix = 'local'
|
6
|
+
|
7
|
+
ReservedKeys = %w(env path file_name local_file_name raw)
|
8
|
+
|
9
|
+
attr_accessor :env, :path, :file_name, :local_file_name, :raw
|
10
|
+
|
11
|
+
def initialize(options={})
|
12
|
+
@path = options.delete :path
|
13
|
+
@file_name = options.delete :file_name
|
14
|
+
@local_file_name = options.delete :local_file_name
|
15
|
+
@env = options.delete :env
|
16
|
+
|
17
|
+
@path = File.expand_path('..', __FILE__) if @path.nil?
|
18
|
+
@file_name ||= SettingsFileName
|
19
|
+
@local_file_name ||= interpolate_local_filename
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(*args)
|
23
|
+
super unless args.size == 1
|
24
|
+
key = args.first.to_s
|
25
|
+
if Mapper.key_is_hash?(@raw, key)
|
26
|
+
Mapper.new(self, key)
|
27
|
+
else
|
28
|
+
Mapper.fetch(@raw, key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def reload!
|
33
|
+
@loaded = false
|
34
|
+
self.load!
|
35
|
+
end
|
36
|
+
|
37
|
+
def load!
|
38
|
+
raise DotLocal::DoubleLoad if @loaded
|
39
|
+
@loaded = true
|
40
|
+
@raw = parse(file_name)
|
41
|
+
@parsed = @raw
|
42
|
+
@raw = @raw.fetch(@env.to_s) unless @env.nil?
|
43
|
+
|
44
|
+
merge_with_local! if local_exists?
|
45
|
+
|
46
|
+
validate_blank_values!
|
47
|
+
validate_reserved_keys!
|
48
|
+
|
49
|
+
@raw.freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def merge(first, second)
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def local_exists?
|
59
|
+
File.exists?(File.join(path, local_file_name))
|
60
|
+
end
|
61
|
+
|
62
|
+
def recursive_find_blank_values(key,value)
|
63
|
+
if value.is_a?(Hash)
|
64
|
+
value.each do |key,value|
|
65
|
+
recursive_find_blank_values(key,value)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
if value.to_s == ''
|
69
|
+
raise BlankValue.new("Blank value found for key #{key}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_blank_values!
|
75
|
+
@raw.each do |key,value|
|
76
|
+
recursive_find_blank_values(key,value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_reserved_keys!
|
81
|
+
ReservedKeys.each do |key|
|
82
|
+
if @raw.keys.map(&:to_s).include? key
|
83
|
+
raise DotLocal::ReservedKey.new("Reserved #{key} found")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse(file_name)
|
89
|
+
file = File.read(File.join(path, file_name).to_s)
|
90
|
+
yaml = YAML.load(file)
|
91
|
+
raise unless yaml.is_a? Hash
|
92
|
+
yaml
|
93
|
+
|
94
|
+
rescue Errno::ENOENT
|
95
|
+
raise DotLocal::MissingFile.new("File #{file} not found")
|
96
|
+
rescue => e
|
97
|
+
raise ParsingError.new(e.message)
|
98
|
+
end
|
99
|
+
|
100
|
+
def interpolate_local_filename
|
101
|
+
ext = File.extname(@file_name)
|
102
|
+
"#{@file_name.gsub(ext, '')}.#{LocalSuffix}#{ext}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def merge_with_local!
|
106
|
+
local_hash = parse(local_file_name)
|
107
|
+
local_hash = local_hash.fetch(@env.to_s) unless @env.nil?
|
108
|
+
@raw = DotLocal.deep_merge!(local_hash, @raw)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module DotLocal
|
2
|
+
class Mapper
|
3
|
+
|
4
|
+
def initialize(config, *args)
|
5
|
+
@parents = args.map!(&:to_s)
|
6
|
+
@config = config
|
7
|
+
@raw = config.raw
|
8
|
+
parents = @parents.dup
|
9
|
+
|
10
|
+
# Iterate the three and stop at the last
|
11
|
+
# key. @raw is now the last member of
|
12
|
+
# the chain.
|
13
|
+
# This means that @raw can be either a Hash or
|
14
|
+
# a value
|
15
|
+
while parent = parents.shift
|
16
|
+
@raw = Mapper.fetch(@raw, parent)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(*args)
|
21
|
+
super if args.count > 1
|
22
|
+
return_value_or_mapper(args.first.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.fetch(hash, key)
|
26
|
+
hash.fetch(key)
|
27
|
+
rescue KeyError
|
28
|
+
raise KeyNotFound.new("You were looking for #{key} but no luck")
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
@raw if @raw.is_a?(Hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def return_value_or_mapper(key)
|
38
|
+
if return_mapper?(@raw, key)
|
39
|
+
@parents << key
|
40
|
+
self.class.new(@config, *@parents)
|
41
|
+
else
|
42
|
+
self.class.fetch(@raw, key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def return_mapper?(value, key)
|
47
|
+
value.is_a?(Hash) && self.class.key_is_hash?(value, key)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.key_is_hash?(value, key)
|
51
|
+
value.fetch(key).is_a?(Hash)
|
52
|
+
rescue KeyError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
data/lib/dotlocal.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'dot_local'
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require './lib/dot_local'
|
3
|
+
|
4
|
+
describe DotLocal do
|
5
|
+
describe 'deep_merge!' do
|
6
|
+
let(:winner) { {:a => {:b => 'y'}, :c => nil }}
|
7
|
+
let(:looser) { {:a => {:b => 'x', :d => 'x'}, :c => 'x'} }
|
8
|
+
before do
|
9
|
+
DotLocal.deep_merge!(winner,looser)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should give priority if key exists' do
|
13
|
+
winner[:a][:b].should == 'y'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should keep key in nested hash' do
|
17
|
+
winner[:a][:d].should == 'x'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should keep exceeding key' do
|
21
|
+
winner[:c].should == 'x'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe DotLocal::Mapper do
|
27
|
+
let(:config_hash) do
|
28
|
+
{'foo' => {'bar' => {'baz' => 10 } } }
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:config) do
|
32
|
+
config = DotLocal::Configuration.new
|
33
|
+
config.stub(:raw => config_hash)
|
34
|
+
config
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should return config value' do
|
38
|
+
mapper = DotLocal::Mapper.new(config, :foo, :bar)
|
39
|
+
mapper.baz.should == 10
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should return a mapper' do
|
43
|
+
mapper = DotLocal::Mapper.new(config, :foo)
|
44
|
+
mapper.bar.should be_a(DotLocal::Mapper)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should have a to_hash method' do
|
48
|
+
mapper = DotLocal::Mapper.new(config, :foo)
|
49
|
+
mapper.bar.to_hash.should == {'baz' => 10}
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe DotLocal::Configuration do
|
55
|
+
it 'should set path by options' do
|
56
|
+
config = DotLocal::Configuration.new(:path => '/path/to/me')
|
57
|
+
config.path.to_s.should == '/path/to/me'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should set env by options' do
|
61
|
+
config = DotLocal::Configuration.new(:env => 'production')
|
62
|
+
config.env.should == 'production'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should look for a file named settings.yml' do
|
66
|
+
path = File.join(File.dirname(__FILE__), 'support', 'fixtures')
|
67
|
+
config = DotLocal::Configuration.new(:path => path)
|
68
|
+
config.load!
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should raise an exception if settings file is not found' do
|
72
|
+
config = DotLocal::Configuration.new(:path => '/path/to/nowhere')
|
73
|
+
expect {
|
74
|
+
config.load!
|
75
|
+
}.to raise_error(DotLocal::MissingFile)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should preserve extension for local file name' do
|
79
|
+
config = DotLocal::Configuration.new(:file_name => 'foo.yml')
|
80
|
+
config.local_file_name.should == 'foo.local.yml'
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'on fixtures path' do
|
84
|
+
let(:path) { File.join(File.dirname(__FILE__), 'support', 'fixtures') }
|
85
|
+
|
86
|
+
context 'with a settings file' do
|
87
|
+
let(:config) do
|
88
|
+
DotLocal::Configuration.new(:path => path).tap do |config|
|
89
|
+
config.load!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should get a value without env' do
|
94
|
+
config.env = nil
|
95
|
+
config.reload!
|
96
|
+
config.production.key_one.should == 1
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should return the raw settings hash' do
|
100
|
+
config.raw.should be_a(Hash)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should raise for double load call' do
|
104
|
+
expect {
|
105
|
+
config.load!
|
106
|
+
}.to raise_error(DotLocal::DoubleLoad)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should allow for reload' do
|
110
|
+
expect {
|
111
|
+
config.reload!
|
112
|
+
}.to_not raise_error
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should raise if settings has reserved keys' do
|
116
|
+
config.file_name = 'reserved_keys.yml'
|
117
|
+
config.env = nil
|
118
|
+
expect {
|
119
|
+
config.reload!
|
120
|
+
}.to raise_error(DotLocal::ReservedKey)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should create methods for top level keys' do
|
124
|
+
config.env = nil
|
125
|
+
config.reload!
|
126
|
+
config.development.should be_a(DotLocal::Mapper)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should set env' do
|
130
|
+
config.env = 'development'
|
131
|
+
config.reload!
|
132
|
+
expect {
|
133
|
+
config.development
|
134
|
+
}.to raise_error(DotLocal::KeyNotFound)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should get a value when env is set' do
|
138
|
+
config.env = 'development'
|
139
|
+
config.file_name = 'settings.yml'
|
140
|
+
config.reload!
|
141
|
+
config.key_one.should == 11
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should raise when calling a missing config key' do
|
145
|
+
expect {
|
146
|
+
config.non_existing
|
147
|
+
}.to raise_error(DotLocal::KeyNotFound)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'with a settings file with errors' do
|
152
|
+
let(:config) do
|
153
|
+
DotLocal::Configuration.new(:path => path,
|
154
|
+
:file_name => 'error_settings.yml')
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should raise ParsingError' do
|
158
|
+
expect {
|
159
|
+
config.load!
|
160
|
+
}.to raise_error(DotLocal::ParsingError)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'on a settings file with nil keys' do
|
165
|
+
let(:config) do
|
166
|
+
DotLocal::Configuration.new(:path => path,
|
167
|
+
:file_name => 'blank_values.yml')
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should raise an exception' do
|
171
|
+
expect {
|
172
|
+
config.load!
|
173
|
+
}.to raise_error(DotLocal::BlankValue)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'a settings file with local' do
|
178
|
+
let(:config) do
|
179
|
+
DotLocal::Configuration.new(:path => path,
|
180
|
+
:file_name => 'with_local.yml')
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should have settings overridden by local' do
|
184
|
+
config.load!
|
185
|
+
config.food.cheese.parmesan.should == 2
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'ruby-debug'
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
config.filter_run :focus
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dotlocal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Fabrizio Regini
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-26 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2157950820 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2157950820
|
25
|
+
description: DotLocal helps in managing config files providing ability to define a
|
26
|
+
local version to override settings
|
27
|
+
email:
|
28
|
+
- freegenie@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- Rakefile
|
36
|
+
- dotlocal.gemspec
|
37
|
+
- lib/dot_local.rb
|
38
|
+
- lib/dot_local/configuration.rb
|
39
|
+
- lib/dot_local/mapper.rb
|
40
|
+
- lib/dot_local/version.rb
|
41
|
+
- lib/dotlocal.rb
|
42
|
+
- spec/dot_local_spec.rb
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
- spec/support/fixtures/blank_values.yml
|
45
|
+
- spec/support/fixtures/error_settings.yml
|
46
|
+
- spec/support/fixtures/reserved_keys.yml
|
47
|
+
- spec/support/fixtures/settings.yml
|
48
|
+
- spec/support/fixtures/with_local.local.yml
|
49
|
+
- spec/support/fixtures/with_local.yml
|
50
|
+
homepage: ''
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project: .
|
70
|
+
rubygems_version: 1.8.10
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: DotLocal, for versionable config files
|
74
|
+
test_files:
|
75
|
+
- spec/dot_local_spec.rb
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
- spec/support/fixtures/blank_values.yml
|
78
|
+
- spec/support/fixtures/error_settings.yml
|
79
|
+
- spec/support/fixtures/reserved_keys.yml
|
80
|
+
- spec/support/fixtures/settings.yml
|
81
|
+
- spec/support/fixtures/with_local.local.yml
|
82
|
+
- spec/support/fixtures/with_local.yml
|