configurate 0.0.1
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/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: []
|