rails_config 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "rspec", ">= 2.0.0.beta.19"
4
+ gem 'autotest'
5
+ gem "growl-glue"
6
+
7
+ gem "ruby-debug"
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ autotest (4.3.2)
5
+ columnize (0.3.1)
6
+ diff-lcs (1.1.2)
7
+ growl-glue (1.0.7)
8
+ linecache (0.43)
9
+ rspec (2.0.0.beta.19)
10
+ rspec-core (= 2.0.0.beta.19)
11
+ rspec-expectations (= 2.0.0.beta.19)
12
+ rspec-mocks (= 2.0.0.beta.19)
13
+ rspec-core (2.0.0.beta.19)
14
+ rspec-expectations (2.0.0.beta.19)
15
+ diff-lcs (>= 1.1.2)
16
+ rspec-mocks (2.0.0.beta.19)
17
+ ruby-debug (0.10.3)
18
+ columnize (>= 0.1)
19
+ ruby-debug-base (~> 0.10.3.0)
20
+ ruby-debug-base (0.10.3)
21
+ linecache (>= 0.3)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ autotest
28
+ growl-glue
29
+ rspec (>= 2.0.0.beta.19)
30
+ ruby-debug
data/Rakefile CHANGED
@@ -9,7 +9,10 @@ begin
9
9
  s.homepage = "http://github.com/railsjedi/rails_config"
10
10
  s.description = "Provides an easy to use Application Configuration object"
11
11
  s.authors = ["Jacques Crocker"]
12
- s.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*"]
12
+ s.files = FileList["[A-Z]*", "{bin,generators,lib,spec}/**/*"]
13
+
14
+ s.add_development_dependency 'rspec', ">=2.0.0.beta.19"
15
+
13
16
  end
14
17
  Jeweler::GemcutterTasks.new
15
18
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
data/lib/rails_config.rb CHANGED
@@ -1,4 +1,5 @@
1
- require 'rails_config/settings/deep_merge' unless defined?(DeepMerge)
2
- require 'rails_config/settings/builder'
1
+ require 'rails_config/vendor/deep_merge' unless defined?(DeepMerge)
2
+ require 'pathname'
3
3
 
4
+ require 'rails_config/setting_builder'
4
5
  require 'rails_config/railtie'
@@ -2,13 +2,10 @@ if defined?(Rails::Railtie)
2
2
  module RailsConfig
3
3
  class Railtie < Rails::Railtie
4
4
  initializer :setup_rails_config do
5
- ::Settings = RailsConfig::Settings::Builder.load_files(
6
- :paths => [
7
- Rails.root.join("config", "settings.yml").to_s,
8
- Rails.root.join("config", "settings", "#{Rails.env}.yml").to_s,
9
- Rails.root.join("config", "environments", "#{Rails.env}.yml").to_s
10
- ],
11
- :root_path => Rails.root
5
+ ::Settings = RailsConfig::SettingBuilder.load_files(
6
+ Rails.root.join("config", "settings.yml").to_s,
7
+ Rails.root.join("config", "settings", "#{Rails.env}.yml").to_s,
8
+ Rails.root.join("config", "environments", "#{Rails.env}.yml").to_s
12
9
  )
13
10
  end
14
11
  end
@@ -0,0 +1,61 @@
1
+ require 'ostruct'
2
+ require 'yaml'
3
+ require 'erb'
4
+
5
+ module RailsConfig
6
+ module SettingBuilder
7
+ @@load_paths = []
8
+
9
+ # Create a config object (OpenStruct) from a yaml file. If a second yaml file is given, then the sections of that file will overwrite the sections
10
+ # if the first file if they exist in the first file.
11
+ def self.load_files(*files)
12
+ config = OpenStruct.new
13
+
14
+ @@load_paths = [files].flatten.compact.uniq
15
+ # add singleton method to our Settings that reloads its settings from the load_paths
16
+ def config.reload!
17
+
18
+ conf = {}
19
+ SettingBuilder.load_paths.to_a.each do |path|
20
+ file_conf = YAML.load(ERB.new(IO.read(path.to_s)).result) if path and File.exists?(path.to_s)
21
+ next unless file_conf
22
+
23
+ if conf.size > 0
24
+ DeepMerge.deep_merge!(file_conf, conf, :preserve_unmergeables => false)
25
+ else
26
+ conf = file_conf
27
+ end
28
+ end
29
+
30
+ # load all the new values into the openstruct
31
+ marshal_load(RailsConfig::SettingBuilder.convert(conf).marshal_dump)
32
+
33
+ return self
34
+ end
35
+
36
+ config.reload!
37
+ return config
38
+ end
39
+
40
+ def self.load_paths
41
+ @@load_paths
42
+ end
43
+
44
+ # Recursively converts Hashes to OpenStructs (including Hashes inside Arrays)
45
+ def self.convert(h) #:nodoc:
46
+ s = OpenStruct.new
47
+ h.each do |k, v|
48
+ s.new_ostruct_member(k)
49
+ if v.is_a?(Hash)
50
+ s.send( (k+'=').to_sym, convert(v))
51
+ elsif v.is_a?(Array)
52
+ converted_array = v.collect { |e| e.instance_of?(Hash) ? convert(e) : e }
53
+ s.send("#{k}=".to_sym, converted_array)
54
+ else
55
+ s.send("#{k}=".to_sym, v)
56
+ end
57
+ end
58
+ s
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,180 @@
1
+ module DeepMerge
2
+ class InvalidParameter < StandardError; end
3
+
4
+ DEFAULT_FIELD_KNOCKOUT_PREFIX = '--'
5
+
6
+ # Deep Merge core documentation.
7
+ # deep_merge! method permits merging of arbitrary child elements. The two top level
8
+ # elements must be hashes. These hashes can contain unlimited (to stack limit) levels
9
+ # of child elements. These child elements to not have to be of the same types.
10
+ # Where child elements are of the same type, deep_merge will attempt to merge them together.
11
+ # Where child elements are not of the same type, deep_merge will skip or optionally overwrite
12
+ # the destination element with the contents of the source element at that level.
13
+ # So if you have two hashes like this:
14
+ # source = {:x => [1,2,3], :y => 2}
15
+ # dest = {:x => [4,5,'6'], :y => [7,8,9]}
16
+ # dest.deep_merge!(source)
17
+ # Results: {:x => [1,2,3,4,5,'6'], :y => 2}
18
+ # By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
19
+ # To avoid this, use "deep_merge" (no bang/exclamation mark)
20
+ #
21
+ # Options:
22
+ # Options are specified in the last parameter passed, which should be in hash format:
23
+ # hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'})
24
+ # :preserve_unmergeables DEFAULT: false
25
+ # Set to true to skip any unmergeable elements from source
26
+ # :knockout_prefix DEFAULT: nil
27
+ # Set to string value to signify prefix which deletes elements from existing element
28
+ # :sort_merged_arrays DEFAULT: false
29
+ # Set to true to sort all arrays that are merged together
30
+ # :unpack_arrays DEFAULT: nil
31
+ # Set to string value to run "Array::join" then "String::split" against all arrays
32
+ # :merge_debug DEFAULT: false
33
+ # Set to true to get console output of merge process for debugging
34
+ #
35
+ # Selected Options Details:
36
+ # :knockout_prefix => The purpose of this is to provide a way to remove elements
37
+ # from existing Hash by specifying them in a special way in incoming hash
38
+ # source = {:x => ['--1', '2']}
39
+ # dest = {:x => ['1', '3']}
40
+ # dest.ko_deep_merge!(source)
41
+ # Results: {:x => ['2','3']}
42
+ # Additionally, if the knockout_prefix is passed alone as a string, it will cause
43
+ # the entire element to be removed:
44
+ # source = {:x => '--'}
45
+ # dest = {:x => [1,2,3]}
46
+ # dest.ko_deep_merge!(source)
47
+ # Results: {:x => ""}
48
+ # :unpack_arrays => The purpose of this is to permit compound elements to be passed
49
+ # in as strings and to be converted into discrete array elements
50
+ # irsource = {:x => ['1,2,3', '4']}
51
+ # dest = {:x => ['5','6','7,8']}
52
+ # dest.deep_merge!(source, {:unpack_arrays => ','})
53
+ # Results: {:x => ['1','2','3','4','5','6','7','8'}
54
+ # Why: If receiving data from an HTML form, this makes it easy for a checkbox
55
+ # to pass multiple values from within a single HTML element
56
+ #
57
+ # There are many tests for this library - and you can learn more about the features
58
+ # and usages of deep_merge! by just browsing the test examples
59
+ def DeepMerge.deep_merge!(source, dest, options = {})
60
+ # turn on this line for stdout debugging text
61
+ merge_debug = options[:merge_debug] || false
62
+ overwrite_unmergeable = !options[:preserve_unmergeables]
63
+ knockout_prefix = options[:knockout_prefix] || nil
64
+ if knockout_prefix == ""; raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!"; end
65
+ if knockout_prefix && !overwrite_unmergeable; raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!"; end
66
+ # if present: we will split and join arrays on this char before merging
67
+ array_split_char = options[:unpack_arrays] || false
68
+ # request that we sort together any arrays when they are merged
69
+ sort_merged_arrays = options[:sort_merged_arrays] || false
70
+ di = options[:debug_indent] || ''
71
+ # do nothing if source is nil
72
+ if source.nil? || (!source.is_a?(FalseClass) && source.respond_to?(:blank?) && source.blank?); return dest; end
73
+ # if dest doesn't exist, then simply copy source to it
74
+ if dest.nil? && overwrite_unmergeable; dest = source; return dest; end
75
+
76
+ puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
77
+ if source.kind_of?(Hash)
78
+ puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
79
+ source.each do |src_key, src_value|
80
+ if dest.kind_of?(Hash)
81
+ puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
82
+ if !dest[src_key].nil?
83
+ puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
84
+ dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + ' '))
85
+ else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
86
+ puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
87
+ # note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
88
+ begin
89
+ src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
90
+ rescue TypeError
91
+ src_dup = src_value
92
+ end
93
+ dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + ' '))
94
+ end
95
+ else # dest isn't a hash, so we overwrite it completely (if permitted)
96
+ if overwrite_unmergeable
97
+ puts "#{di} overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over-> #{dest.inspect}" if merge_debug
98
+ dest = overwrite_unmergeables(source, dest, options)
99
+ end
100
+ end
101
+ end
102
+ elsif source.kind_of?(Array)
103
+ puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
104
+ # if we are instructed, join/split any source arrays before processing
105
+ if array_split_char
106
+ puts "#{di} split/join on source: #{source.inspect}" if merge_debug
107
+ source = source.join(array_split_char).split(array_split_char)
108
+ if dest.kind_of?(Array); dest = dest.join(array_split_char).split(array_split_char); end
109
+ end
110
+ # if there's a naked knockout_prefix in source, that means we are to truncate dest
111
+ if source.index(knockout_prefix); dest = clear_or_nil(dest); source.delete(knockout_prefix); end
112
+ if dest.kind_of?(Array)
113
+ if knockout_prefix
114
+ print "#{di} knocking out: " if merge_debug
115
+ # remove knockout prefix items from both source and dest
116
+ source.delete_if do |ko_item|
117
+ retval = false
118
+ item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item
119
+ if item != ko_item
120
+ print "#{ko_item} - " if merge_debug
121
+ dest.delete(item)
122
+ dest.delete(ko_item)
123
+ retval = true
124
+ end
125
+ retval
126
+ end
127
+ puts if merge_debug
128
+ end
129
+ puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
130
+ dest = dest | source
131
+ if sort_merged_arrays; dest.sort!; end
132
+ elsif overwrite_unmergeable
133
+ puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
134
+ dest = overwrite_unmergeables(source, dest, options)
135
+ end
136
+ else # src_hash is not an array or hash, so we'll have to overwrite dest
137
+ puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
138
+ dest = overwrite_unmergeables(source, dest, options)
139
+ end
140
+ puts "#{di}Returning #{dest.inspect}" if merge_debug
141
+ dest
142
+ end # deep_merge!
143
+
144
+ # allows deep_merge! to uniformly handle overwriting of unmergeable entities
145
+ def DeepMerge::overwrite_unmergeables(source, dest, options)
146
+ merge_debug = options[:merge_debug] || false
147
+ overwrite_unmergeable = !options[:preserve_unmergeables]
148
+ knockout_prefix = options[:knockout_prefix] || false
149
+ di = options[:debug_indent] || ''
150
+ if knockout_prefix && overwrite_unmergeable
151
+ if source.kind_of?(String) # remove knockout string from source before overwriting dest
152
+ src_tmp = source.gsub(%r{^#{knockout_prefix}},"")
153
+ elsif source.kind_of?(Array) # remove all knockout elements before overwriting dest
154
+ src_tmp = source.delete_if {|ko_item| ko_item.kind_of?(String) && ko_item.match(%r{^#{knockout_prefix}}) }
155
+ else
156
+ src_tmp = source
157
+ end
158
+ if src_tmp == source # if we didn't find a knockout_prefix then we just overwrite dest
159
+ puts "#{di}#{src_tmp.inspect} -over-> #{dest.inspect}" if merge_debug
160
+ dest = src_tmp
161
+ else # if we do find a knockout_prefix, then we just delete dest
162
+ puts "#{di}\"\" -over-> #{dest.inspect}" if merge_debug
163
+ dest = ""
164
+ end
165
+ elsif overwrite_unmergeable
166
+ dest = source
167
+ end
168
+ dest
169
+ end
170
+
171
+ def DeepMerge::clear_or_nil(obj)
172
+ if obj.respond_to?(:clear)
173
+ obj.clear
174
+ else
175
+ obj = nil
176
+ end
177
+ obj
178
+ end
179
+
180
+ end # module DeepMerge
@@ -1,5 +1,4 @@
1
1
  size: 2
2
- computed: <%= 1 + 2 + 3 %>
3
2
  section:
4
3
  size: 3
5
4
  servers: [ {name: yahoo.com}, {name: amazon.com} ]
File without changes
File without changes
@@ -0,0 +1 @@
1
+ another: "something"
@@ -0,0 +1,4 @@
1
+ computed: <%= 1 + 2 + 3 %>
2
+ section:
3
+ computed1: <%= "1" %>
4
+ computed2: <%= "2" %>
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ module RailsConfig
4
+ describe SettingBuilder do
5
+
6
+ it "should load a basic config file" do
7
+ config = SettingBuilder.load_files(setting_path("settings.yml"))
8
+ config.size.should == 1
9
+ config.server.should == "google.com"
10
+ end
11
+
12
+ it "should load 2 basic config files" do
13
+ config = SettingBuilder.load_files(setting_path("settings.yml"), setting_path("settings2.yml"))
14
+ config.size.should == 1
15
+ config.server.should == "google.com"
16
+ config.another.should == "something"
17
+ end
18
+
19
+ it "should load empty config for a missing file path" do
20
+ config = SettingBuilder.load_files(setting_path("some_file_that_doesnt_exist.yml"))
21
+ config.should == OpenStruct.new
22
+ end
23
+
24
+ it "should load an empty config for multiple missing file paths" do
25
+ files = [setting_path("doesnt_exist1.yml"), setting_path("doesnt_exist2.yml")]
26
+ config = SettingBuilder.load_files(files)
27
+ config.should == OpenStruct.new
28
+ end
29
+
30
+ it "should load empty config for an empty setting file" do
31
+ config = SettingBuilder.load_files(setting_path("empty1.yml"))
32
+ config.should == OpenStruct.new
33
+ end
34
+
35
+ it "should load an empty config for multiple missing file paths" do
36
+ files = [setting_path("empty1.yml"), setting_path("empty2.yml")]
37
+ config = SettingBuilder.load_files(files)
38
+ config.should == OpenStruct.new
39
+ end
40
+
41
+ it "should allow overrides" do
42
+ files = [setting_path("settings.yml"), setting_path("development.yml")]
43
+ config = SettingBuilder.load_files(files)
44
+ config.server.should == "google.com"
45
+ config.size.should == 2
46
+ end
47
+
48
+ context "Nested Settings" do
49
+ let(:config) do
50
+ SettingBuilder.load_files(setting_path("development.yml"))
51
+ end
52
+
53
+ it "should allow nested sections" do
54
+ config.section.size.should == 3
55
+ end
56
+
57
+ it "should allow configuration collections (arrays)" do
58
+ config.section.servers[0].name.should == "yahoo.com"
59
+ config.section.servers[1].name.should == "amazon.com"
60
+ end
61
+ end
62
+
63
+ context "Settings with ERB tags" do
64
+ let(:config) do
65
+ SettingBuilder.load_files(setting_path("with_erb.yml"))
66
+ end
67
+
68
+ it "should evaluate ERB tags" do
69
+ config.computed.should == 6
70
+ end
71
+
72
+ it "should evaluated nested ERB tags" do
73
+ config.section.computed1.should == 1
74
+ config.section.computed2.should == 2
75
+ end
76
+ end
77
+
78
+ context "Deep Merging" do
79
+ let(:config) do
80
+ files = [setting_path("deep_merge/config1.yml"), setting_path("deep_merge/config2.yml")]
81
+ SettingBuilder.load_files(files)
82
+ end
83
+
84
+ it "should merge hashes from multiple configs" do
85
+ config.inner.marshal_dump.keys.size.should == 3
86
+ config.inner2.inner2_inner.marshal_dump.keys.size.should == 3
87
+ end
88
+
89
+ it "should merge arrays from multiple configs" do
90
+ config.arraylist1.size.should == 6
91
+ config.arraylist2.inner.size.should == 6
92
+ end
93
+ end
94
+
95
+ context "Boolean Overrides" do
96
+ let(:config) do
97
+ files = [setting_path("bool_override/config1.yml"), setting_path("bool_override/config2.yml")]
98
+ SettingBuilder.load_files(files)
99
+ end
100
+
101
+ it "should allow overriding of bool settings" do
102
+ config.override_bool.should == false
103
+ config.override_bool_opposite.should == true
104
+ end
105
+ end
106
+
107
+
108
+ end
109
+ end
@@ -0,0 +1,25 @@
1
+ require 'rails_config'
2
+ require "bundler"
3
+ Bundler.setup
4
+
5
+ def in_editor?
6
+ ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM')
7
+ end
8
+
9
+ RSpec.configure do |c|
10
+ c.color_enabled = !in_editor?
11
+ c.run_all_when_everything_filtered = true
12
+
13
+ # setup fixtures path
14
+ c.before(:all) do
15
+ @fixture_path = Pathname.new(File.join(File.dirname(__FILE__), "/fixtures"))
16
+ raise "Fixture folder not found: #{@fixture_path}" unless @fixture_path.directory?
17
+ end
18
+
19
+ # returns the file path of a fixture setting file
20
+ def setting_path(filename)
21
+ @fixture_path.join(filename)
22
+ end
23
+
24
+ end
25
+
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_config
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 4
10
- version: 0.0.4
9
+ - 5
10
+ version: 0.0.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jacques Crocker
@@ -15,10 +15,27 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-07 00:00:00 -07:00
18
+ date: 2010-08-08 00:00:00 -07:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 62196421
30
+ segments:
31
+ - 2
32
+ - 0
33
+ - 0
34
+ - beta
35
+ - 19
36
+ version: 2.0.0.beta.19
37
+ type: :development
38
+ version_requirements: *id001
22
39
  description: Provides an easy to use Application Configuration object
23
40
  email: railsjedi@gmail.com
24
41
  executables: []
@@ -29,25 +46,30 @@ extra_rdoc_files:
29
46
  - LICENSE
30
47
  - README
31
48
  files:
49
+ - Gemfile
50
+ - Gemfile.lock
32
51
  - LICENSE
33
52
  - README
34
53
  - Rakefile
35
54
  - VERSION
36
55
  - lib/rails_config.rb
37
56
  - lib/rails_config/railtie.rb
38
- - lib/rails_config/settings/builder.rb
39
- - lib/rails_config/settings/deep_merge.rb
40
- - test/config_builder_test.rb
41
- - test/test_configs/app_config.yml
42
- - test/test_configs/bool_override/config1.yml
43
- - test/test_configs/bool_override/config2.yml
44
- - test/test_configs/deep_merge/config1.yml
45
- - test/test_configs/deep_merge/config2.yml
46
- - test/test_configs/deep_merge2/config1.yml
47
- - test/test_configs/deep_merge2/config2.yml
48
- - test/test_configs/development.yml
49
- - test/test_configs/empty1.yml
50
- - test/test_configs/empty2.yml
57
+ - lib/rails_config/setting_builder.rb
58
+ - lib/rails_config/vendor/deep_merge.rb
59
+ - spec/fixtures/bool_override/config1.yml
60
+ - spec/fixtures/bool_override/config2.yml
61
+ - spec/fixtures/deep_merge/config1.yml
62
+ - spec/fixtures/deep_merge/config2.yml
63
+ - spec/fixtures/deep_merge2/config1.yml
64
+ - spec/fixtures/deep_merge2/config2.yml
65
+ - spec/fixtures/development.yml
66
+ - spec/fixtures/empty1.yml
67
+ - spec/fixtures/empty2.yml
68
+ - spec/fixtures/settings.yml
69
+ - spec/fixtures/settings2.yml
70
+ - spec/fixtures/with_erb.yml
71
+ - spec/rails_config/setting_builder_spec.rb
72
+ - spec/spec_helper.rb
51
73
  has_rdoc: true
52
74
  homepage: http://github.com/railsjedi/rails_config
53
75
  licenses: []
@@ -83,4 +105,5 @@ signing_key:
83
105
  specification_version: 3
84
106
  summary: provides an Settings for rails3 that reads config/settings.yml
85
107
  test_files:
86
- - test/config_builder_test.rb
108
+ - spec/rails_config/setting_builder_spec.rb
109
+ - spec/spec_helper.rb
@@ -1,170 +0,0 @@
1
- require 'ostruct'
2
- require 'yaml'
3
- require 'erb'
4
-
5
- module RailsConfig
6
- module Settings
7
- # == Summary
8
- # This is API documentation, NOT documentation on how to use this plugin. For that, see the README.
9
- class Builder
10
- @@load_paths = []
11
- @@expand_keys = []
12
- @@root_path = ""
13
-
14
- # Create a config object (OpenStruct) from a yaml file. If a second yaml file is given, then the sections of that file will overwrite the sections
15
- # if the first file if they exist in the first file.
16
- def self.load_files(options = {})
17
- config = OpenStruct.new
18
-
19
- @@load_paths = [options[:paths]].flatten.compact.uniq
20
- @@expand_keys = [options[:expand_keys]].flatten.compact.uniq
21
- @@root_path = options[:root_path]
22
-
23
- # add singleton method to our Settings that reloads its settings from the load_paths options
24
- def config.reload!
25
-
26
- conf = {}
27
- Builder.load_paths.to_a.each do |path|
28
- file_conf = YAML.load(ERB.new(IO.read(path)).result) if path and File.exists?(path)
29
- next unless file_conf
30
-
31
- if conf.size > 0
32
- DeepMerge.deep_merge!(file_conf, conf, :preserve_unmergeables => false)
33
- else
34
- conf = file_conf
35
- end
36
- end
37
-
38
- # expand the javascripts config to handle *.* paths
39
- Builder.expand_keys.to_a.each do |expand_path|
40
- expand_path = expand_path.to_s
41
- if conf[expand_path]
42
- conf[expand_path] = RailsConfig::Settings::Builder.expand(conf[expand_path], "#{Builder.root_path}/public/#{expand_path}")
43
- end
44
- end
45
-
46
- # load all the new values into the openstruct
47
- marshal_load(RailsConfig::Settings::Builder.convert(conf).marshal_dump)
48
-
49
- return self
50
- end
51
-
52
- config.reload!
53
- return config
54
- end
55
-
56
- def self.load_paths
57
- @@load_paths
58
- end
59
-
60
- def self.expand_keys
61
- @@expand_keys
62
- end
63
-
64
- def self.root_path
65
- @@root_path
66
- end
67
-
68
- # Recursively converts Hashes to OpenStructs (including Hashes inside Arrays)
69
- def self.convert(h) #:nodoc:
70
- s = OpenStruct.new
71
- h.each do |k, v|
72
- s.new_ostruct_member(k)
73
- if v.is_a?(Hash)
74
- s.send( (k+'=').to_sym, convert(v))
75
- elsif v.is_a?(Array)
76
- converted_array = v.collect { |e| e.instance_of?(Hash) ? convert(e) : e }
77
- s.send("#{k}=".to_sym, converted_array)
78
- else
79
- s.send("#{k}=".to_sym, v)
80
- end
81
- end
82
- s
83
- end
84
-
85
- # expand a config val
86
- def self.expand(config, base_path)
87
- case config.class.to_s
88
- when "Hash"
89
- return expand_hash(config, base_path)
90
- when "Array"
91
- return expand_array(config, base_path)
92
- when "String"
93
- return expand_string(config, base_path)
94
- end
95
- return config
96
- end
97
-
98
- # expand a string and returns a list
99
- def self.expand_string(config, base_path)
100
- # puts "Expanding String: #{config.inspect}"
101
- if config.include?("*")
102
- results = Dir["#{base_path}/#{config}"].map{|i| i.to_s.gsub("#{base_path}/", "") }
103
-
104
- # puts "EXPANDED PATH: #{base_path}/#{config}"
105
- # puts results.inspect
106
- return results
107
- else
108
- return config
109
- end
110
- end
111
-
112
- # expand a hash by cycling throw all the hash values
113
- def self.expand_hash(config, base_path)
114
- # puts "Expanding Hash: #{config.inspect}"
115
- new_config = {}
116
- config.each do |key, val|
117
- new_config[key] = expand(val, base_path)
118
- end
119
- return new_config
120
- end
121
-
122
- # expand an array by cycling through all the values
123
- def self.expand_array(config, base_path)
124
- # puts "Expanding Array: #{config.inspect}"
125
- new_config = []
126
- config.each do |val|
127
- new_val = expand(val, base_path)
128
- if new_val.is_a?(Array)
129
- new_val.each do |inner|
130
- new_config << inner
131
- end
132
- else
133
- new_config << new_val
134
- end
135
- end
136
- return new_config.uniq
137
- end
138
-
139
- # Cycles through the array of single element hashes
140
- # and deep merges any duplicates it finds
141
- #
142
- # This is needed so you can define stylesheet keys
143
- # in multiple config files
144
- def self.merge_assets(list)
145
- assets = Array(list).map do |i|
146
- if i.is_a?(OpenStruct)
147
- i.marshal_dump
148
- else
149
- i
150
- end
151
- end
152
-
153
- # filter out the duplicate single hash keys
154
- hash_keys = assets.select{|i| i.is_a?(Hash) and i.keys.size == 1}.group_by{|i| i.keys[0]}
155
- hash_keys.each do |key, value|
156
- if Array(value).size > 1
157
- merged = value.inject({}){|merged, v| DeepMerge.deep_merge!(v,merged)}
158
- value[0].replace(merged)
159
- value[1..-1].each do |v|
160
- v.clear
161
- end
162
- end
163
- end
164
-
165
- assets.select{|i| !i.blank? }
166
- end
167
-
168
- end
169
- end
170
- end
@@ -1,185 +0,0 @@
1
- module RailsConfig
2
- module Settings
3
- module DeepMerge
4
-
5
- class InvalidParameter < StandardError; end
6
-
7
- DEFAULT_FIELD_KNOCKOUT_PREFIX = '--'
8
-
9
- # Deep Merge core documentation.
10
- # deep_merge! method permits merging of arbitrary child elements. The two top level
11
- # elements must be hashes. These hashes can contain unlimited (to stack limit) levels
12
- # of child elements. These child elements to not have to be of the same types.
13
- # Where child elements are of the same type, deep_merge will attempt to merge them together.
14
- # Where child elements are not of the same type, deep_merge will skip or optionally overwrite
15
- # the destination element with the contents of the source element at that level.
16
- # So if you have two hashes like this:
17
- # source = {:x => [1,2,3], :y => 2}
18
- # dest = {:x => [4,5,'6'], :y => [7,8,9]}
19
- # dest.deep_merge!(source)
20
- # Results: {:x => [1,2,3,4,5,'6'], :y => 2}
21
- # By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
22
- # To avoid this, use "deep_merge" (no bang/exclamation mark)
23
- #
24
- # Options:
25
- # Options are specified in the last parameter passed, which should be in hash format:
26
- # hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'})
27
- # :preserve_unmergeables DEFAULT: false
28
- # Set to true to skip any unmergeable elements from source
29
- # :knockout_prefix DEFAULT: nil
30
- # Set to string value to signify prefix which deletes elements from existing element
31
- # :sort_merged_arrays DEFAULT: false
32
- # Set to true to sort all arrays that are merged together
33
- # :unpack_arrays DEFAULT: nil
34
- # Set to string value to run "Array::join" then "String::split" against all arrays
35
- # :merge_debug DEFAULT: false
36
- # Set to true to get console output of merge process for debugging
37
- #
38
- # Selected Options Details:
39
- # :knockout_prefix => The purpose of this is to provide a way to remove elements
40
- # from existing Hash by specifying them in a special way in incoming hash
41
- # source = {:x => ['--1', '2']}
42
- # dest = {:x => ['1', '3']}
43
- # dest.ko_deep_merge!(source)
44
- # Results: {:x => ['2','3']}
45
- # Additionally, if the knockout_prefix is passed alone as a string, it will cause
46
- # the entire element to be removed:
47
- # source = {:x => '--'}
48
- # dest = {:x => [1,2,3]}
49
- # dest.ko_deep_merge!(source)
50
- # Results: {:x => ""}
51
- # :unpack_arrays => The purpose of this is to permit compound elements to be passed
52
- # in as strings and to be converted into discrete array elements
53
- # irsource = {:x => ['1,2,3', '4']}
54
- # dest = {:x => ['5','6','7,8']}
55
- # dest.deep_merge!(source, {:unpack_arrays => ','})
56
- # Results: {:x => ['1','2','3','4','5','6','7','8'}
57
- # Why: If receiving data from an HTML form, this makes it easy for a checkbox
58
- # to pass multiple values from within a single HTML element
59
- #
60
- # There are many tests for this library - and you can learn more about the features
61
- # and usages of deep_merge! by just browsing the test examples
62
- def DeepMerge.deep_merge!(source, dest, options = {})
63
- # turn on this line for stdout debugging text
64
- merge_debug = options[:merge_debug] || false
65
- overwrite_unmergeable = !options[:preserve_unmergeables]
66
- knockout_prefix = options[:knockout_prefix] || nil
67
- if knockout_prefix == ""; raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!"; end
68
- if knockout_prefix && !overwrite_unmergeable; raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!"; end
69
- # if present: we will split and join arrays on this char before merging
70
- array_split_char = options[:unpack_arrays] || false
71
- # request that we sort together any arrays when they are merged
72
- sort_merged_arrays = options[:sort_merged_arrays] || false
73
- di = options[:debug_indent] || ''
74
- # do nothing if source is nil
75
- if source.nil? || (!source.is_a?(FalseClass) && source.respond_to?(:blank?) && source.blank?); return dest; end
76
- # if dest doesn't exist, then simply copy source to it
77
- if dest.nil? && overwrite_unmergeable; dest = source; return dest; end
78
-
79
- puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
80
- if source.kind_of?(Hash)
81
- puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
82
- source.each do |src_key, src_value|
83
- if dest.kind_of?(Hash)
84
- puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
85
- if !dest[src_key].nil?
86
- puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
87
- dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + ' '))
88
- else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
89
- puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
90
- # note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
91
- begin
92
- src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
93
- rescue TypeError
94
- src_dup = src_value
95
- end
96
- dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + ' '))
97
- end
98
- else # dest isn't a hash, so we overwrite it completely (if permitted)
99
- if overwrite_unmergeable
100
- puts "#{di} overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over-> #{dest.inspect}" if merge_debug
101
- dest = overwrite_unmergeables(source, dest, options)
102
- end
103
- end
104
- end
105
- elsif source.kind_of?(Array)
106
- puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
107
- # if we are instructed, join/split any source arrays before processing
108
- if array_split_char
109
- puts "#{di} split/join on source: #{source.inspect}" if merge_debug
110
- source = source.join(array_split_char).split(array_split_char)
111
- if dest.kind_of?(Array); dest = dest.join(array_split_char).split(array_split_char); end
112
- end
113
- # if there's a naked knockout_prefix in source, that means we are to truncate dest
114
- if source.index(knockout_prefix); dest = clear_or_nil(dest); source.delete(knockout_prefix); end
115
- if dest.kind_of?(Array)
116
- if knockout_prefix
117
- print "#{di} knocking out: " if merge_debug
118
- # remove knockout prefix items from both source and dest
119
- source.delete_if do |ko_item|
120
- retval = false
121
- item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item
122
- if item != ko_item
123
- print "#{ko_item} - " if merge_debug
124
- dest.delete(item)
125
- dest.delete(ko_item)
126
- retval = true
127
- end
128
- retval
129
- end
130
- puts if merge_debug
131
- end
132
- puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
133
- dest = dest | source
134
- if sort_merged_arrays; dest.sort!; end
135
- elsif overwrite_unmergeable
136
- puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
137
- dest = overwrite_unmergeables(source, dest, options)
138
- end
139
- else # src_hash is not an array or hash, so we'll have to overwrite dest
140
- puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
141
- dest = overwrite_unmergeables(source, dest, options)
142
- end
143
- puts "#{di}Returning #{dest.inspect}" if merge_debug
144
- dest
145
- end # deep_merge!
146
-
147
- # allows deep_merge! to uniformly handle overwriting of unmergeable entities
148
- def DeepMerge::overwrite_unmergeables(source, dest, options)
149
- merge_debug = options[:merge_debug] || false
150
- overwrite_unmergeable = !options[:preserve_unmergeables]
151
- knockout_prefix = options[:knockout_prefix] || false
152
- di = options[:debug_indent] || ''
153
- if knockout_prefix && overwrite_unmergeable
154
- if source.kind_of?(String) # remove knockout string from source before overwriting dest
155
- src_tmp = source.gsub(%r{^#{knockout_prefix}},"")
156
- elsif source.kind_of?(Array) # remove all knockout elements before overwriting dest
157
- src_tmp = source.delete_if {|ko_item| ko_item.kind_of?(String) && ko_item.match(%r{^#{knockout_prefix}}) }
158
- else
159
- src_tmp = source
160
- end
161
- if src_tmp == source # if we didn't find a knockout_prefix then we just overwrite dest
162
- puts "#{di}#{src_tmp.inspect} -over-> #{dest.inspect}" if merge_debug
163
- dest = src_tmp
164
- else # if we do find a knockout_prefix, then we just delete dest
165
- puts "#{di}\"\" -over-> #{dest.inspect}" if merge_debug
166
- dest = ""
167
- end
168
- elsif overwrite_unmergeable
169
- dest = source
170
- end
171
- dest
172
- end
173
-
174
- def DeepMerge::clear_or_nil(obj)
175
- if obj.respond_to?(:clear)
176
- obj.clear
177
- else
178
- obj = nil
179
- end
180
- obj
181
- end
182
-
183
- end # module DeepMerge
184
- end
185
- end
@@ -1,90 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'extlib'
4
- require File.dirname(__FILE__)+'/../lib/application_config/config_builder'
5
- require File.dirname(__FILE__)+'/../lib/application_config/deep_merge'
6
-
7
- module RailsConfig
8
- module Settings
9
- class BuilderTest < Test::Unit::TestCase
10
- def setup
11
- @settings_path = File.dirname(__FILE__)+"/test_configs"
12
- end
13
-
14
- def test_missing_files
15
- files = ["#{@settings_path}/empty1.yml", "#{@settings_path}/empty2.yml"]
16
- config = RailsConfig::Settings::Builder.load_files(:paths => files)
17
- assert_equal OpenStruct.new, config
18
- end
19
-
20
- def test_empty_files
21
- files = ["#{@settings_path}/empty1.yml", "#{@settings_path}/empty2.yml"]
22
- config = RailsConfig::Settings::Builder.load_files(:paths => files)
23
- assert_equal OpenStruct.new, config
24
- end
25
-
26
- def test_common
27
- config = RailsConfig::Settings::Builder.load_files(:paths => "#{@settings_path}/settings.yml")
28
- assert_equal 1, config.size
29
- assert_equal 'google.com', config.server
30
- end
31
-
32
- def test_environment_override
33
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/settings.yml", "#{@settings_path}/development.yml"])
34
- assert_equal 2, config.size
35
- assert_equal 'google.com', config.server
36
- end
37
-
38
- def test_nested
39
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/development.yml"])
40
- assert_equal 3, config.section.size
41
- end
42
-
43
- def test_array
44
- config = RailsConfig::Settings::Builder.load_files(:paths => "#{@settings_path}/development.yml")
45
- assert_equal 'yahoo.com', config.section.servers[0].name
46
- assert_equal 'amazon.com', config.section.servers[1].name
47
- end
48
-
49
- def test_erb
50
- config = RailsConfig::Settings::Builder.load_files(:paths => "#{@settings_path}/development.yml")
51
- assert_equal 6, config.computed
52
- end
53
-
54
- def test_merge_hashes_from_multiple_configs
55
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/deep_merge/config1.yml", "#{@settings_path}/deep_merge/config2.yml"])
56
-
57
- assert_equal 3, config.inner.marshal_dump.keys.size
58
- assert_equal 3, config.inner2.inner2_inner.marshal_dump.keys.size
59
- end
60
-
61
-
62
- def test_merge_arrays_from_multiple_configs
63
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/deep_merge/config1.yml", "#{@settings_path}/deep_merge/config2.yml"])
64
- assert_equal 6, config.arraylist1.size
65
- assert_equal 6, config.arraylist2.inner.size
66
- end
67
-
68
- def test_merge_assets
69
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/deep_merge/config1.yml", "#{@settings_path}/deep_merge/config2.yml"])
70
- merged = RailsConfig::Settings::Builder.merge_assets(config.hash_array)
71
-
72
- assert_equal 3, merged.size
73
- assert_equal 6, merged.select{|i| i.is_a?(Hash)}.first[:inner].size
74
- end
75
-
76
- def test_merge_assets2
77
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/deep_merge2/config1.yml", "#{@settings_path}/deep_merge2/config2.yml"])
78
-
79
- assert_equal 500, config.tvrage.cache
80
- assert_equal "http://url2", config.tvrage.service_url
81
- end
82
-
83
- def test_boolean_overrides
84
- config = RailsConfig::Settings::Builder.load_files(:paths => ["#{@settings_path}/bool_override/config1.yml", "#{@settings_path}/bool_override/config2.yml"])
85
- assert_equal false, config.override_bool
86
- assert_equal true, config.override_bool_opposite
87
- end
88
- end
89
- end
90
- end