viva-app_config 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ 10/03/2008
2
+ * recursively merge the configuration hashes
3
+
4
+ 07/01/2009
5
+ * Packaged as a gem (but still works as a Rails plugin).
6
+ * The app config object is now an instance of ApplicationConfiguration.
7
+ * NoMethodError raised if you try to access a config element that doesn't exist.
8
+ * ApplicationConfiguration#reload! to reread the config files and rebuild the app config object.
9
+
10
+ 08/27/2009
11
+ * Updated the ClosedStruct class.
12
+
13
+ 08/28/2009
14
+ * Added support for "environments" in the YAML files.
@@ -0,0 +1,89 @@
1
+ == Summary
2
+ Application level configuration.
3
+
4
+ == Features
5
+
6
+ * simple YAML config files
7
+ * config files support ERB
8
+ * config files support inheritance
9
+ * access config information via convenient object member notation
10
+
11
+ === Basic Usage
12
+
13
+ You simply write a configuration file in YAML. Notice you can use ERB.
14
+
15
+ <em>config.yml</em>
16
+ aws:
17
+ access_key: 123ABC
18
+ secret_key: ABC123
19
+ now: <%= Time.now %>
20
+ servers: [ {name: example1.com}, {name: example2.com} ]
21
+
22
+ Then somewhere in your code, you create a global constant from the config file. Then access the config data via object member notation.
23
+
24
+ _code_
25
+ ::AppConfig = ApplicationConfiguration.new("config.yml")
26
+ AppConfig.aws.access_key # => "123ABC"
27
+ AppConfig.aws.secret_key # => "ABC123"
28
+ AppConfig.now # => Tue May 05 21:55:15 -0500 2009
29
+ AppConfig.servers[0].name # => "example1.com"
30
+
31
+ === Inheritance
32
+
33
+ You can have a second config file that is recursively merged with the first config file.
34
+
35
+ <em>base.yml</em>
36
+ app_name: MyCoolApp
37
+ domain: dev.mycoolapp.com
38
+
39
+ <em>production.yml</em>
40
+ domain: www.mycoolapp.com
41
+
42
+ _code_
43
+ ::AppConfig = ApplicationConfiguration.new("base.yml", "production.yml")
44
+ AppConfig.app_name # => "MyCoolApp"
45
+ AppConfig.domain # => "www.mycoolapp.com"
46
+
47
+ === Using in a Rails app
48
+
49
+ You just need to create an initializer that looks something like this.
50
+
51
+ require 'app_config'
52
+ ::AppConfig = ApplicationConfiguration.new(RAILS_ROOT+"/config/app_config.yml",
53
+ RAILS_ROOT+"/config/environments/#{RAILS_ENV}.yml")
54
+
55
+ If you installed this as a Rails plugin instead of a gem, that code is already run for you in
56
+ the plugin's init.rb.
57
+
58
+ === Environments
59
+
60
+ Alternatively to splitting out your environments into separate files, you can just have a single file which defines
61
+ the application configuration for all environments (much like how databases.yml works). Note if you do this, nested
62
+ configurations will not be recursively merged. See example below.
63
+
64
+ <em>app_config.yml</em>
65
+ defaults: &defaults
66
+ one: won
67
+ two: too
68
+ nested:
69
+ foo: foo
70
+ bar: bar
71
+
72
+ development:
73
+ <<: *defaults
74
+ two: new
75
+ nested:
76
+ foo: bar
77
+
78
+ _code_
79
+ RAILS_ENV # => "development"
80
+ ::AppConfig = ApplicationConfiguration.new("app_config.yml")
81
+ AppConfig.use_environment!(RAILS_ENV)
82
+ AppConfig.one # => "won"
83
+ AppConfig.two # => "new"
84
+ AppConfig.nested.foo # => "bar"
85
+ AppConfig.nested.bar # raises NoMethodError because nested configurations are not recursively merged
86
+
87
+
88
+ == Author
89
+ Christopher J. Bottaro
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "app_config"
9
+ gem.summary = %Q{Application level configuration.}
10
+ gem.description = %Q{Application level configuration that supports YAML config file, inheritance, ERB, and object member notation.}
11
+ gem.email = "cjbottaro@alumni.cs.utexas.edu"
12
+ gem.homepage = "http://github.com/cjbottaro/app_config"
13
+ gem.authors = ["Christopher J Bottaro"]
14
+
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
20
+
21
+ desc 'Default: run unit tests.'
22
+ task :default => :test
23
+
24
+ desc 'Test the app_config plugin.'
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << 'lib'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = true
29
+ end
30
+
31
+ desc 'Generate documentation for the app_config plugin.'
32
+ Rake::RDocTask.new(:rdoc) do |rdoc|
33
+ rdoc.rdoc_dir = 'rdoc'
34
+ rdoc.title = 'AppConfig'
35
+ rdoc.options << '--line-numbers' << '--inline-source'
36
+ rdoc.rdoc_files.include('README.rdoc')
37
+ rdoc.rdoc_files.include('lib/**/*.rb')
38
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 1
4
+ :minor: 2
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{viva-app_config}
8
+ s.version = "1.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christopher J Bottaro"]
12
+ s.date = %q{2009-09-08}
13
+ s.description = %q{Application level configuration that supports YAML config file, inheritance, ERB, and object member notation.}
14
+ s.email = %q{cjbottaro@alumni.cs.utexas.edu}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "CHANGELOG",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION.yml",
23
+ "app_config.gemspec",
24
+ "init.rb",
25
+ "install.rb",
26
+ "lib/app_config.rb",
27
+ "lib/closed_struct.rb",
28
+ "tasks/app_config_tasks.rake",
29
+ "test/app_config.yml",
30
+ "test/app_config_test.rb",
31
+ "test/closed_struct_test.rb",
32
+ "test/development.yml",
33
+ "test/empty1.yml",
34
+ "test/empty2.yml",
35
+ "test/environments.yml",
36
+ "test/override_with.yml",
37
+ "uninstall.rb"
38
+ ]
39
+ s.has_rdoc = true
40
+ s.homepage = %q{http://github.com/cjbottaro/app_config}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.2}
44
+ s.summary = %q{Application level configuration.}
45
+ s.test_files = [
46
+ "test/app_config_test.rb",
47
+ "test/closed_struct_test.rb"
48
+ ]
49
+
50
+ if s.respond_to? :specification_version then
51
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
+ else
56
+ end
57
+ else
58
+ end
59
+ end
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'app_config'
2
+
3
+ ::AppConfig = ApplicationConfiguration.new(RAILS_ROOT+"/config/app_config.yml",
4
+ RAILS_ROOT+"/config/environments/#{RAILS_ENV}.yml")
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,64 @@
1
+ require 'closed_struct'
2
+ require 'yaml'
3
+ require 'erb'
4
+
5
+ class ApplicationConfiguration
6
+
7
+ # Create a new ApplicationConfiguration object. <tt>conf_path_1</tt> is the path to your YAML configuration file.
8
+ # If <tt>conf_path_2</tt> is given, the contents are recursively merged with the contents of <tt>conf_path_1</tt>.
9
+ # This allows you to have a "base" configuration with settings that are overrided by "environment specific"
10
+ # (developement, test, production, etc) settings.
11
+ #
12
+ # Ex:
13
+ # ApplicationConfiguration.new(RAILS_ROOT+"/config/base.yml", RAILS_ROOT+"/environments/#{RAILS_ENV}_config.yml")
14
+ def initialize(conf_path_1, conf_path_2 = nil)
15
+ @conf_path_1, @conf_path_2 = conf_path_1, conf_path_2
16
+ reload!
17
+ end
18
+
19
+ # Rereads your configuration files and rebuilds your ApplicationConfiguration object. This is useful
20
+ # for when you edit your config files, but don't want to restart your web server.
21
+ def reload!
22
+ conf1 = load_conf_file(@conf_path_1)
23
+ conf2 = load_conf_file(@conf_path_2)
24
+ @config_hash = recursive_merge(conf1, conf2)
25
+ @config = ClosedStruct.r_new(@config_hash)
26
+ end
27
+
28
+ def use_environment!(environment, options = {})
29
+ raise ArgumentError, "environment doesn't exist in app config: #{environment}" \
30
+ unless @config_hash.has_key?(environment.to_s)
31
+
32
+ @config_hash = @config_hash[environment.to_s]
33
+ @config = @config.send(environment)
34
+
35
+ if options[:override_with] and File.exist?(options[:override_with])
36
+ overriding_config = load_conf_file(options[:override_with])
37
+ @config_hash = recursive_merge(@config_hash, overriding_config)
38
+ @config = ClosedStruct.r_new(@config_hash)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def method_missing(name, *args)
45
+ if @config.respond_to?(name)
46
+ @config.send(name, *args)
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ def load_conf_file(conf_path)
53
+ return {} if !conf_path or conf_path.empty?
54
+ File.open(conf_path, "r") do |file|
55
+ YAML.load(ERB.new(file.read).result) || {}
56
+ end
57
+ end
58
+
59
+ # Recursively merges hashes. h2 will overwrite h1.
60
+ def recursive_merge(h1, h2) #:nodoc:
61
+ h1.merge(h2){ |k, v1, v2| v2.kind_of?(Hash) ? recursive_merge(v1, v2) : v2 }
62
+ end
63
+
64
+ end
@@ -0,0 +1,60 @@
1
+ require 'ostruct'
2
+
3
+ # Like OpenStruct, but raises an exception if you try to access a member that wasn't specified in the initializer.
4
+ class ClosedStruct < OpenStruct
5
+
6
+ def self.r_new(hash)
7
+ closed_struct = ClosedStruct.new(hash)
8
+ closed_struct.send(:recursive_initialize)
9
+ closed_struct
10
+ end
11
+
12
+ def initialize(*args)
13
+ if args.length == 1 and args.first.kind_of?(Hash)
14
+ super(args.first)
15
+ elsif args.length > 1 and args.all?{ |arg| [Symbol, String].include?(arg.class) }
16
+ args = args.inject({}){ |memo, arg| memo[arg.to_sym] = nil; memo }
17
+ super(args)
18
+ else
19
+ raise ArgumentError, "invalid arguments: #{args.inspect}"
20
+ end
21
+ @closed = true
22
+ end
23
+
24
+ def new_ostruct_member(name)
25
+ if @closed
26
+ raise RuntimeError, "cannot add members to closed struct"
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def method_missing(name, *args)
33
+ raise NoMethodError, "undefined method '#{name}' for #{self}"
34
+ end
35
+
36
+ def id
37
+ if @table.has_key?(:id)
38
+ @table[:id]
39
+ else
40
+ method_missing(:id)
41
+ end
42
+ end
43
+
44
+ def to_h
45
+ @table.dup
46
+ end
47
+
48
+ private
49
+
50
+ def recursive_initialize
51
+ @table.each do |k, v|
52
+ if v.kind_of?(Hash)
53
+ @table[k] = ClosedStruct.r_new(v)
54
+ elsif v.kind_of?(Array)
55
+ @table[k] = v.collect{ |e| e.kind_of?(Hash) ? ClosedStruct.r_new(e) : e }
56
+ end
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :app_config do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,5 @@
1
+ size: 1
2
+ server: google.com
3
+ emails:
4
+ support: support@domain.com
5
+ webmaster: web@domain.com
@@ -0,0 +1,105 @@
1
+ require 'test/unit'
2
+ require 'app_config'
3
+
4
+ class AppConfigTest < Test::Unit::TestCase
5
+
6
+ def test_missing_files
7
+ assert_raise(Errno::ENOENT){ ApplicationConfiguration.new('not_here1', 'not_here2') }
8
+ end
9
+
10
+ def test_empty_files
11
+ config = ApplicationConfiguration.new('test/empty1.yml', 'test/empty2.yml')
12
+ assert_equal OpenStruct.new, config.instance_variable_get("@config")
13
+ end
14
+
15
+ def test_common
16
+ config = ApplicationConfiguration.new('test/app_config.yml')
17
+ assert_equal 1, config.size
18
+ assert_equal 'google.com', config.server
19
+ end
20
+
21
+ def test_override
22
+ config = ApplicationConfiguration.new('test/app_config.yml', 'test/development.yml')
23
+ assert_equal 2, config.size
24
+ assert_equal 'google.com', config.server
25
+ end
26
+
27
+ def test_nested
28
+ config = ApplicationConfiguration.new('test/development.yml')
29
+ assert_equal 3, config.section.size
30
+ end
31
+
32
+ def test_array
33
+ config = ApplicationConfiguration.new('test/development.yml')
34
+ assert_equal 'yahoo.com', config.section.servers[0].name
35
+ assert_equal 'amazon.com', config.section.servers[1].name
36
+ end
37
+
38
+ def test_erb
39
+ config = ApplicationConfiguration.new('test/development.yml')
40
+ assert_equal 6, config.computed
41
+ end
42
+
43
+ def test_recursive_merge
44
+ config = ApplicationConfiguration.new('test/app_config.yml', 'test/development.yml')
45
+ assert_equal 'support@domain.com', config.emails.support
46
+ assert_equal 'webmaster@domain.com', config.emails.webmaster
47
+ assert_equal 'feedback@domain.com', config.emails.feedback
48
+ end
49
+
50
+ def test_exception_on_non_existant_values
51
+ config = ApplicationConfiguration.new('test/app_config.yml')
52
+ assert_raise(NoMethodError){ config.not_here1 = "blah" }
53
+ assert_raise(NoMethodError){ config.not_here2 }
54
+ end
55
+
56
+ def test_reload
57
+ config = ApplicationConfiguration.new('test/app_config.yml')
58
+ config.size = 2
59
+ assert_equal 2, config.size
60
+ config.reload!
61
+ assert_equal 1, config.size
62
+ end
63
+
64
+ def test_environments
65
+ config = ApplicationConfiguration.new('test/environments.yml')
66
+ config.use_environment!("development")
67
+ assert_equal 2, config.size
68
+ assert_equal "google.com", config.server
69
+ assert_equal 6, config.computed
70
+ assert_equal 3, config.section.size
71
+ assert_equal "yahoo.com", config.section.servers[0].name
72
+ assert_equal "amazon.com", config.section.servers[1].name
73
+ assert_equal "webmaster@domain.com", config.emails.webmaster
74
+ assert_equal "feedback@domain.com", config.emails.feedback
75
+ assert_raise(NoMethodError){ config.emails.support }
76
+ end
77
+
78
+ def test_use_environment_override_with
79
+ config = ApplicationConfiguration.new('test/environments.yml')
80
+ config.use_environment!("development", :override_with => "test/override_with.yml")
81
+ assert_equal 10, config.size
82
+ assert_equal "over.com", config.section.servers[0].name
83
+ assert_equal "ride.com", config.section.servers[1].name
84
+ assert_equal "google.com", config.server
85
+ assert_equal 6, config.computed
86
+ assert_equal "webmaster@domain.com", config.emails.webmaster
87
+ assert_equal "feedback@domain.com", config.emails.feedback
88
+ assert_raise(NoMethodError){ config.emails.support }
89
+ end
90
+
91
+ def test_use_environment_override_with_no_file
92
+ config = ApplicationConfiguration.new('test/environments.yml')
93
+ config.use_environment!("development", :override_with => "test/non_existant.yml")
94
+ assert_equal 2, config.size
95
+ assert_equal "google.com", config.server
96
+ assert_equal 6, config.computed
97
+ assert_equal 3, config.section.size
98
+ assert_equal "yahoo.com", config.section.servers[0].name
99
+ assert_equal "amazon.com", config.section.servers[1].name
100
+ assert_equal "webmaster@domain.com", config.emails.webmaster
101
+ assert_equal "feedback@domain.com", config.emails.feedback
102
+ assert_raise(NoMethodError){ config.emails.support }
103
+ end
104
+
105
+ end
@@ -0,0 +1,33 @@
1
+ require 'test/unit'
2
+ require 'app_config'
3
+
4
+ class ClosedStructTest < Test::Unit::TestCase
5
+
6
+ def test_from_hash
7
+ s = ClosedStruct.new :a => "a", "b" => "b", :c => 123
8
+ assert_equal "a", s.a
9
+ assert_equal "b", s.b
10
+ assert_equal 123, s.c
11
+ assert_raise(NoMethodError){ s.d }
12
+ end
13
+
14
+ def test_from_array
15
+ s = ClosedStruct.new :a, :b, :c
16
+ s.b = "b"
17
+ assert_nil s.a
18
+ assert_equal "b", s.b
19
+ assert_nil s.c
20
+ assert_raise(NoMethodError){ s.d }
21
+ end
22
+
23
+ def test_nested_hash
24
+ s = ClosedStruct.r_new :a => :a, :b => { :c => :c }, :d => :d
25
+ assert_equal :c, s.b.c
26
+ end
27
+
28
+ def test_nested_hashes_in_array
29
+ s = ClosedStruct.r_new :a => :a, :b => [ {:c => :c }, { :d => :d } ], :e => :e
30
+ assert_equal :c, s.b[0].c
31
+ end
32
+
33
+ end
@@ -0,0 +1,8 @@
1
+ size: 2
2
+ computed: <%= 1 + 2 + 3 %>
3
+ section:
4
+ size: 3
5
+ servers: [ {name: yahoo.com}, {name: amazon.com} ]
6
+ emails:
7
+ webmaster: webmaster@domain.com
8
+ feedback: feedback@domain.com
File without changes
File without changes
@@ -0,0 +1,17 @@
1
+ defaults: &defaults
2
+ size: 1
3
+ server: google.com
4
+ emails:
5
+ support: support@domain.com
6
+ webmaster: web@domain.com
7
+
8
+ development:
9
+ <<: *defaults
10
+ size: 2
11
+ computed: <%= 1 + 2 + 3 %>
12
+ section:
13
+ size: 3
14
+ servers: [ {name: yahoo.com}, {name: amazon.com} ]
15
+ emails:
16
+ webmaster: webmaster@domain.com
17
+ feedback: feedback@domain.com
@@ -0,0 +1,3 @@
1
+ size: 10
2
+ section:
3
+ servers: [ {name: over.com}, {name: ride.com} ]
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: viva-app_config
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Christopher J Bottaro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-08 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Application level configuration that supports YAML config file, inheritance, ERB, and object member notation.
17
+ email: cjbottaro@alumni.cs.utexas.edu
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - CHANGELOG
26
+ - README.rdoc
27
+ - Rakefile
28
+ - VERSION.yml
29
+ - app_config.gemspec
30
+ - init.rb
31
+ - install.rb
32
+ - lib/app_config.rb
33
+ - lib/closed_struct.rb
34
+ - tasks/app_config_tasks.rake
35
+ - test/app_config.yml
36
+ - test/app_config_test.rb
37
+ - test/closed_struct_test.rb
38
+ - test/development.yml
39
+ - test/empty1.yml
40
+ - test/empty2.yml
41
+ - test/environments.yml
42
+ - test/override_with.yml
43
+ - uninstall.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/cjbottaro/app_config
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Application level configuration.
72
+ test_files:
73
+ - test/app_config_test.rb
74
+ - test/closed_struct_test.rb