configurate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []