dotlocal 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .rspec
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ module DotLocal
2
+ VERSION = "0.0.2"
3
+ 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
@@ -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
@@ -0,0 +1,4 @@
1
+ value:
2
+ blank:
3
+
4
+
@@ -0,0 +1,10 @@
1
+ :this
2
+ :is
3
+ ------- a line
4
+ { something wrong }
5
+ :not
6
+ :a
7
+ :good
8
+ :yaml
9
+ :file
10
+
@@ -0,0 +1,2 @@
1
+ path: 1
2
+ file_name: 2
@@ -0,0 +1,14 @@
1
+ :defaults: &defaults
2
+ key_one: 1
3
+ key_two: 2
4
+
5
+ production:
6
+ <<: *defaults
7
+
8
+ development:
9
+ key_one: 11
10
+ key_two: 22
11
+
12
+ test:
13
+ key_one: 111
14
+ key_two: 222
@@ -0,0 +1,10 @@
1
+ food:
2
+ cheese:
3
+ cheddar: 1
4
+ parmesan: 2
5
+
6
+ sport:
7
+ racing:
8
+ bikers: 1
9
+ cars: 2
10
+
@@ -0,0 +1,10 @@
1
+ food:
2
+ cheese:
3
+ cheddar: 1
4
+ parmesan:
5
+
6
+ sport:
7
+ racing:
8
+ bikers: 1
9
+ cars:
10
+
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