pax_global_header00006660000000000000000000000064125225132350014512gustar00rootroot0000000000000052 comment=ff9f0a277065629af964695b3c9fde653c748534 configurate-0.3.1/000077500000000000000000000000001252251323500140215ustar00rootroot00000000000000configurate-0.3.1/.gitignore000066400000000000000000000000461252251323500160110ustar00rootroot00000000000000*.gem .bundle coverage/ doc/ .yardoc/ configurate-0.3.1/.rspec000066400000000000000000000000321252251323500151310ustar00rootroot00000000000000--color --format progress configurate-0.3.1/.rubocop.yml000066400000000000000000000101611252251323500162720ustar00rootroot00000000000000# Commonly used screens these days easily fit more than 80 characters. Metrics/LineLength: Max: 120 # Too short methods lead to extraction of single-use methods, which can make # the code easier to read (by naming things), but can also clutter the class Metrics/MethodLength: Max: 20 # The guiding principle of classes is SRP, SRP can't be accurately measured by LoC Metrics/ClassLength: Max: 1500 # No space makes the method definition shorter and differentiates # from a regular assignment. Style/SpaceAroundEqualsInParameterDefault: EnforcedStyle: no_space # Single quotes being faster is hardly measurable and only affects parse time. # Enforcing double quotes reduces the times where you need to change them # when introducing an interpolation. Use single quotes only if their semantics # are needed. Style/StringLiterals: EnforcedStyle: double_quotes # We do not need to support Ruby 1.9, so this is good to use. Style/SymbolArray: Enabled: true # Most readable form. Style/AlignHash: EnforcedHashRocketStyle: table EnforcedColonStyle: table # Mixing the styles looks just silly. # REVIEW: Enable once https://github.com/bbatsov/rubocop/commit/760ce1ed2cf10beda5e163f934c03a6fb6daa38e # is released. #Style/HashSyntax: # EnforcedStyle: ruby19_no_mixed_keys # has_key? and has_value? are far more readable than key? and value? Style/DeprecatedHashMethods: Enabled: false # String#% is by far the least verbose and only object oriented variant. Style/FormatString: EnforcedStyle: percent Style/CollectionMethods: Enabled: true PreferredMethods: # inject seems more common in the community. reduce: "inject" # Either allow this style or don't. Marking it as safe with parenthesis # is silly. Let's try to live without them for now. Style/ParenthesesAroundCondition: AllowSafeAssignment: false Lint/AssignmentInCondition: AllowSafeAssignment: false # A specialized exception class will take one or more arguments and construct the message from it. # So both variants make sense. Style/RaiseArgs: Enabled: false # Fail is an alias of raise. Avoid aliases, it's more cognitive load for no gain. Style/SignalException: EnforcedStyle: only_raise # Suppressing exceptions can be perfectly fine, and be it to avoid to # explicitly type nil into the rescue since that's what you want to return, # or suppressing LoadError for optional dependencies Lint/HandleExceptions: Enabled: false Style/SpaceInsideBlockBraces: # The space here provides no real gain in readability while consuming # horizontal space in that could be used for a better parameter name. # Also {| differentiates better from a hash than { | does. SpaceBeforeBlockParameters: false # No trailing space differentiates better from the block: # foo} means hash, foo } means block. Style/SpaceInsideHashLiteralBraces: EnforcedStyle: no_space # { ... } for multi-line blocks is okay, follow Weirichs rule instead: # https://web.archive.org/web/20140221124509/http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc Style/Blocks: Enabled: false # do / end blocks should be used for side effects, # methods that run a block for side effects and have # a useful return value are rare, assign the return # value t to a local variable for those cases. Style/MethodCalledOnDoEndBlock: Enabled: true # Enforcing the names of variables? To single letter ones? Just no. Style/SingleLineBlockParams: Enabled: false # Shadowing outer local variables with block parameters is often useful # to not reinvent a new name for the same thing, it highlights the relation # between the outer variable and the parameter. The cases where it's actually # confusing are rare, and usually bad for other reasons already, for example # because the method is too long. Lint/ShadowingOuterLocalVariable: Enabled: false # Check with yard instead. Style/Documentation: Enabled: false # This is just silly. Calling the argument `other` in all cases makes no sense. Style/OpMethod: Enabled: false # There are valid cases, for example debugging Cucumber steps, # also they'll fail CI anyway Lint/Debugger: Enabled: false # Style preference Style/MethodDefParentheses: Enabled: false configurate-0.3.1/.ruby-version000066400000000000000000000000041252251323500164600ustar00rootroot000000000000002.2 configurate-0.3.1/.travis.yml000066400000000000000000000001651252251323500161340ustar00rootroot00000000000000language: ruby sudo: false cache: bundler bundler_args: "--without doc build" rvm: - 2.0 - 2.1 - 2.2 - rbx configurate-0.3.1/Changelog.md000066400000000000000000000035721252251323500162410ustar00rootroot00000000000000# 0.3.1 * Configurate::Provider::Dynamic returns true when passed the special `reset_dynamic!` call. # 0.3.0 * Add new exception: Configurate::MissingSetting to be raised and bubble up to the user if a setting wasn't found and the user requested to be informed. * Configurate::Provider::YAML got the new option raise_on_missing to raise Configurate::MissingSetting if the requested key is not in the YAML document. # 0.2.0 * Dynamic provider listens to reset_dynamic! message and forgets all settings on it. * Calls ending in ! call the providers directly. * Added SettingPath#action?, remove is_ prefix from SettingPath methods. * Add implicit converters to Proxy that call the explicit converters. # 0.1.0 * Dynamic provider resolves nested assignments # 0.0.8 * Include README.md into the gem * Skip namespace warning if there but empty * Do not overwrite dup in SettingPath * Fix tolerant loading of coveralls in the spec helper * Improve comparisions in Proxy # 0.0.7 * Only directly delegate methods returning meta-information in SettingPath * Clean output of more methods in SettingPath * Sanitize more input methods in SettingPath # 0.0.6 * Use Forwardable instead of method_missing where possible * Fix warning message on invalid namespace in YAML provider * Refactor SettingPath to correctly handle special paths in way more places * SettingPath#new now handles string paths, dropped SettingPath::from_string # 0.0.4/0.0.5 * Pass duplicates of SettingPath into the provider so that it can be modified by it. * Ensure SettingPath elements are strings # 0.0.3 * Support Ruby 2.0.0 * Prefer `public_send` over `send` * Manage setting paths through dedicated objects * Pass new SettingPath objects directly into the providers * Improve specs # 0.0.2 * Return duplicates from the environment provider so that the return value can be modified by the client. # 0.0.1 * Initial release configurate-0.3.1/Gemfile000066400000000000000000000005411252251323500153140ustar00rootroot00000000000000source "https://rubygems.org" gem "coveralls", require: false, group: :coverage group :development do gem "guard-rspec" gem "guard-yard" gem "guard-rubocop" gem "rubocop", require: false end group :doc do gem "yard", require: false gem "redcarpet", require: false end platform :rbx do gem "psych" gem "rubysl-singleton" end gemspec configurate-0.3.1/Gemfile.lock000066400000000000000000000055721252251323500162540ustar00rootroot00000000000000PATH remote: . specs: configurate (0.3.0) GEM remote: https://rubygems.org/ specs: ast (2.0.0) astrolabe (1.3.0) parser (>= 2.2.0.pre.3, < 3.0) celluloid (0.16.0) timers (~> 4.0.0) coderay (1.1.0) coveralls (0.8.1) json (~> 1.8) rest-client (>= 1.6.8, < 2) simplecov (~> 0.10.0) term-ansicolor (~> 1.3) thor (~> 0.19.1) diff-lcs (1.2.5) docile (1.1.5) domain_name (0.5.24) unf (>= 0.0.5, < 1.0.0) ffi (1.9.8) formatador (0.2.5) guard (2.12.5) formatador (>= 0.2.4) listen (~> 2.7) lumberjack (~> 1.0) nenv (~> 0.1) notiffany (~> 0.0) pry (>= 0.9.12) shellany (~> 0.0) thor (>= 0.18.1) guard-compat (1.2.1) guard-rspec (4.5.0) guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) guard-rubocop (1.2.0) guard (~> 2.0) rubocop (~> 0.20) guard-yard (2.1.4) guard (>= 1.1.0) yard (>= 0.7.0) hitimes (1.2.2) http-cookie (1.0.2) domain_name (~> 0.5) json (1.8.2) listen (2.10.0) celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) lumberjack (1.0.9) method_source (0.8.2) mime-types (2.5) nenv (0.2.0) netrc (0.10.3) notiffany (0.0.6) nenv (~> 0.1) shellany (~> 0.0) parser (2.2.2.2) ast (>= 1.1, < 3.0) powerpack (0.1.1) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) psych (2.0.13) rainbow (2.0.0) rake (10.4.2) rb-fsevent (0.9.4) rb-inotify (0.9.5) ffi (>= 0.5.0) redcarpet (3.2.3) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) rspec (3.2.0) rspec-core (~> 3.2.0) rspec-expectations (~> 3.2.0) rspec-mocks (~> 3.2.0) rspec-core (3.2.3) rspec-support (~> 3.2.0) rspec-expectations (3.2.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.2.0) rspec-mocks (3.2.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.2.0) rspec-support (3.2.2) rubocop (0.31.0) astrolabe (~> 1.3) parser (>= 2.2.2.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) ruby-progressbar (1.7.5) rubysl-singleton (2.0.0) shellany (0.0.1) simplecov (0.10.0) docile (~> 1.1.0) json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) slop (3.6.0) term-ansicolor (1.3.0) tins (~> 1.0) thor (0.19.1) timers (4.0.1) hitimes tins (1.5.1) unf (0.1.4) unf_ext unf_ext (0.0.7.1) yard (0.8.7.6) PLATFORMS ruby DEPENDENCIES configurate! coveralls guard-rspec guard-rubocop guard-yard psych rake (>= 10.0.3) redcarpet rspec (>= 3.0) rubocop rubysl-singleton yard configurate-0.3.1/Guardfile000066400000000000000000000006221252251323500156460ustar00rootroot00000000000000guard :rspec, cmd: "bundle exec rspec" do watch(/^spec\/.+_spec\.rb$/) watch(/^lib\/(.+)\.rb$/) {|m| "spec/#{m[1]}_spec.rb" } watch(%r{^lib/open_graph_reader/(.+)\.rb$}) {|m| "spec/#{m[1]}_spec.rb" } watch(/^lib\/(.+)\.rb$/) { "spec/integration" } watch("spec/spec_helper.rb") { "spec" } end guard "yard" do watch(/lib\/.+\.rb/) end guard "Rubocop" do watch(/(?:lib|spec)\/.+\.rb/) end configurate-0.3.1/LICENSE000066400000000000000000000020361252251323500150270ustar00rootroot00000000000000Copyright (c) 2012 Jonne Haß Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. configurate-0.3.1/README.md000066400000000000000000000143761252251323500153130ustar00rootroot00000000000000# Configurate - A flexible configuration system [![Gem Version](https://badge.fury.io/rb/configurate.png)](https://rubygems.org/gems/configurate) [![Build Status](https://secure.travis-ci.org/jhass/configurate.png?branch=master)](https://travis-ci.org/jhass/configurate) [![Gemnasium](https://gemnasium.com/jhass/configurate.png)](https://gemnasium.com/jhass/configurate) [![Code Climate](https://codeclimate.com/github/jhass/configurate.png)](https://codeclimate.com/github/jhass/configurate) [![Coverage Status](https://coveralls.io/repos/jhass/configurate/badge.svg?branch=master)](https://coveralls.io/r/jhass/configurate?branch=master) Configurate allows you to specify a chain of configuration providers which are queried in order until one returns a value. This allows scenarios like overriding your default settings with a user configuration file and let those be overridden by environment variables. The query interface allows to group and nest your configuration options to a practically unlimited level. Configurate supports Ruby 2.0 or later. ## Installation Just add ```ruby gem 'configurate' ``` to your `Gemfile`. ## Usage A basic loader could look like this: ```ruby require 'configurate' Config = Configurate::Settings.create do add_provider Configurate::Provider::Env add_provider Configurate::Provider::YAML, '/etc/app_settings.yml', namespace: Rails.env, required: false add_provider Configurate::Provider::YAML, 'config/default_settings.yml' end # Somewhere later if Config.remote_assets.enable? set_asset_host Config.remote_assets.host end ``` You can add custom methods working with your settings to your `Configurate::Settings` instance by calling `extend YourConfigurationMethods` inside the block passed to `#create`. Providers are called in the order they're added. You can already use the added providers to determine if further ones should be added: ```ruby require 'configurate' Config = Configurate::Settings.create do add_provider Configurate::Provider::Env add_provider Configurate::Provider::YAML, 'config/settings.yml' unless heroku? end ``` `add_provider` can be called later on the created object to add more providers to the chain. It takes a constant and parameters that should be passed to the initializer. A providers only requirement is that it responds to the `#lookup` method. `#lookup` is passed the current `SettingPath`, for example for a call to `Config.foo.bar.baz?` it gets a path with the items `'foo'`, `'bar'`, `'baz'` passed. `SettingPath` behaves like `Array` with some methods added. The provider should raise `Configurate::SettingNotFoundError` if it can't provide a value for the requested option. Any additional parameters are passed along to the provider, thus a `#lookup` method must be able to take any number of additional parameters. You're not limited to one instance of the configuration object. ## Gotchas ### False Ruby does not allow to metaprogram `false`, thus something like ```ruby puts "yep" if Config.enable_stuff ``` always outputs `yep`. The workaround is to append `.get`, or `?` to get the real value: ```ruby puts "yep" if Config.enable_stuff? ``` ### Module#=== Another thing you can't overwrite in Ruby is the `===` operator, rendering case statements useless ```ruby puts case Config.some.setting when NilClass "nil" when String "string" else "unknown" end ``` will always output `unknown`. Again use `.get` ## Shipped providers ### Configurate::Provider::Base A convenience base class changing the interface for implementers. It provides a basic `#lookup` method which just passes all parameters through to `#lookup_path`. The result of `#lookup_path` is returned, unless it's `nil` then `Configurate::SettingNotFoundError` is raised. Subclasses are expected to implement `#lookup_path`. Do not use this class directly as a provider! ### Configurate::Provider::Env This class transforms a query string into a name for a environment variable and looks up this variable then. The conversion scheme is the following: Convert to uppercase, join path with underscores. So for example `Config.foo.bar.baz` would look for a environment variable named `FOO_BAR_BAZ`. Additionally it splits comma separated values into arrays. This provider does not take any additional initialization parameters. ### Configurate::Provider::YAML This provider reads settings from a given [YAML](http://www.yaml.org) file. It converts the sections of query string to a nested value. For a given YAML file ```yaml stuff: enable: true param: "foo" nested: param: "bar" ``` the following queries would be valid: ```ruby Config.stuff.enable? # => true Config.stuff.param # => "foo" Config.stuff.nested.param # => "bar" ``` The initializer takes a path to the configuration file as mandatory first argument and the following optional parameters, as a hash: * *namespace:* Specify a alternative root. This is useful if you for example add the same file multiple times through multiple providers, with different namespaces, letting you override settings depending on the rails environment, without duplicating common settings. Defaults to none. * *required:* Whether to raise an error if the the file isn't found or, if one is given, the namespace doesn't exist in the file. ### Configurate::Provider::Dynamic A provider which stores the first additional parameter if the query string ends with an equal sign and can return it later. This is mainly useful for testing but can be useful to temporarily override stuff too. To clarify a small example: ```ruby Config.foo.bar # => nil Config.foo.bar = "baz" Config.foo.bar # => "baz" Config.reset_dynamic! Config.foo.bar # => nil ``` ## Writing a provider ...should be pretty easy. For example here is the `Configurate::Provider::Env` provider: ```ruby class Configurate::Provider::Env < Configurate::Provider::Base def lookup_path(setting_path, *args) value = ENV[setting_path.join("_").upcase] unless value.nil? value = value.dup value = value.split(",") if value.include?(",") end value end end ``` ## Documentation You can find the current documentation for the master branch [here](http://rubydoc.info/github/jhass/configurate/master/frames/index). ## License MIT, see [LICENSE](./LICENSE) configurate-0.3.1/Rakefile000066400000000000000000000001641252251323500154670ustar00rootroot00000000000000require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:rspec) task default: :rspec configurate-0.3.1/configurate.gemspec000066400000000000000000000013331252251323500176740ustar00rootroot00000000000000Gem::Specification.new do |s| s.name = "configurate" s.version = "0.3.1" s.summary = "Flexbile configuration system" s.description = "Configurate is a flexible configuration system that can "\ "read settings from multiple sources at the same time." s.authors = ["Jonne Haß"] s.email = "me@jhass.eu" s.homepage = "http://jhass.github.io/configurate" s.license = "MIT" s.files = Dir["lib/**/*.rb"] + ["README.md", "Changelog.md", "LICENSE"] s.test_files = Dir["spec/**/*.rb"] s.require_paths = ["lib"] s.required_ruby_version = ">= 1.9.2" s.add_development_dependency "rake", ">= 10.0.3" s.add_development_dependency "rspec", ">= 3.0" end configurate-0.3.1/lib/000077500000000000000000000000001252251323500145675ustar00rootroot00000000000000configurate-0.3.1/lib/configurate.rb000066400000000000000000000055621252251323500174320ustar00rootroot00000000000000require "forwardable" require "configurate/setting_path" require "configurate/lookup_chain" require "configurate/provider" require "configurate/proxy" # A flexible and extendable configuration system. # The calling logic is isolated from the lookup logic # through configuration providers, whose only requirement # is to define the +#lookup+ method and show a certain behavior on that. # The providers are asked in the order they were added until one provides # a response. This allows to even add multiple providers of the same type, # you never easier defined your default configuration parameters. # There is no shared state, you can have an unlimited amount of # independent configuration sources at the same time. # # See {Settings} for a quick start. module Configurate # This is your main entry point. Instead of lengthy explanations # let an example demonstrate its usage: # # require 'configuration_methods' # # AppSettings = Configurate::Settings.create do # add_provider Configurate::Provider::Env # add_provider Configurate::Provider::YAML, '/etc/app_settings.yml', # namespace: Rails.env, required: false # add_provider Configurate::Provider::YAML, 'config/default_settings.yml' # # extend YourConfigurationMethods # end # # AppSettings.setup_something if AppSettings.something.enable? # # Please also read the note at {Proxy}! class Settings attr_reader :lookup_chain undef_method :method # Remove possible conflicts with common setting names extend Forwardable def initialize @lookup_chain = LookupChain.new warn "Warning you called Configurate::Settings.new with a block, you really meant to call #create" if block_given? end # @!method lookup(setting) # (see {LookupChain#lookup}) # @!method add_provider(provider, *args) # (see {LookupChain#add_provider}) # @!method [](setting) # (see {LookupChain#[]}) def_delegators :@lookup_chain, :lookup, :add_provider, :[] # See description and {#lookup}, {#[]} and {#add_provider} def method_missing(method, *args, &block) Proxy.new(@lookup_chain).public_send(method, *args, &block) end # Create a new configuration object # @yield the given block will be evaluated in the context of the new object def self.create(&block) config = new config.instance_eval(&block) if block_given? config end end # This is supposed to be raised by providers if the requested setting # does not exist, (remember, nil is a valid value and thus rarely a sufficient check) # and this should be communicated to the end user. class MissingSetting < RuntimeError; end # This is supposed to be raised by providers if the requested setting # cannot be found and the next provider in the chain should be tried. class SettingNotFoundError < RuntimeError; end end configurate-0.3.1/lib/configurate/000077500000000000000000000000001252251323500170755ustar00rootroot00000000000000configurate-0.3.1/lib/configurate/lookup_chain.rb000066400000000000000000000036171252251323500221040ustar00rootroot00000000000000module Configurate # This object builds a chain of configuration providers to try to find # the value of a setting. class LookupChain def initialize @provider = [] end # Adds a provider to the chain. Providers are tried in the order # they are added, so the order is important. # # @param provider [#lookup] # @param *args the arguments passed to the providers constructor # @raise [ArgumentError] if an invalid provider is given # @return [void] def add_provider(provider, *args) unless provider.respond_to?(:instance_methods) && provider.instance_methods.include?(:lookup) raise ArgumentError, "the given provider does not respond to lookup" end @provider << provider.new(*args) end # Tries all providers in the order they were added to provide a response # for setting. # # @param setting [SettingPath,String] nested settings as strings should # be separated by a dot # @param ... further args passed to the provider # @return [Array,Hash,String,Boolean,nil] whatever the responding # provider provides is casted to a {String}, except for some special values def lookup(setting, *args) setting = SettingPath.new setting if setting.is_a? String @provider.each do |provider| begin return special_value_or_string(provider.lookup(setting.clone, *args)) rescue SettingNotFoundError; end end nil end alias_method :[], :lookup private def special_value_or_string(value) case value when TrueClass, FalseClass, NilClass, Array, Hash value else if value.respond_to?(:to_s) case value.to_s.strip when "true" then true when "false" then false when "", "nil" then nil else value.to_s end else value end end end end end configurate-0.3.1/lib/configurate/provider.rb000066400000000000000000000022071252251323500212550ustar00rootroot00000000000000module Configurate module Provider # This provides a basic {#lookup} method for other providers to build # upon. Childs are expected to define +lookup_path(path, *args)+. # The method should return nil if the setting # wasn't found and {#lookup} will raise an {SettingNotFoundError} in that # case. class Base def lookup(*args) result = lookup_path(*args) return result unless result.nil? raise Configurate::SettingNotFoundError, "The setting #{args.first} was not found" end end # Utility function to lookup a settings path in a hash # @param setting_path [SettingPath] # @param hash [Hash] # @yield fallback value if not found # @return [Object] def self.lookup_in_hash setting_path, hash, &fallback fallback ||= proc { nil } while hash.is_a?(Hash) && hash.has_key?(setting_path.first) && !setting_path.empty? hash = hash[setting_path.shift] end return fallback.call unless setting_path.empty? hash end end end require "configurate/provider/yaml" require "configurate/provider/env" require "configurate/provider/dynamic" configurate-0.3.1/lib/configurate/provider/000077500000000000000000000000001252251323500207275ustar00rootroot00000000000000configurate-0.3.1/lib/configurate/provider/dynamic.rb000066400000000000000000000022471252251323500227050ustar00rootroot00000000000000module Configurate module Provider # This provider knows nothing upon initialization, however if you access # a setting ending with +=+ and give one argument to that call it remembers # that setting, stripping the +=+ and will return it on the next call # without +=+. Sending +reset_dynamic!+ to it will make it forget all # settings. Also assigning nil will have the effect of it forgetting # a setting. class Dynamic < Base def lookup_path(setting_path, *args) if setting_path.to_s == "reset_dynamic!" @settings = nil return true end if setting_path.setter? && args.length > 0 *root, key = setting_path.to_a hash = root.inject(settings) {|hash, key| hash[key] } hash[key] = extract_value(args) end Provider.lookup_in_hash setting_path, settings end private def settings @settings ||= Hash.new {|hash, key| hash[key] = Hash.new(&hash.default_proc) } end def extract_value args value = args.first value = value.get if value.respond_to?(:_proxy?) && value._proxy? value end end end end configurate-0.3.1/lib/configurate/provider/env.rb000066400000000000000000000012211252251323500220400ustar00rootroot00000000000000module Configurate module Provider # This provider looks for settings in the environment. # For the setting +foo.bar_baz+ this provider will look for an # environment variable +FOO_BAR_BAZ+, joining all components of the # setting with underscores and upcasing the result. # If an value contains any commas (,) it's split at them and returned as array. class Env < Base def lookup_path(setting_path, *_args) value = ENV[setting_path.join("_").upcase] unless value.nil? value = value.dup value = value.split(",") if value.include?(",") end value end end end end configurate-0.3.1/lib/configurate/provider/yaml.rb000066400000000000000000000031401252251323500222140ustar00rootroot00000000000000require "yaml" module Configurate module Provider # This provider tries to open a YAML file and does nested lookups # in it. class YAML < Base # @param file [String] the path to the file # @param namespace [String] optionally set this as the root # @param required [Boolean] whether or not to raise an error if # the file or the namespace, if given, is not found. Defaults to +true+. # @param raise_on_missing [Boolean] whether to raise {Configurate::MissingSetting} # if a setting can't be provided. Defaults to +false+. # @raise [ArgumentError] if the namespace isn't found in the file # @raise [Errno:ENOENT] if the file isn't found def initialize file, namespace: nil, required: true, raise_on_missing: false @raise_on_missing = raise_on_missing @settings = {} @settings = ::YAML.load_file(file) unless namespace.nil? @settings = Provider.lookup_in_hash(SettingPath.new(namespace), @settings) do raise ArgumentError, "Namespace #{namespace} not found in #{file}" if required $stderr.puts "WARNING: Namespace #{namespace} not found in #{file}" nil end end rescue Errno::ENOENT => e warn "WARNING: Configuration file #{file} not found, ensure it's present" raise e if required end def lookup_path setting_path, *_ Provider.lookup_in_hash(setting_path, @settings) { raise MissingSetting.new "#{setting_path} is not a valid setting." if @raise_on_missing nil } end end end end configurate-0.3.1/lib/configurate/proxy.rb000066400000000000000000000051011252251323500206000ustar00rootroot00000000000000module Configurate # Proxy object to support nested settings # # *Cavehats*: Since this object is always true, adding a +?+ at the end # returns the value, if found, instead of the proxy object. # So instead of +if settings.foo.bar+ use +if settings.foo.bar?+ # to check for boolean values, +if settings.foo.bar.nil?+ to # check for nil values and of course you can do +if settings.foo.bar.present?+ to check for # empty values if you're in Rails. Call {#get} to actually return the value, # commonly when doing +settings.foo.bar.get || "default"+. Also don't # use this in case statements since +Module#===+ can't be fooled, again # call {#get}. # # If a setting ends with +=+ it's too called directly, just like with +?+. class Proxy < BasicObject # @param lookup_chain [#lookup] def initialize lookup_chain @lookup_chain = lookup_chain @setting_path = SettingPath.new end def ! !target end %i(!= == eql? coerce).each do |method| define_method method do |other| target.public_send method, target_or_object(other) end end { to_int: :to_i, to_hash: :to_h, to_str: :to_s, to_ary: :to_a }.each do |method, converter| define_method method do value = target return value.public_send converter if value.respond_to? converter value.public_send method end end def _proxy? true end def respond_to? method, include_private=false method == :_proxy? || target_respond_to?(method, include_private) end def send *args, &block __send__(*args, &block) end alias_method :public_send, :send def method_missing setting, *args, &block return target.public_send(setting, *args, &block) if target_respond_to? setting @setting_path << setting return target(*args) if @setting_path.question_action_or_setter? self end # Get the setting at the current path, if found. # (see LookupChain#lookup) def target *args return if @setting_path.empty? @lookup_chain.lookup @setting_path, *args end alias_method :get, :target private COMMON_KEY_NAMES = %i(key method) def target_respond_to? setting, include_private=false return false if COMMON_KEY_NAMES.include? setting value = target return false if proxy? value value.respond_to? setting, include_private end def proxy? obj obj.respond_to?(:_proxy?) && obj._proxy? end def target_or_object obj proxy?(obj) ? obj.target : obj end end end configurate-0.3.1/lib/configurate/setting_path.rb000066400000000000000000000034351252251323500221200ustar00rootroot00000000000000require "forwardable" module Configurate # Class encapsulating the concept of a path to a setting class SettingPath include Enumerable extend Forwardable def initialize path=[] path = path.split(".") if path.is_a? String @path = path end def initialize_copy original super @path = @path.clone end def_delegators :@path, :empty?, :length, :size, :hsh # Whether the current path looks like a question or setter method def question_action_or_setter? question? || action? || setter? end # Whether the current path looks like a question method def question? @path.last.to_s.end_with?("?") end # Whether the current path looks like an action method def action? @path.last.to_s.end_with?("!") end # Whether the current path looks like a setter method def setter? @path.last.to_s.end_with?("=") end def each return to_enum(:each) unless block_given? @path.each do |component| yield clean_special_characters(component) end end %i(join first last shift pop).each do |method| define_method method do |*args| clean_special_characters @path.public_send(method, *args) end end %i(<< unshift push).each do |method| define_method method do |*args| @path.public_send method, *args.map(&:to_s) end end def to_s join(".") end def ==(other) to_s == other.to_s end def inspect "" end private def clean_special_characters value value.to_s.chomp("?").chomp("=") end end end configurate-0.3.1/spec/000077500000000000000000000000001252251323500147535ustar00rootroot00000000000000configurate-0.3.1/spec/configurate/000077500000000000000000000000001252251323500172615ustar00rootroot00000000000000configurate-0.3.1/spec/configurate/lookup_chain_spec.rb000066400000000000000000000065631252251323500233050ustar00rootroot00000000000000require "spec_helper" class InvalidConfigurationProvider; end class ValidConfigurationProvider def lookup(_setting, *_args); end end describe Configurate::LookupChain do subject { described_class.new } describe "#add_provider" do it "adds a valid provider" do expect { subject.add_provider ValidConfigurationProvider }.to change { subject.instance_variable_get(:@provider).size }.by 1 end it "doesn't add an invalid provider" do expect { subject.add_provider InvalidConfigurationProvider }.to raise_error ArgumentError end it "passes extra args to the provider" do expect(ValidConfigurationProvider).to receive(:new).with(:extra) subject.add_provider ValidConfigurationProvider, :extra end end describe "#lookup" do before do subject.add_provider ValidConfigurationProvider subject.add_provider ValidConfigurationProvider @provider = subject.instance_variable_get(:@provider) end it "it tries all providers" do setting = Configurate::SettingPath.new "some.setting" allow(setting).to receive(:clone).and_return(setting) @provider.each do |provider| expect(provider).to receive(:lookup).with(setting).and_raise(Configurate::SettingNotFoundError) end subject.lookup(setting) end it "converts a string to a SettingPath" do provider = @provider.first path = double allow(path).to receive(:clone).and_return(path) expect(provider).to receive(:lookup).with(path).and_raise(Configurate::SettingNotFoundError) setting = "bar" expect(Configurate::SettingPath).to receive(:new).with(setting).and_return(path) subject.lookup(setting) end it "passes a copy of the SettingPath to the provider" do provider = @provider.first path = double("path") copy = double("copy") expect(path).to receive(:clone).at_least(:once).and_return(copy) expect(provider).to receive(:lookup).with(copy).and_raise(Configurate::SettingNotFoundError) subject.lookup(path) end it "stops if a value is found" do expect(@provider[0]).to receive(:lookup).and_return("something") expect(@provider[1]).to_not receive(:lookup) subject.lookup("bla") end it "converts numbers to strings" do allow(@provider[0]).to receive(:lookup).and_return(5) expect(subject.lookup "foo").to eq "5" end it "does not convert false to a string" do allow(@provider[0]).to receive(:lookup).and_return(false) expect(subject.lookup "enable").to be_falsey end it "converts 'true' to true" do allow(@provider[0]).to receive(:lookup).and_return("true") expect(subject.lookup "enable").to be_truthy end it "converts 'false' to false" do allow(@provider[0]).to receive(:lookup).and_return("false") expect(subject.lookup "enable").to be_falsey end it "returns the value unchanged if it can't be converted" do value = double allow(value).to receive(:respond_to?).with(:to_s).and_return(false) allow(@provider[0]).to receive(:lookup).and_return(value) expect(subject.lookup "enable").to eq value end it "returns nil if no value is found" do @provider.each {|p| allow(p).to receive(:lookup).and_raise(Configurate::SettingNotFoundError) } expect(subject.lookup "not.me").to be_nil end end end configurate-0.3.1/spec/configurate/provider/000077500000000000000000000000001252251323500211135ustar00rootroot00000000000000configurate-0.3.1/spec/configurate/provider/dynamic_spec.rb000066400000000000000000000025201252251323500240750ustar00rootroot00000000000000require "spec_helper" describe Configurate::Provider::Dynamic do subject { described_class.new } describe "#lookup_path" do it "returns nil if the setting was never set" do expect(subject.lookup_path Configurate::SettingPath.new(["not_me"])).to be_nil end it "remembers the setting if it ends with =" do subject.lookup_path Configurate::SettingPath.new(["find_me", "later="]), "there" expect(subject.lookup_path Configurate::SettingPath.new(%w(find_me later))).to eq "there" end it "calls .get on the argument if a proxy object is given" do proxy = double(respond_to: true, _proxy?: true) expect(proxy).to receive(:get) subject.lookup_path Configurate::SettingPath.new(["bla="]), proxy end it "resolves nested calls after group assignment" do subject.lookup_path Configurate::SettingPath.new(%w(find_me later=)), "a" => "b" expect(subject.lookup_path Configurate::SettingPath.new(%w(find_me later a))).to eq "b" end it "clears out all overrides on reset_dynamic!" do subject.lookup_path Configurate::SettingPath.new(["find_me", "later="]), "there" expect(subject.lookup_path Configurate::SettingPath.new(["reset_dynamic!"])).to eq true expect(subject.lookup_path Configurate::SettingPath.new(%w(find_me later))).to_not eq "there" end end end configurate-0.3.1/spec/configurate/provider/env_spec.rb000066400000000000000000000017701252251323500232470ustar00rootroot00000000000000require "spec_helper" describe Configurate::Provider::Env do subject { described_class.new } let(:existing_path) { %w(existing setting) } let(:not_existing_path) { %w(not existing path) } let(:array_path) { ["array"] } before(:all) do ENV["EXISTING_SETTING"] = "there" ENV["ARRAY"] = "foo,bar,baz" end after(:all) do ENV["EXISTING_SETTING"] = nil ENV["ARRAY"] = nil end describe "#lookup_path" do it "joins and upcases the path" do expect(ENV).to receive(:[]).with("EXISTING_SETTING") subject.lookup_path existing_path end it "returns nil if the setting isn't available" do expect(subject.lookup_path not_existing_path).to be_nil end it "makes an array out of comma separated values" do expect(subject.lookup_path array_path).to eq %w(foo bar baz) end it "returns a unfrozen string" do expect { setting = subject.lookup_path(existing_path) setting << "foo" }.to_not raise_error end end end configurate-0.3.1/spec/configurate/provider/yaml_spec.rb000066400000000000000000000055631252251323500234250ustar00rootroot00000000000000require "spec_helper" describe Configurate::Provider::YAML do let(:settings) { { "toplevel" => "bar", "some" => { "nested" => {"some" => "lala", "setting" => "foo"} } } } describe "#initialize" do it "loads the file" do file = "foobar.yml" expect(::YAML).to receive(:load_file).with(file).and_return({}) described_class.new file end it "raises if the file is not found" do allow(::YAML).to receive(:load_file).and_raise(Errno::ENOENT) expect { silence_stderr do described_class.new "foo" end }.to raise_error Errno::ENOENT end context "with a namespace" do it "looks in the file for that namespace" do namespace = "some.nested" allow(::YAML).to receive(:load_file).and_return(settings) provider = described_class.new "bla", namespace: namespace expect(provider.instance_variable_get :@settings).to eq settings["some"]["nested"] end it "raises if the namespace isn't found" do allow(::YAML).to receive(:load_file).and_return({}) expect { described_class.new "bla", namespace: "bar" }.to raise_error end it "works with an empty namespace in the file" do allow(::YAML).to receive(:load_file).and_return("foo" => {"bar" => nil}) expect { described_class.new "bla", namespace: "foo.bar" }.to_not raise_error end end context "with required set to false" do it "doesn't raise if a file isn't found" do allow(::YAML).to receive(:load_file).and_raise(Errno::ENOENT) expect { described_class.new "not_me", required: false }.not_to raise_error end it "doesn't raise if a namespace isn't found" do allow(::YAML).to receive(:load_file).and_return({}) expect { described_class.new "bla", namespace: "foo", required: false }.not_to raise_error end end end describe "#lookup_path" do before do allow(::YAML).to receive(:load_file).and_return(settings) @provider = described_class.new "dummy" end it "looks up the whole nesting" do expect(@provider.lookup_path %w(some nested some)).to eq settings["some"]["nested"]["some"] end it "returns nil if no setting is found" do expect(@provider.lookup_path ["not_me"]).to be_nil end context "with raise_on_missing set to true" do before do @provider = described_class.new "dummy", raise_on_missing: true end it "looks up the whole nesting" do expect(@provider.lookup_path %w(some nested some)).to eq settings["some"]["nested"]["some"] end it "returns nil if no setting is found" do expect { @provider.lookup_path ["not_me"] }.to raise_error Configurate::MissingSetting end end end end configurate-0.3.1/spec/configurate/provider_spec.rb000066400000000000000000000017361252251323500224610ustar00rootroot00000000000000require "spec_helper" describe Configurate::Provider::Base do describe "#lookup" do subject { described_class.new } it "calls #lookup_path" do path = Configurate::SettingPath.new(%w(foo bar)) expect(subject).to receive(:lookup_path).with(path).and_return("something") expect(subject.lookup(path)).to eq "something" end it "raises SettingNotFoundError if the #lookup_path returns nil" do allow(subject).to receive(:lookup_path).and_return(nil) expect { subject.lookup("bla") }.to raise_error Configurate::SettingNotFoundError end end describe "::lookup_in_hash" do let(:hash) { {foo: {bar: nil}} } it "returns nil if key is nil" do expect(Configurate::Provider.lookup_in_hash(%i(foo bar), hash) { :fallback }).to be_nil end it "returns fallback for a non-existent key" do expect(Configurate::Provider.lookup_in_hash(%i(foo bar baz), hash) { :fallback }).to eq :fallback end end end configurate-0.3.1/spec/configurate/proxy_spec.rb000066400000000000000000000057401252251323500220070ustar00rootroot00000000000000require "spec_helper" describe Configurate::Proxy do let(:lookup_chain) { double(lookup: "something") } let(:proxy) { described_class.new(lookup_chain) } describe "in case statements" do it "acts like the target" do pending "If anyone knows a sane way to overwrite Module#===, please tell me :P" result = case proxy when String "string" else "wrong" end expect(result).to eq "string" end end describe "#method_missing" do it "calls #target if the method ends with a ?" do expect(lookup_chain).to receive(:lookup).and_return(false) proxy.method_missing(:enable?) end it "calls #target if the method ends with a !" do expect(lookup_chain).to receive(:lookup).and_return(false) proxy.method_missing(:do_it!) end it "calls #target if the method ends with a =" do expect(lookup_chain).to receive(:lookup).and_return(false) proxy.method_missing(:url=) end end describe "delegations" do it "calls the target when negating" do target = double allow(lookup_chain).to receive(:lookup).and_return(target) expect(target).to receive(:!) proxy.something.__send__(:!) end it "enables sends even though be BasicObject" do expect(proxy).to receive(:foo) proxy.send(:foo) end end describe "#proxy" do subject { proxy._proxy? } it { should be_truthy } end describe "#target" do %i(to_s to_xml respond_to? present? != eql? each try size length count == =~ gsub blank? chop start_with? end_with?).each do |method| it "is called for accessing #{method} on the proxy" do target = double(respond_to?: true, _proxy?: false) allow(lookup_chain).to receive(:lookup).and_return(target) expect(target).to receive(method).and_return("something") proxy.something.__send__(method, double) end end described_class::COMMON_KEY_NAMES.each do |method| it "is not called for accessing #{method} on the proxy" do target = double expect(lookup_chain).to_not receive(:lookup) expect(target).to_not receive(method) proxy.something.__send__(method, double) end end it "returns nil if no setting is given" do expect(proxy.target).to be_nil end it "converts to a string" do allow(lookup_chain).to receive(:lookup).and_return("bar") expect("foo#{proxy.something}").to eq "foobar" end it "converts to a number" do allow(lookup_chain).to receive(:lookup).and_return(1) expect(2 + proxy.something).to eq 3 end it "converts to an array" do allow(lookup_chain).to receive(:lookup).and_return([1, 2]) expect(%i(a b).zip(proxy.something)).to eq [[:a, 1], [:b, 2]] end it "converts to a hash" do allow(lookup_chain).to receive(:lookup).and_return(a: :b) expect({c: :d}.merge(proxy.something)).to eq a: :b, c: :d end end end configurate-0.3.1/spec/configurate/setting_path_spec.rb000066400000000000000000000064561252251323500233240ustar00rootroot00000000000000require "spec_helper" describe Configurate::SettingPath do let(:normal_path) { described_class.new([:foo]) } let(:question_path) { described_class.new([:foo?]) } let(:action_path) { described_class.new([:foo!]) } let(:setter_path) { described_class.new([:foo=]) } let(:long_path) { described_class.new(["foo", "bar?"]) } describe "#initialize" do context "with a string" do it "creates a path" do expect(described_class.new long_path.to_s).to eq long_path end end end describe "#question?" do context "with a question signature as setting" do subject { question_path.question? } it { should be_truthy } end context "with a normal path as setting" do subject { normal_path.question? } it { should be_falsey } end end describe "#action?" do context "with a action signature as setting" do subject { action_path.action? } it { should be_truthy } end context "with a normal path as setting" do subject { normal_path.action? } it { should be_falsey } end end describe "#setter?" do context "with a setter signature as setting" do subject { setter_path.setter? } it { should be_truthy } end context "with a normal path as setting" do subject { normal_path.setter? } it { should be_falsey } end end describe "#initialize_copy" do it "modifying a copy leaves the original unchanged" do original = described_class.new %w(foo bar) copy = original.clone copy << "baz" expect(copy).to include "baz" expect(original).not_to include "baz" end end describe "#question_action_or_setter?" do context "with a question signature as setting" do subject { question_path.question_action_or_setter? } it { should be_truthy } end context "with an action signature as setting" do subject { action_path.question_action_or_setter? } it { should be_truthy } end context "with a setter signature as setting" do subject { setter_path.question_action_or_setter? } it { should be_truthy } end context "with a normal path as setting" do subject { normal_path.question_action_or_setter? } it { should be_falsey } end end describe "#each" do it "should strip special characters" do expect(long_path.all? {|c| c.include? "?" }).to be_falsey end end %i(join first last shift pop).each do |method| describe "##{method}" do subject { question_path.public_send method } it { should_not include "?" } end end %i(<< unshift push).each do |method| describe "##{method}" do it "converts the argument to a string" do arg = double expect(arg).to receive(:to_s).and_return("bar") described_class.new.public_send method, arg end end end describe "#to_s" do let(:path) { "example.path" } subject { described_class.new(path.split(".")).to_s } it { should == path } context "with a question signature as setting" do subject { described_class.new("#{path}?".split(".")).to_s } it { should == path } end end describe "#inspect" do it "includes the dotted path" do path = described_class.new(%i(foo bar)) expect(path.inspect).to include "foo.bar" end end end configurate-0.3.1/spec/configurate_spec.rb000066400000000000000000000012461252251323500206230ustar00rootroot00000000000000require "spec_helper" describe Configurate::Settings do describe "#method_missing" do subject { described_class.create } it "delegates the call to a new proxy object" do proxy = double expect(Configurate::Proxy).to receive(:new).and_return(proxy) expect(proxy).to receive(:method_missing).with(:some_setting).and_return("foo") subject.some_setting end end %i(lookup add_provider []).each do |method| describe "#{method}" do subject { described_class.create } it "delegates the call to #lookup_chain" do expect(subject.lookup_chain).to receive(method) subject.send(method) end end end end configurate-0.3.1/spec/spec_helper.rb000066400000000000000000000016401252251323500175720ustar00rootroot00000000000000# This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # Require this file using `require "spec_helper"` to ensure that it is only # loaded once. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration begin require "coveralls" Coveralls.wear! rescue LoadError; end require "configurate" def silence_stderr $stderr = StringIO.new yield $stderr = STDERR end RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = "random" config.expect_with :rspec do |expect_config| expect_config.syntax = :expect end end