configurate 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/configurate.rb +69 -0
- data/lib/configurate/lookup_chain.rb +65 -0
- data/lib/configurate/provider.rb +19 -0
- data/lib/configurate/provider/dynamic.rb +24 -0
- data/lib/configurate/provider/env.rb +14 -0
- data/lib/configurate/provider/yaml.rb +52 -0
- data/lib/configurate/proxy.rb +76 -0
- metadata +53 -0
data/lib/configurate.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
require 'configurate/lookup_chain'
|
3
|
+
require 'configurate/provider'
|
4
|
+
require 'configurate/proxy'
|
5
|
+
|
6
|
+
|
7
|
+
# A flexible and extendable configuration system.
|
8
|
+
# The calling logic is isolated from the lookup logic
|
9
|
+
# through configuration providers, which only requirement
|
10
|
+
# is to define the +#lookup+ method and show a certain behavior on that.
|
11
|
+
# The providers are asked in the order they were added until one provides
|
12
|
+
# a response. This allows to even add multiple providers of the same type,
|
13
|
+
# you never easier defined your default configuration parameters.
|
14
|
+
# There are no class methods used, you can have an unlimited amount of
|
15
|
+
# independent configuration sources at the same time.
|
16
|
+
#
|
17
|
+
# See {Settings} for a quick start.
|
18
|
+
module Configurate
|
19
|
+
# This is your main entry point. Instead of lengthy explanations
|
20
|
+
# let an example demonstrate its usage:
|
21
|
+
#
|
22
|
+
# require Rails.root.join('lib', 'configuration')
|
23
|
+
#
|
24
|
+
# AppSettings = Configuration::Settings.create do
|
25
|
+
# add_provider Configuration::Provider::Env
|
26
|
+
# add_provider Configuration::Provider::YAML, '/etc/app_settings.yml',
|
27
|
+
# namespace: Rails.env, required: false
|
28
|
+
# add_provider Configuration::Provider::YAML, 'config/default_settings.yml'
|
29
|
+
#
|
30
|
+
# extend YourConfigurationMethods
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# AppSettings.setup_something if AppSettings.something.enable?
|
34
|
+
#
|
35
|
+
# Please also read the note at {Proxy}!
|
36
|
+
class Settings
|
37
|
+
|
38
|
+
attr_reader :lookup_chain
|
39
|
+
|
40
|
+
undef_method :method # Remove possible conflicts with common setting names
|
41
|
+
|
42
|
+
# @!method lookup(setting)
|
43
|
+
# (see LookupChain#lookup)
|
44
|
+
# @!method add_provider(provider, *args)
|
45
|
+
# (see LookupChain#add_provider)
|
46
|
+
# @!method [](setting)
|
47
|
+
# (see LookupChain#[])
|
48
|
+
def method_missing(method, *args, &block)
|
49
|
+
return @lookup_chain.send(method, *args, &block) if [:lookup, :add_provider, :[]].include?(method)
|
50
|
+
|
51
|
+
Proxy.new(@lookup_chain).send(method, *args, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize
|
55
|
+
@lookup_chain = LookupChain.new
|
56
|
+
$stderr.puts "Warning you called Configuration::Settings.new with a block, you really meant to call #create" if block_given?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create a new configuration object
|
60
|
+
# @yield the given block will be evaluated in the context of the new object
|
61
|
+
def self.create(&block)
|
62
|
+
config = self.new
|
63
|
+
config.instance_eval(&block) if block_given?
|
64
|
+
config
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class SettingNotFoundError < RuntimeError; end
|
69
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Configurate
|
2
|
+
# This object builds a chain of configuration providers to try to find
|
3
|
+
# a setting.
|
4
|
+
class LookupChain
|
5
|
+
def initialize
|
6
|
+
@provider = []
|
7
|
+
end
|
8
|
+
|
9
|
+
# Add a provider to the chain. Providers are tried in the order
|
10
|
+
# they are added, so the order is important.
|
11
|
+
#
|
12
|
+
# @param provider [#lookup]
|
13
|
+
# @param *args the arguments passed to the providers constructor
|
14
|
+
# @raise [ArgumentError] if an invalid provider is given
|
15
|
+
# @return [void]
|
16
|
+
def add_provider(provider, *args)
|
17
|
+
unless provider.respond_to?(:instance_methods) && provider.instance_methods.include?(:lookup)
|
18
|
+
raise ArgumentError, "the given provider does not respond to lookup"
|
19
|
+
end
|
20
|
+
|
21
|
+
@provider << provider.new(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Tries all providers in the order they were added to provide a response
|
26
|
+
# for setting.
|
27
|
+
#
|
28
|
+
# @param setting [#to_s] settings should be underscore_case,
|
29
|
+
# nested settings should be separated by a dot
|
30
|
+
# @param *args further args passed to the provider
|
31
|
+
# @return [Array,String,Boolean,nil] whatever the provider provides
|
32
|
+
# is casted to a {String}, except for some special values
|
33
|
+
def lookup(setting, *args)
|
34
|
+
setting = setting.to_s
|
35
|
+
|
36
|
+
@provider.each do |provider|
|
37
|
+
begin
|
38
|
+
return special_value_or_string(provider.lookup(setting, *args))
|
39
|
+
rescue SettingNotFoundError; end
|
40
|
+
end
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
alias_method :[], :lookup
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def special_value_or_string(value)
|
49
|
+
if [TrueClass, FalseClass, NilClass, Array, Hash].include?(value.class)
|
50
|
+
return value
|
51
|
+
elsif value.is_a?(String)
|
52
|
+
return case value.strip
|
53
|
+
when "true" then true
|
54
|
+
when "false" then false
|
55
|
+
when "", "nil" then nil
|
56
|
+
else value
|
57
|
+
end
|
58
|
+
elsif value.respond_to?(:to_s)
|
59
|
+
return value.to_s
|
60
|
+
else
|
61
|
+
return value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Configurate; module Provider
|
2
|
+
# This provides a basic {#lookup} method for other providers to build
|
3
|
+
# upon. Childs are expected to define +lookup_path(path, *args)+ where
|
4
|
+
# +path+ will be passed an array of settings generated by splitting the
|
5
|
+
# called setting at the dots. The method should return nil if the setting
|
6
|
+
# wasn't found and {#lookup} will raise an {SettingNotFoundError} in that
|
7
|
+
# case.
|
8
|
+
class Base
|
9
|
+
def lookup(setting, *args)
|
10
|
+
result = lookup_path(setting.split("."), *args)
|
11
|
+
return result unless result.nil?
|
12
|
+
raise Configurate::SettingNotFoundError, "The setting #{setting} was not found"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end; end
|
16
|
+
|
17
|
+
require 'configurate/provider/yaml'
|
18
|
+
require 'configurate/provider/env'
|
19
|
+
require 'configurate/provider/dynamic'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Configurate; module Provider
|
2
|
+
# This provider knows nothing upon initialization, however if you access
|
3
|
+
# a setting ending with +=+ and give one argument to that call it remembers
|
4
|
+
# that setting, stripping the +=+ and will return it on the next call
|
5
|
+
# without +=+.
|
6
|
+
class Dynamic < Base
|
7
|
+
def initialize
|
8
|
+
@settings = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def lookup_path(settings_path, *args)
|
12
|
+
key = settings_path.join(".")
|
13
|
+
|
14
|
+
if key.end_with?("=") && args.length > 0
|
15
|
+
key = key.chomp("=")
|
16
|
+
value = args.first
|
17
|
+
value = value.get if value.respond_to?(:_proxy?) && value._proxy?
|
18
|
+
@settings[key] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
@settings[key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end; end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Configurate; module Provider
|
2
|
+
# This provider looks for settings in the environment.
|
3
|
+
# For the setting +foo.bar_baz+ this provider will look for an
|
4
|
+
# environment variable +FOO_BAR_BAZ+, replacing all dots in the setting
|
5
|
+
# and upcasing the result. If an value contains +,+ it's split at them
|
6
|
+
# and returned as array.
|
7
|
+
class Env < Base
|
8
|
+
def lookup_path(settings_path, *args)
|
9
|
+
value = ENV[settings_path.join("_").upcase]
|
10
|
+
value = value.split(",") if value && value.include?(",")
|
11
|
+
value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end; end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Configurate; module Provider
|
4
|
+
# This provider tries to open a YAML file and does in nested lookups
|
5
|
+
# in it.
|
6
|
+
class YAML < Base
|
7
|
+
# @param file [String] the path to the file
|
8
|
+
# @param opts [Hash]
|
9
|
+
# @option opts [String] :namespace optionally set this as the root
|
10
|
+
# @option opts [Boolean] :required wheter or not to raise an error if
|
11
|
+
# the file or the namespace, if given, is not found. Defaults to +true+.
|
12
|
+
# @raise [ArgumentError] if the namespace isn't found in the file
|
13
|
+
# @raise [Errno:ENOENT] if the file isn't found
|
14
|
+
def initialize(file, opts = {})
|
15
|
+
@settings = {}
|
16
|
+
required = opts.has_key?(:required) ? opts.delete(:required) : true
|
17
|
+
|
18
|
+
@settings = ::YAML.load_file(file)
|
19
|
+
|
20
|
+
namespace = opts.delete(:namespace)
|
21
|
+
unless namespace.nil?
|
22
|
+
actual_settings = lookup_in_hash(namespace.split("."), @settings)
|
23
|
+
unless actual_settings.nil?
|
24
|
+
@settings = actual_settings
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Namespace #{namespace} not found in #{file}" if required
|
27
|
+
end
|
28
|
+
end
|
29
|
+
rescue Errno::ENOENT => e
|
30
|
+
$stderr.puts "WARNING: configuration file #{file} not found, ensure it's present"
|
31
|
+
raise e if required
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def lookup_path(settings_path, *args)
|
36
|
+
lookup_in_hash(settings_path, @settings)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def lookup_in_hash(setting_path, hash)
|
42
|
+
setting = setting_path.shift
|
43
|
+
if hash.has_key?(setting)
|
44
|
+
if setting_path.length > 0 && hash[setting].is_a?(Hash)
|
45
|
+
return lookup_in_hash(setting_path, hash[setting]) if setting.length > 1
|
46
|
+
else
|
47
|
+
return hash[setting]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end; end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Configurate
|
2
|
+
# Proxy object to support nested settings
|
3
|
+
# Cavehat: Since this is always true, adding a ? at the end
|
4
|
+
# returns the value, if found, instead of the proxy object.
|
5
|
+
# So instead of +if settings.foo.bar+ use +if settings.foo.bar?+
|
6
|
+
# to check for boolean values, +if settings.foo.bar.nil?+ to
|
7
|
+
# check for nil values, +if settings.foo.bar.present?+ to check for
|
8
|
+
# empty values if you're in Rails and call {#get} to actually return the value,
|
9
|
+
# commonly when doing +settings.foo.bar.get || 'default'+. If a setting
|
10
|
+
# ends with +=+ is too called directly, just like with +?+.
|
11
|
+
class Proxy < BasicObject
|
12
|
+
COMMON_KEY_NAMES = [:key, :method]
|
13
|
+
|
14
|
+
# @param lookup_chain [#lookup]
|
15
|
+
def initialize(lookup_chain)
|
16
|
+
@lookup_chain = lookup_chain
|
17
|
+
@setting = ""
|
18
|
+
end
|
19
|
+
|
20
|
+
def !
|
21
|
+
!self.get
|
22
|
+
end
|
23
|
+
|
24
|
+
def !=(other)
|
25
|
+
self.get != other
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
self.get == other
|
30
|
+
end
|
31
|
+
|
32
|
+
def _proxy?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def respond_to?(method, include_private=false)
|
37
|
+
method == :_proxy? || self.get.respond_to?(method, include_private)
|
38
|
+
end
|
39
|
+
|
40
|
+
def send(*args, &block)
|
41
|
+
self.__send__(*args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(setting, *args, &block)
|
45
|
+
unless COMMON_KEY_NAMES.include? setting
|
46
|
+
target = self.get
|
47
|
+
if !(target.respond_to?(:_proxy?) && target._proxy?) && target.respond_to?(setting)
|
48
|
+
return target.send(setting, *args, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
setting = setting.to_s
|
53
|
+
|
54
|
+
self.append_setting(setting)
|
55
|
+
|
56
|
+
return self.get(*args) if setting.end_with?("?") || setting.end_with?("=")
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get the setting at the current path, if found.
|
62
|
+
# (see LookupChain#lookup)
|
63
|
+
def get(*args)
|
64
|
+
setting = @setting[1..-1]
|
65
|
+
return unless setting
|
66
|
+
val = @lookup_chain.lookup(setting.chomp("?"), *args)
|
67
|
+
val
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
def append_setting(setting)
|
72
|
+
@setting << "."
|
73
|
+
@setting << setting
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: configurate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jonne Haß
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-30 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Configurate is a flexible configuration system that can read settings
|
15
|
+
from multiple sources at the same time.
|
16
|
+
email: me@mrzyx.de
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/configurate/provider/env.rb
|
22
|
+
- lib/configurate/provider/yaml.rb
|
23
|
+
- lib/configurate/provider/dynamic.rb
|
24
|
+
- lib/configurate/proxy.rb
|
25
|
+
- lib/configurate/lookup_chain.rb
|
26
|
+
- lib/configurate/provider.rb
|
27
|
+
- lib/configurate.rb
|
28
|
+
homepage: https://github.com/MrZYX/configurate
|
29
|
+
licenses:
|
30
|
+
- MIT
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.24
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: Flexbile configuration system
|
53
|
+
test_files: []
|