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 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