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.
@@ -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: []