pax_global_header00006660000000000000000000000064137304753470014527gustar00rootroot0000000000000052 comment=ceca1479c1199b527b6acea819bba47f8ab520f4 configurate-0.5.0/000077500000000000000000000000001373047534700140375ustar00rootroot00000000000000configurate-0.5.0/.gitignore000066400000000000000000000001011373047534700160170ustar00rootroot00000000000000*.gem .bundle coverage/ doc/ .yardoc/ vendor/bundle Gemfile.lock configurate-0.5.0/.rspec000066400000000000000000000000321373047534700151470ustar00rootroot00000000000000--color --format progress configurate-0.5.0/.rubocop.yml000066400000000000000000000107121373047534700163120ustar00rootroot00000000000000AllCops: NewCops: enable # Commonly used screens these days easily fit more than 80 characters. Layout/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. Layout/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. Layout/HashAlignment: 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/PreferredHashMethods: 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/SuppressedException: Enabled: false Layout/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. Layout/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/BlockDelimiters: 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. Naming/BinaryOperatorParameterName: Enabled: false # Yeah, no Metrics/BlockLength: Enabled: false # alias_method is a lot more flexible and predictable Style/Alias: EnforcedStyle: prefer_alias_method # There are valid cases, for example debugging Cucumber steps, # also they'll fail CI anyway Lint/Debugger: Enabled: false # Rubocop supports >= 2.4 but we have no reason to drop support for 2.0 - 2.3 Gemspec/RequiredRubyVersion: Enabled: false # Style preference Style/MethodDefParentheses: Enabled: false configurate-0.5.0/.travis.yml000066400000000000000000000002011373047534700161410ustar00rootroot00000000000000language: ruby cache: bundler bundler_args: "--without doc build" rvm: - 2.4 - 2.5 - 2.6 - 2.7 - jruby - truffleruby configurate-0.5.0/Changelog.md000066400000000000000000000043451373047534700162560ustar00rootroot00000000000000# 0.5.0 * Support and prefer `toml-rb` over `tomlrb` in the shipped TOML provider. * Drop explicit support for Ruby < 2.4 # 0.4.0 * `Configurate::Proxy` returns its own singleton class if the target does not support creating one. * Extract `Configurate::Provider::StringHash` as a new base class for hash based providers. * Add `Configurate::Provider::TOML`. # 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.5.0/Gemfile000066400000000000000000000005031373047534700153300ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" gem "coveralls", require: false, group: :coverage group :development do gem "guard-rspec" gem "guard-rubocop" gem "guard-yard" gem "rubocop", require: false end group :doc do gem "redcarpet", require: false gem "yard", require: false end gemspec configurate-0.5.0/Guardfile000066400000000000000000000005741373047534700156720ustar00rootroot00000000000000# frozen_string_literal: true guard :rspec, cmd: "bundle exec rspec" do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) {|m| "spec/#{m[1]}_spec.rb" } watch(%r{^lib/configurate/(.+)\.rb$}) {|m| "spec/#{m[1]}_spec.rb" } watch("spec/spec_helper.rb") { "spec" } end guard "yard" do watch(%r{lib/.+\.rb}) end guard "Rubocop" do watch(%{(?:lib|spec)/.+\.rb}) end configurate-0.5.0/LICENSE000066400000000000000000000020361373047534700150450ustar00rootroot00000000000000Copyright (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.5.0/README.md000066400000000000000000000212661373047534700153250ustar00rootroot00000000000000# Configurate - A flexible configuration system [![Gem Version](https://badge.fury.io/rb/configurate.svg)](https://badge.fury.io/rb/configurate) [![Build Status](https://travis-ci.org/jhass/configurate.svg?branch=master)](https://travis-ci.org/jhass/configurate) [![Code Climate](https://codeclimate.com/github/jhass/configurate.svg)](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::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::TOML This provider reads settings from a given [TOML](https://github.com/toml-lang/toml) file. It converts the sections of query string to a nested value. For a given TOML file ```toml [stuff] enable = true param = "foo" [stuff.nested] param = "bar" ``` the following queries would be valid: ```ruby Config.stuff.enable? # => true Config.stuff.param # => "foo" Config.stuff.nested.param # => "bar" ``` This provider depends on the [toml-rb](https://github.com/emancu/toml-rb) or the [tomlrb](https://github.com/fbernier/tomlrb) gem. This is why it is not loaded by default and needs an explicit `require 'configurate/provider/toml'` to be available. If both are available, toml-rb is preferred. The initializer takes a path to the configuration file a the mandatory first argument and the following optional parameters: * *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::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 a the mandatory first argument and the following optional parameters: * *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::StringHash A provider taking a (nested) `Hash` where all keys are strings. The query string is then looked up in this hash. So for a given `Hash` ```ruby { "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 the hash as the mandatory first argument and the following optional parameters: * *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 namespace doesn't exist in the hash. * *source:* A hint text about the origin of the configuration data to be used in error messages. As you may have noticed by now, `Configurate::Provider::YAML` and `Configurate::Provider::TOML` are merely convenience subclasses of this provider, loading the file for you. ### 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 ``` ### 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! ## 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 ``` `Configurate::Provider::StringHash` should also serve as a useful baseclass for most providers. ## 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.5.0/Rakefile000066400000000000000000000002231373047534700155010ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:rspec) task default: :rspec configurate-0.5.0/configurate.gemspec000066400000000000000000000014571373047534700177210ustar00rootroot00000000000000# frozen_string_literal: true Gem::Specification.new do |s| s.name = "configurate" s.version = "0.5.0" 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 = ">= 2.0.0" s.add_development_dependency "rake", ">= 10.0.3" s.add_development_dependency "rspec", ">= 3.0" s.add_development_dependency "toml-rb", ">= 2.0.1" end configurate-0.5.0/lib/000077500000000000000000000000001373047534700146055ustar00rootroot00000000000000configurate-0.5.0/lib/configurate.rb000066400000000000000000000061021373047534700174370ustar00rootroot00000000000000# frozen_string_literal: true require "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, :[] # rubocop:disable Style/MethodMissingSuper we handle all calls # rubocop:disable Style/MissingRespondToMissing we override respond_to? instead # See description and {#lookup}, {#[]} and {#add_provider} def method_missing(method, *args, &block) Proxy.new(@lookup_chain).public_send(method, *args, &block) end # rubocop:enable all # 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.5.0/lib/configurate/000077500000000000000000000000001373047534700171135ustar00rootroot00000000000000configurate-0.5.0/lib/configurate/lookup_chain.rb000066400000000000000000000036561373047534700221250ustar00rootroot00000000000000# frozen_string_literal: true module 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.5.0/lib/configurate/provider.rb000066400000000000000000000023231373047534700212720ustar00rootroot00000000000000# frozen_string_literal: true module 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/string_hash" require "configurate/provider/yaml" require "configurate/provider/env" require "configurate/provider/dynamic" configurate-0.5.0/lib/configurate/provider/000077500000000000000000000000001373047534700207455ustar00rootroot00000000000000configurate-0.5.0/lib/configurate/provider/dynamic.rb000066400000000000000000000023031373047534700227140ustar00rootroot00000000000000# frozen_string_literal: true module 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.empty? *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.5.0/lib/configurate/provider/env.rb000066400000000000000000000012601373047534700220610ustar00rootroot00000000000000# frozen_string_literal: true module 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.5.0/lib/configurate/provider/string_hash.rb000066400000000000000000000034151373047534700236060ustar00rootroot00000000000000# frozen_string_literal: true module Configurate module Provider # This provider takes a nested string keyed hash and does nested lookups in it. class StringHash < Base # @param hash [::Hash] the string keyed hash to provide values from # @param namespace [String] optionally set this as the root # @param required [Boolean] whether or not to raise an error if # 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+. # @param source [String] optional hint of what's the source of this configuration. Used in error messages. # @raise [ArgumentError] if the namespace isn't found in the hash or the given object is not a hash def initialize hash, namespace: nil, required: true, raise_on_missing: false, source: nil raise ArgumentError, "Please provide a hash" unless hash.is_a?(Hash) @required = required @raise_on_missing = raise_on_missing @source = source @settings = root_from hash, namespace 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 private def root_from hash, namespace return hash if namespace.nil? Provider.lookup_in_hash(SettingPath.new(namespace), hash) do raise ArgumentError, "Namespace #{namespace} not found #{"in #{@source}" if @source}" if @required warn "WARNING: Namespace #{namespace} not found #{"in #{@source}" if @source}" nil end end end end end configurate-0.5.0/lib/configurate/provider/toml.rb000066400000000000000000000025131373047534700222460ustar00rootroot00000000000000# frozen_string_literal: true require "configurate" module Configurate module Provider # This provider tries to open a TOML file and does nested lookups # in it. class TOML < StringHash begin require "toml-rb" PARSER = TomlRB rescue LoadError => e require "tomlrb" PARSER = Tomlrb end # @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 super(PARSER.load_file(file), namespace: namespace, required: required, raise_on_missing: raise_on_missing, source: file ) rescue Errno::ENOENT => e warn "WARNING: Configuration file #{file} not found, ensure it's present" raise e if required end end end end configurate-0.5.0/lib/configurate/provider/yaml.rb000066400000000000000000000022561373047534700222410ustar00rootroot00000000000000# frozen_string_literal: true require "yaml" module Configurate module Provider # This provider tries to open a YAML file and does nested lookups # in it. class YAML < StringHash # @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 super(::YAML.load_file(file), namespace: namespace, required: required, raise_on_missing: raise_on_missing, source: file ) rescue Errno::ENOENT => e warn "WARNING: Configuration file #{file} not found, ensure it's present" raise e if required end end end end configurate-0.5.0/lib/configurate/proxy.rb000066400000000000000000000056311373047534700206260ustar00rootroot00000000000000# frozen_string_literal: true module 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 singleton_class target.singleton_class rescue ::TypeError class << self self end end # rubocop:disable Style/MethodMissingSuper we handle all calls # rubocop:disable Style/MissingRespondToMissing we override respond_to? instead 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 # rubocop:enable all # 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].freeze 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.5.0/lib/configurate/setting_path.rb000066400000000000000000000034751373047534700221420ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/000077500000000000000000000000001373047534700147715ustar00rootroot00000000000000configurate-0.5.0/spec/configurate/000077500000000000000000000000001373047534700172775ustar00rootroot00000000000000configurate-0.5.0/spec/configurate/lookup_chain_spec.rb000066400000000000000000000066301373047534700233160ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/configurate/provider/000077500000000000000000000000001373047534700211315ustar00rootroot00000000000000configurate-0.5.0/spec/configurate/provider/dynamic_spec.rb000066400000000000000000000025641373047534700241230ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/configurate/provider/env_spec.rb000066400000000000000000000020311373047534700232540ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/configurate/provider/string_hash_spec.rb000066400000000000000000000042541373047534700250060ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe Configurate::Provider::StringHash do let(:settings) { { "toplevel" => "bar", "some" => { "nested" => {"some" => "lala", "setting" => "foo"} } } } describe "#initialize" do it "raises if the argument is not hash" do expect { described_class.new "foo" }.to raise_error ArgumentError end context "with a namespace" do it "looks in the hash for that namespace" do namespace = "some.nested" provider = described_class.new settings, namespace: namespace expect(provider.instance_variable_get(:@settings)).to eq settings["some"]["nested"] end it "raises if the namespace isn't found" do expect { described_class.new({}, namespace: "bar") }.to raise_error ArgumentError end it "works with an empty namespace in the file" do expect { described_class.new({"foo" => {"bar" => nil}}, namespace: "foo.bar") }.to_not raise_error end end context "with required set to false" do it "doesn't raise if a namespace isn't found" do expect { silence_stderr do described_class.new({}, namespace: "foo", required: false) end }.not_to raise_error end end end describe "#lookup_path" do before do @provider = described_class.new settings 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 settings, 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.5.0/spec/configurate/provider/toml_spec.rb000066400000000000000000000062511373047534700234470ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require "configurate/provider/toml" describe Configurate::Provider::TOML do PARSER = Configurate::Provider::TOML::PARSER let(:settings) { { "toplevel" => "bar", "some" => { "nested" => {"some" => "lala", "setting" => "foo"} } } } describe "#initialize" do it "loads the file" do file = "foobar.toml" expect(PARSER).to receive(:load_file).with(file).and_return({}) described_class.new file end it "raises if the file is not found" do allow(PARSER).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(PARSER).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(PARSER).to receive(:load_file).and_return({}) expect { silence_stderr do described_class.new "bla", namespace: "bar" end }.to raise_error ArgumentError end it "works with an empty namespace in the file" do allow(PARSER).to receive(:load_file).and_return("foo" => {"bar" => nil}) expect { silence_stderr do described_class.new "bla", namespace: "foo.bar" end }.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(PARSER).to receive(:load_file).and_raise(Errno::ENOENT) expect { silence_stderr do described_class.new "not_me", required: false end }.not_to raise_error end it "doesn't raise if a namespace isn't found" do allow(PARSER).to receive(:load_file).and_return({}) expect { silence_stderr do described_class.new "bla", namespace: "foo", required: false end }.not_to raise_error end end end describe "#lookup_path" do before do allow(PARSER).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.5.0/spec/configurate/provider/yaml_spec.rb000066400000000000000000000057741373047534700234470ustar00rootroot00000000000000# frozen_string_literal: true require "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 ArgumentError 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 { silence_stderr do described_class.new "not_me", required: false end }.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 { silence_stderr do described_class.new "bla", namespace: "foo", required: false end }.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.5.0/spec/configurate/provider_spec.rb000066400000000000000000000017741373047534700225010ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/configurate/proxy_spec.rb000066400000000000000000000057771373047534700220370ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/configurate/setting_path_spec.rb000066400000000000000000000065161373047534700233370ustar00rootroot00000000000000# frozen_string_literal: true require "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.5.0/spec/configurate_spec.rb000066400000000000000000000013051373047534700206350ustar00rootroot00000000000000# frozen_string_literal: true require "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.to_s 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.5.0/spec/spec_helper.rb000066400000000000000000000017051373047534700176120ustar00rootroot00000000000000# frozen_string_literal: true # 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 ensure $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