rails_config 0.0.4 → 0.0.5

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