viva-app_config 1.2.0

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