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