cjbottaro-app_config 1.0.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.
- data/README.rdoc +59 -0
- data/Rakefile +38 -0
- data/VERSION.yml +4 -0
- data/lib/app_config.rb +66 -0
- data/lib/closed_struct.rb +39 -0
- data/test/app_config.yml +5 -0
- data/test/app_config_test.rb +64 -0
- data/test/development.yml +8 -0
- data/test/empty1.yml +0 -0
- data/test/empty2.yml +0 -0
- metadata +63 -0
data/README.rdoc
ADDED
@@ -0,0 +1,59 @@
|
|
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
|
+
== Author
|
59
|
+
Christopher J. Bottaro
|
data/Rakefile
ADDED
@@ -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
|
data/VERSION.yml
ADDED
data/lib/app_config.rb
ADDED
@@ -0,0 +1,66 @@
|
|
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
|
+
conf = recursive_merge(conf1, conf2)
|
25
|
+
@config = convert(conf)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def method_missing(name, *args)
|
31
|
+
if @config.respond_to?(name)
|
32
|
+
@config.send(name, *args)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_conf_file(conf_path)
|
39
|
+
return {} if !conf_path or conf_path.empty?
|
40
|
+
File.open(conf_path, "r") do |file|
|
41
|
+
YAML.load(ERB.new(file.read).result) || {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Recursively converts Hashes to ClosedStructs (including Hashes inside Arrays)
|
46
|
+
def convert(h) #:nodoc:
|
47
|
+
s = ClosedStruct.new(h.keys)
|
48
|
+
h.each do |k, v|
|
49
|
+
if v.instance_of?(Hash)
|
50
|
+
s.send( (k+'=').to_sym, convert(v))
|
51
|
+
elsif v.instance_of?(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
|
+
|
61
|
+
# Recursively merges hashes. h2 will overwrite h1.
|
62
|
+
def recursive_merge(h1, h2) #:nodoc:
|
63
|
+
h1.merge(h2){ |k, v1, v2| v2.kind_of?(Hash) ? recursive_merge(v1, v2) : v2 }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
class ClosedStruct < OpenStruct # :nodoc:
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
if args.length == 1
|
7
|
+
super(args.first)
|
8
|
+
else
|
9
|
+
h = args.inject({}){ |memo, k| memo[k] = nil; memo }
|
10
|
+
super(h)
|
11
|
+
end
|
12
|
+
@closed = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_ostruct_member(name)
|
16
|
+
if @closed
|
17
|
+
raise RuntimeError, "cannot add members to closed struct"
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(name, *args)
|
24
|
+
raise NoMethodError, "undefined method '#{name}' for #{self}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
if @table.has_key?(:id)
|
29
|
+
@table[:id]
|
30
|
+
else
|
31
|
+
method_missing(:id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_h
|
36
|
+
@table.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/test/app_config.yml
ADDED
@@ -0,0 +1,64 @@
|
|
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
|
+
end
|
data/test/empty1.yml
ADDED
File without changes
|
data/test/empty2.yml
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cjbottaro-app_config
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher J Bottaro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-01 00:00:00 -07: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
|
+
- README.rdoc
|
26
|
+
- Rakefile
|
27
|
+
- VERSION.yml
|
28
|
+
- lib/app_config.rb
|
29
|
+
- lib/closed_struct.rb
|
30
|
+
- test/app_config.yml
|
31
|
+
- test/app_config_test.rb
|
32
|
+
- test/development.yml
|
33
|
+
- test/empty1.yml
|
34
|
+
- test/empty2.yml
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/cjbottaro/app_config
|
37
|
+
licenses:
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options:
|
40
|
+
- --charset=UTF-8
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.5
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: Application level configuration.
|
62
|
+
test_files:
|
63
|
+
- test/app_config_test.rb
|