diff --git a/Changelog.md b/Changelog.md index e97ba60c2..5b33cde11 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ * Removed unused stuff [#3714](https://github.com/diaspora/diaspora/pull/3714), [#3754](https://github.com/diaspora/diaspora/pull/3754) * Last post link isn't displayed anymore if there are no visible posts [#3750](https://github.com/diaspora/diaspora/issues/3750) * Ported tag followings to backbone [#3713](https://github.com/diaspora/diaspora/pull/3713) +* Extracted configuration system to a gem. ## Bug Fixes diff --git a/Gemfile b/Gemfile index 18d4d35e8..0c5ba139d 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,9 @@ gem 'unicorn', '4.4.0', :require => false gem 'rails_autolink', '1.0.9' +# configuration +gem 'configurate', '0.0.1' + # cross-origin resource sharing gem 'rack-cors', '0.2.7', :require => 'rack/cors' diff --git a/Gemfile.lock b/Gemfile.lock index bb4a98390..397670596 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,6 +90,7 @@ GEM execjs coffee-script-source (1.4.0) columnize (0.3.6) + configurate (0.0.1) crack (0.3.1) cucumber (1.2.1) builder (>= 2.1.2) @@ -430,6 +431,7 @@ DEPENDENCIES capybara (= 1.1.3) carrierwave (= 0.7.1) client_side_validations (= 3.2.1) + configurate (= 0.0.1) cucumber-rails (= 1.3.0) database_cleaner (= 0.9.1) debugger (= 1.2.1) diff --git a/config/load_config.rb b/config/load_config.rb index 91af27e07..88f8d4c4d 100644 --- a/config/load_config.rb +++ b/config/load_config.rb @@ -1,5 +1,4 @@ -require Rails.root.join('lib', 'configuration') -require Rails.root.join('lib', 'configuration', 'methods') +require Rails.root.join('lib', 'configuration_methods') config_dir = Rails.root.join("config") @@ -9,9 +8,9 @@ if File.exists?(config_dir.join("application.yml")) end -AppConfig ||= Configuration::Settings.create do - add_provider Configuration::Provider::Dynamic - add_provider Configuration::Provider::Env +AppConfig ||= Configurate::Settings.create do + add_provider Configurate::Provider::Dynamic + add_provider Configurate::Provider::Env unless heroku? || Rails.env == "test" || File.exists?(config_dir.join("diaspora.yml")) $stderr.puts "FATAL: Configuration not found. Copy over diaspora.yml.example" @@ -19,16 +18,16 @@ AppConfig ||= Configuration::Settings.create do Process.exit(1) end - add_provider Configuration::Provider::YAML, + add_provider Configurate::Provider::YAML, config_dir.join("diaspora.yml"), namespace: Rails.env, required: false - add_provider Configuration::Provider::YAML, + add_provider Configurate::Provider::YAML, config_dir.join("diaspora.yml"), namespace: "configuration", required: false - add_provider Configuration::Provider::YAML, + add_provider Configurate::Provider::YAML, config_dir.join("defaults.yml"), namespace: Rails.env - add_provider Configuration::Provider::YAML, + add_provider Configurate::Provider::YAML, config_dir.join("defaults.yml"), namespace: "defaults" diff --git a/lib/configuration.rb b/lib/configuration.rb deleted file mode 100644 index c691b4940..000000000 --- a/lib/configuration.rb +++ /dev/null @@ -1,69 +0,0 @@ - -require Rails.root.join('lib', 'configuration', 'lookup_chain') -require Rails.root.join('lib', 'configuration', 'provider') -require Rails.root.join('lib', 'configuration', 'proxy') - - -# A flexible and extendable configuration system. -# The calling logic is isolated from the lookup logic -# through configuration providers, which 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 are no class methods used, you can have an unlimited amount of -# independent configuration sources at the same time. -# -# See {Settings} for a quick start. -module Configuration - # This is your main entry point. Instead of lengthy explanations - # let an example demonstrate its usage: - # - # require Rails.root.join('lib', 'configuration') - # - # AppSettings = Configuration::Settings.create do - # add_provider Configuration::Provider::Env - # add_provider Configuration::Provider::YAML, '/etc/app_settings.yml', - # namespace: Rails.env, required: false - # add_provider Configuration::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 - - # @!method lookup(setting) - # (see LookupChain#lookup) - # @!method add_provider(provider, *args) - # (see LookupChain#add_provider) - # @!method [](setting) - # (see LookupChain#[]) - def method_missing(method, *args, &block) - return @lookup_chain.send(method, *args, &block) if [:lookup, :add_provider, :[]].include?(method) - - Proxy.new(@lookup_chain).send(method, *args, &block) - end - - def initialize - @lookup_chain = LookupChain.new - $stderr.puts "Warning you called Configuration::Settings.new with a block, you really meant to call #create" if block_given? - 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 = self.new - config.instance_eval(&block) if block_given? - config - end - end - - class SettingNotFoundError < RuntimeError; end -end diff --git a/lib/configuration/lookup_chain.rb b/lib/configuration/lookup_chain.rb deleted file mode 100644 index 803a86610..000000000 --- a/lib/configuration/lookup_chain.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Configuration - # This object builds a chain of configuration providers to try to find - # a setting. - class LookupChain - def initialize - @provider = [] - end - - # Add 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.instance_method_names.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 [#to_s] settings should be underscore_case, - # nested settings should be separated by a dot - # @param *args further args passed to the provider - # @return [Array,String,Boolean,nil] whatever the provider provides - # is casted to a {String}, except for some special values - def lookup(setting, *args) - setting = setting.to_s - - @provider.each do |provider| - begin - return special_value_or_string(provider.lookup(setting, *args)) - rescue SettingNotFoundError; end - end - - nil - end - alias_method :[], :lookup - - private - - def special_value_or_string(value) - if [TrueClass, FalseClass, NilClass, Array, Hash].include?(value.class) - return value - elsif value.is_a?(String) - return case value.strip - when "true" then true - when "false" then false - when "", "nil" then nil - else value - end - elsif value.respond_to?(:to_s) - return value.to_s - else - return value - end - end - end -end diff --git a/lib/configuration/provider.rb b/lib/configuration/provider.rb deleted file mode 100644 index cb8bc82a1..000000000 --- a/lib/configuration/provider.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Configuration::Provider - # This provides a basic {#lookup} method for other providers to build - # upon. Childs are expected to define +lookup_path(path, *args)+ where - # +path+ will be passed an array of settings generated by splitting the - # called setting at the dots. The method should return nil if the setting - # wasn't found and {#lookup} will raise an {SettingNotFoundError} in that - # case. - class Base - def lookup(setting, *args) - result = lookup_path(setting.split("."), *args) - return result unless result.nil? - raise Configuration::SettingNotFoundError, "The setting #{setting} was not found" - end - end -end - -require Rails.root.join("lib", "configuration", "provider", "yaml") -require Rails.root.join("lib", "configuration", "provider", "env") -require Rails.root.join("lib", "configuration", "provider", "dynamic") diff --git a/lib/configuration/provider/dynamic.rb b/lib/configuration/provider/dynamic.rb deleted file mode 100644 index 245f76a9a..000000000 --- a/lib/configuration/provider/dynamic.rb +++ /dev/null @@ -1,24 +0,0 @@ -module Configuration::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 +=+. - class Dynamic < Base - def initialize - @settings = {} - end - - def lookup_path(settings_path, *args) - key = settings_path.join(".") - - if key.end_with?("=") && args.length > 0 - key = key.chomp("=") - value = args.first - value = value.get if value.respond_to?(:_proxy?) && value._proxy? - @settings[key] = value - end - - @settings[key] - end - end -end diff --git a/lib/configuration/provider/env.rb b/lib/configuration/provider/env.rb deleted file mode 100644 index ea17f694e..000000000 --- a/lib/configuration/provider/env.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Configuration::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+, replacing all dots in the setting - # and upcasing the result. If an value contains +,+ it's split at them - # and returned as array. - class Env < Base - def lookup_path(settings_path, *args) - value = ENV[settings_path.join("_").upcase] - value = value.split(",") if value && value.include?(",") - value - end - end -end diff --git a/lib/configuration/provider/yaml.rb b/lib/configuration/provider/yaml.rb deleted file mode 100644 index 635e179d6..000000000 --- a/lib/configuration/provider/yaml.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'yaml' - -module Configuration::Provider - # This provider tries to open a YAML file and does in nested lookups - # in it. - class YAML < Base - # @param file [String] the path to the file - # @param opts [Hash] - # @option opts [String] :namespace optionally set this as the root - # @option opts [Boolean] :required wheter or not to raise an error if - # the file or the namespace, if given, is not found. Defaults to +true+. - # @raise [ArgumentError] if the namespace isn't found in the file - # @raise [Errno:ENOENT] if the file isn't found - def initialize(file, opts = {}) - @settings = {} - required = opts.has_key?(:required) ? opts.delete(:required) : true - - @settings = ::YAML.load_file(file) - - namespace = opts.delete(:namespace) - unless namespace.nil? - actual_settings = lookup_in_hash(namespace.split("."), @settings) - unless actual_settings.nil? - @settings = actual_settings - else - raise ArgumentError, "Namespace #{namespace} not found in #{file}" if required - end - end - rescue Errno::ENOENT => e - $stderr.puts "WARNING: configuration file #{file} not found, ensure it's present" - raise e if required - end - - - def lookup_path(settings_path, *args) - lookup_in_hash(settings_path, @settings) - end - - private - - def lookup_in_hash(setting_path, hash) - setting = setting_path.shift - if hash.has_key?(setting) - if setting_path.length > 0 && hash[setting].is_a?(Hash) - return lookup_in_hash(setting_path, hash[setting]) if setting.length > 1 - else - return hash[setting] - end - end - end - end -end diff --git a/lib/configuration/proxy.rb b/lib/configuration/proxy.rb deleted file mode 100644 index 4acbbcc00..000000000 --- a/lib/configuration/proxy.rb +++ /dev/null @@ -1,76 +0,0 @@ -module Configuration - # Proxy object to support nested settings - # Cavehat: Since this 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, +if settings.foo.bar.present?+ to check for - # empty values if you're in Rails and call {#get} to actually return the value, - # commonly when doing +settings.foo.bar.get || 'default'+. If a setting - # ends with +=+ is too called directly, just like with +?+. - class Proxy < BasicObject - COMMON_KEY_NAMES = [:key, :method] - - # @param lookup_chain [#lookup] - def initialize(lookup_chain) - @lookup_chain = lookup_chain - @setting = "" - end - - def ! - !self.get - end - - def !=(other) - self.get != other - end - - def ==(other) - self.get == other - end - - def _proxy? - true - end - - def respond_to?(method, include_private=false) - method == :_proxy? || self.get.respond_to?(method, include_private) - end - - def send(*args, &block) - self.__send__(*args, &block) - end - - def method_missing(setting, *args, &block) - unless COMMON_KEY_NAMES.include? setting - target = self.get - if !(target.respond_to?(:_proxy?) && target._proxy?) && target.respond_to?(setting) - return target.send(setting, *args, &block) - end - end - - setting = setting.to_s - - self.append_setting(setting) - - return self.get(*args) if setting.end_with?("?") || setting.end_with?("=") - - self - end - - # Get the setting at the current path, if found. - # (see LookupChain#lookup) - def get(*args) - setting = @setting[1..-1] - return unless setting - val = @lookup_chain.lookup(setting.chomp("?"), *args) - val - end - - protected - def append_setting(setting) - @setting << "." - @setting << setting - end - end -end diff --git a/lib/configuration/methods.rb b/lib/configuration_methods.rb similarity index 100% rename from lib/configuration/methods.rb rename to lib/configuration_methods.rb diff --git a/spec/lib/configuration/lookup_chain_spec.rb b/spec/lib/configuration/lookup_chain_spec.rb deleted file mode 100644 index 272106dc3..000000000 --- a/spec/lib/configuration/lookup_chain_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'spec_helper' - -class InvalidConfigurationProvider; end -class ValidConfigurationProvider - def lookup(setting, *args); end -end - -describe Configuration::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 - ValidConfigurationProvider.should_receive(:new).with(:extra) - subject.add_provider ValidConfigurationProvider, :extra - end - end - - describe "#lookup" do - before(:all) do - subject.add_provider ValidConfigurationProvider - subject.add_provider ValidConfigurationProvider - @provider = subject.instance_variable_get(:@provider) - end - - it "it tries all providers" do - setting = "some.setting" - @provider.each do |provider| - provider.should_receive(:lookup).with(setting).and_raise(Configuration::SettingNotFoundError) - end - - subject.lookup(setting) - end - - it "stops if a value is found" do - @provider[0].should_receive(:lookup).and_return("something") - @provider[1].should_not_receive(:lookup) - subject.lookup("bla") - end - - it "converts numbers to strings" do - @provider[0].stub(:lookup).and_return(5) - subject.lookup("foo").should == "5" - end - - it "does not convert false to a string" do - @provider[0].stub(:lookup).and_return(false) - subject.lookup("enable").should be_false - end - - it "returns nil if no value is found" do - @provider.each { |p| p.stub(:lookup).and_raise(Configuration::SettingNotFoundError) } - subject.lookup("not.me").should be_nil - end - end -end diff --git a/spec/lib/configuration/provider/dynamic_spec.rb b/spec/lib/configuration/provider/dynamic_spec.rb deleted file mode 100644 index 937dd00e2..000000000 --- a/spec/lib/configuration/provider/dynamic_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'spec_helper' - -describe Configuration::Provider::Dynamic do - subject { described_class.new } - describe "#lookup_path" do - it "returns nil if the setting was never set" do - subject.lookup_path(["not_me"]).should be_nil - end - - it "remembers the setting if it ends with =" do - subject.lookup_path(["find_me", "later="], "there") - subject.lookup_path(["find_me", "later"]).should == "there" - end - - it "calls .get on the argument if a proxy object is given" do - proxy = mock - proxy.stub(:respond_to?).and_return(true) - proxy.stub(:_proxy?).and_return(true) - proxy.should_receive(:get) - subject.lookup_path(["bla="], proxy) - end - end -end diff --git a/spec/lib/configuration/provider/env_spec.rb b/spec/lib/configuration/provider/env_spec.rb deleted file mode 100644 index 0bc5eaa56..000000000 --- a/spec/lib/configuration/provider/env_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -describe Configuration::Provider::Env do - subject { described_class.new } - let(:existing_path) { ['existing', 'setting']} - let(:not_existing_path) { ['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 - ENV.should_receive(:[]).with("EXISTING_SETTING") - subject.lookup_path(existing_path) - end - - it "returns nil if the setting isn't available" do - subject.lookup_path(not_existing_path).should be_nil - end - - it "makes an array out of comma separated values" do - subject.lookup_path(array_path).should == ["foo", "bar", "baz"] - end - end -end diff --git a/spec/lib/configuration/provider/yaml_spec.rb b/spec/lib/configuration/provider/yaml_spec.rb deleted file mode 100644 index ea4da964d..000000000 --- a/spec/lib/configuration/provider/yaml_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -require 'spec_helper' - -describe Configuration::Provider::YAML do - let(:settings) { {"toplevel" => "bar", - "some" => { - "nested" => { "some" => "lala", "setting" => "foo"} - } - } } - - describe "#initialize" do - it "loads the file" do - file = "foobar.yml" - ::YAML.should_receive(:load_file).with(file).and_return({}) - described_class.new file - end - - it "raises if the file is not found" do - ::YAML.stub(:load_file).and_raise(Errno::ENOENT) - expect { - described_class.new "foo" - }.to raise_error Errno::ENOENT - end - - - context "with a namespace" do - it "looks in the file for that namespace" do - namespace = "some" - ::YAML.stub(:load_file).and_return(settings) - provider = described_class.new 'bla', namespace: namespace - provider.instance_variable_get(:@settings).should == settings[namespace] - end - - it "raises if the namespace isn't found" do - ::YAML.stub(:load_file).and_return({}) - expect { - described_class.new 'bla', namespace: "foo" - }.to raise_error ArgumentError - end - end - - context "with required set to false" do - it "doesn't raise if a file isn't found" do - ::YAML.stub(:load_file).and_raise(Errno::ENOENT) - expect { - described_class.new "not_me", required: false - }.not_to raise_error Errno::ENOENT - end - - it "doesn't raise if a namespace isn't found" do - ::YAML.stub(:load_file).and_return({}) - expect { - described_class.new 'bla', namespace: "foo", required: false - }.not_to raise_error ArgumentError - end - end - end - - describe "#lookup_path" do - before do - ::YAML.stub(:load_file).and_return(settings) - @provider = described_class.new 'dummy' - end - - it "looks up the whole nesting" do - @provider.lookup_path(["some", "nested", "some"]).should == settings["some"]["nested"]["some"] - end - - it "returns nil if no setting is found" do - @provider.lookup_path(["not_me"]).should be_nil - end - end -end diff --git a/spec/lib/configuration/provider_spec.rb b/spec/lib/configuration/provider_spec.rb deleted file mode 100644 index 12cba2491..000000000 --- a/spec/lib/configuration/provider_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe Configuration::Provider::Base do - subject { described_class.new } - describe "#lookup" do - it "calls #lookup_path with the setting as array" do - subject.should_receive(:lookup_path).with(["foo", "bar"]).and_return("something") - subject.lookup("foo.bar").should == "something" - end - - it "raises SettingNotFoundError if the #lookup_path returns nil" do - subject.should_receive(:lookup_path).and_return(nil) - expect { - subject.lookup("bla") - }.to raise_error Configuration::SettingNotFoundError - end - end -end diff --git a/spec/lib/configuration/proxy_spec.rb b/spec/lib/configuration/proxy_spec.rb deleted file mode 100644 index 950112d2f..000000000 --- a/spec/lib/configuration/proxy_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe Configuration::Proxy do - let(:lookup_chain) { mock } - before do - lookup_chain.stub(:lookup).and_return("something") - end - - describe "#method_missing" do - it "calls #get if the method ends with a ?" do - lookup_chain.should_receive(:lookup).with("enable").and_return(false) - described_class.new(lookup_chain).method_missing(:enable?) - end - - it "calls #get if the method ends with a =" do - lookup_chain.should_receive(:lookup).with("url=").and_return(false) - described_class.new(lookup_chain).method_missing(:url=) - end - end - - describe "#get" do - [:to_str, :to_s, :to_xml, :respond_to?, :present?, :!=, - :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 = mock - lookup_chain.should_receive(:lookup).and_return(target) - target.should_receive(method).and_return("something") - described_class.new(lookup_chain).something.__send__(method, mock) - end - end - - described_class::COMMON_KEY_NAMES.each do |method| - it "is not called for accessing #{method} on the proxy" do - target = mock - lookup_chain.should_not_receive(:lookup).and_return(target) - target.should_not_receive(method).and_return("something") - described_class.new(lookup_chain).something.__send__(method, mock) - end - end - - it "strips leading dots" do - lookup_chain.should_receive(:lookup).with("foo.bar").and_return("something") - described_class.new(lookup_chain).foo.bar.get - end - - it "returns nil if no setting is given" do - described_class.new(lookup_chain).get.should be_nil - end - - it "strips ? at the end" do - lookup_chain.should_receive(:lookup).with("foo.bar").and_return("something") - described_class.new(lookup_chain).foo.bar? - end - end -end diff --git a/spec/lib/configuration/methods_spec.rb b/spec/lib/configuration_methods_spec.rb similarity index 96% rename from spec/lib/configuration/methods_spec.rb rename to spec/lib/configuration_methods_spec.rb index 1ec0b7520..8cc3525d8 100644 --- a/spec/lib/configuration/methods_spec.rb +++ b/spec/lib/configuration_methods_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Configuration::Methods do before(:all) do - @settings = Configuration::Settings.create do - add_provider Configuration::Provider::Dynamic - add_provider Configuration::Provider::Env + @settings = Configurate::Settings.create do + add_provider Configurate::Provider::Dynamic + add_provider Configurate::Provider::Env extend Configuration::Methods end end diff --git a/spec/lib/configuration_spec.rb b/spec/lib/configuration_spec.rb deleted file mode 100644 index f75619541..000000000 --- a/spec/lib/configuration_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'spec_helper' - -describe Configuration::Settings do - describe "#method_missing" do - subject { described_class.create } - - it "delegates the call to a new proxy object" do - proxy = mock - Configuration::Proxy.should_receive(:new).and_return(proxy) - proxy.should_receive(:method_missing).with(:some_setting).and_return("foo") - subject.some_setting - end - end - - [:lookup, :add_provider, :[]].each do |method| - describe "#{method}" do - subject { described_class.create } - - it "delegates the call to #lookup_chain" do - subject.lookup_chain.should_receive(method) - subject.send(method) - end - end - end -end