mc-settings 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ codecov:
2
+ require_ci_to_pass: no
3
+
4
+ notify:
5
+ wait_for_ci: yes
6
+
7
+ parsers:
8
+ v1:
9
+ include_full_missed_files: true # To use with Ruby so we see files that have NO tests written
10
+
11
+ coverage:
12
+ range: 50..75
13
+ round: down
14
+ precision: 1
15
+ status:
16
+ project:
17
+ default: off
18
+ settings:
19
+ target: 70%
20
+ threshold: 10%
21
+ informational: true
22
+ if_not_found: success
23
+ if_ci_failed: error
24
+ paths:
25
+ - lib/
26
+ flags:
27
+ settings:
28
+ paths:
29
+ - lib/
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Setting
4
+ VERSION = '0.2.0'
5
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'singleton'
2
4
  require 'yaml'
3
5
  require 'erb'
@@ -19,91 +21,103 @@ end
19
21
 
20
22
  class Setting
21
23
  class NotFound < RuntimeError; end
24
+
22
25
  class FileError < RuntimeError; end
26
+
23
27
  class AlreadyLoaded < RuntimeError; end
24
28
 
25
29
  include Singleton
26
-
27
30
  attr_reader :available_settings
28
31
 
29
- # This method can be called only once.
30
- #
31
- # Parameter hash looks like this:
32
- #
33
- # { :files => [ "file1.yml", "file2.yml", ...],
34
- # :path => "/var/www/apps/my-app/current/config/settings",
35
- # :local => true }
36
- #
37
- # If :local => true is set, we will load all *.yml files under :path/local directory
38
- # after all files in :files have been loaded. "Local" settings thus take precedence
39
- # by design. See README for more details.
40
- #
41
- def self.load(args = {})
42
- raise AlreadyLoaded.new('Settings already loaded') if self.instance.loaded?
43
- self.instance.load(args)
44
- end
32
+ class << self
33
+ # This method can be called only once.
34
+ #
35
+ # Parameter hash looks like this:
36
+ #
37
+ # { :files => [ "file1.yml", "file2.yml", ...],
38
+ # :path => "/var/www/apps/my-app/current/config/settings",
39
+ # :local => true }
40
+ #
41
+ # If :local => true is set, we will load all *.yml files under :path/local directory
42
+ # after all files in :files have been loaded. "Local" settings thus take precedence
43
+ # by design. See README for more details.
44
+ #
45
+ def load(**args)
46
+ raise AlreadyLoaded, 'Settings already loaded' if instance.loaded?
47
+
48
+ instance.load(**args)
49
+ end
45
50
 
46
- def self.reload(args = {})
47
- self.instance.load(args)
48
- end
51
+ def reload(**args)
52
+ instance.load(**args)
53
+ end
49
54
 
50
- # In Method invocation syntax we collapse Hash values
51
- # and return a single value if 'default' is found among keys
52
- # or Hash has only one key/value pair.
53
- #
54
- # For example, if the YML data is:
55
- # tax:
56
- # default: 0.0
57
- # california: 7.5
58
- #
59
- # Then calling Setting.tax returns "0.0""
60
- #
61
- # This is the preferred method of using settings class.
62
- #
63
- def self.method_missing(method, *args, &block)
64
- self.instance.value_for(method, args) do |v, args|
65
- self.instance.collapse_hashes(v, args)
55
+ # In Method invocation syntax we collapse Hash values
56
+ # and return a single value if 'default' is found among keys
57
+ # or Hash has only one key/value pair.
58
+ #
59
+ # For example, if the YML data is:
60
+ # tax:
61
+ # default: 0.0
62
+ # california: 7.5
63
+ #
64
+ # Then calling Setting.tax returns "0.0""
65
+ #
66
+ # This is the preferred method of using settings class.
67
+ #
68
+ def method_missing(method, *args)
69
+ instance.value_for(method, args) do |v, args|
70
+ instance.collapse_hashes(v, args)
71
+ end
66
72
  end
67
- end
68
73
 
69
- # In [] invocation syntax, we return settings value 'as is' without
70
- # Hash conversions.
71
- #
72
- # For example, if the YML data is:
73
- # tax:
74
- # default: 0.0
75
- # california: 7.5
76
- #
77
- # Then calling Setting['tax'] returns
78
- # { 'default' => "0.0", 'california' => "7.5"}
79
-
80
- def self.[](value)
81
- self.instance.value_for(value)
82
- end
74
+ def respond_to_missing?
75
+ true
76
+ end
83
77
 
84
- # <b>DEPRECATED:</b> Please use <tt>method accessors</tt> instead.
85
- def self.available_settings
86
- self.instance.available_settings
78
+ # In [] invocation syntax, we return settings value 'as is' without
79
+ # Hash conversions.
80
+ #
81
+ # For example, if the YML data is:
82
+ # tax:
83
+ # default: 0.0
84
+ # california: 7.5
85
+ #
86
+ # Then calling Setting['tax'] returns
87
+ # { 'default' => "0.0", 'california' => "7.5"}
88
+
89
+ def [](value)
90
+ instance.value_for(value)
91
+ end
92
+
93
+ # <b>DEPRECATED:</b> Please use <tt>method accessors</tt> instead.
94
+ def available_settings
95
+ instance.available_settings
96
+ end
87
97
  end
88
98
 
89
- #=================================================================
90
99
  # Instance Methods
91
- #=================================================================
92
100
 
93
101
  def initialize
94
- @available_settings ||= {}
102
+ @available_settings = {}
95
103
  end
96
104
 
97
- def has_key?(key)
98
- @available_settings.has_key?(key) ||
99
- (key[-1,1] == '?' && @available_settings.has_key?(key.chop))
105
+ # @param [Object] key
106
+ def key?(key)
107
+ @available_settings.key?(key) ||
108
+ (key[-1, 1] == '?' && @available_settings.key?(key.chop))
100
109
  end
101
110
 
111
+ alias has_key? key?
112
+
102
113
  def value_for(key, args = [])
103
114
  name = key.to_s
104
- raise NotFound.new("#{name} was not found") unless has_key?(name)
115
+ unless key?(name)
116
+ raise NotFound, "#{name} was not found"
117
+ end
118
+
105
119
  bool = false
106
- if name[-1,1] == '?'
120
+ if name[-1, 1] == '?'
107
121
  name.chop!
108
122
  bool = true
109
123
  end
@@ -113,7 +127,7 @@ class Setting
113
127
  v = yield(v, args)
114
128
  end
115
129
 
116
- if v.is_a?(Fixnum) && bool
130
+ if v.is_a?(Integer) && bool
117
131
  v.to_i > 0
118
132
  else
119
133
  v
@@ -122,60 +136,68 @@ class Setting
122
136
 
123
137
  # This method performs collapsing of the Hash settings values if the Hash
124
138
  # contains 'default' value, or just 1 element.
125
-
139
+
126
140
  def collapse_hashes(v, args)
127
141
  out = if v.is_a?(Hash)
128
- if args.empty?
129
- if v.has_key?("default")
130
- v['default'].nil? ? "" : v['default']
131
- elsif v.keys.size == 1
132
- v.values.first
133
- else
134
- v
135
- end
136
- else
137
- v[args.shift.to_s]
138
- end
139
- else
140
- v
141
- end
142
+ if args.empty?
143
+ if v.key?("default")
144
+ v['default'].nil? ? "" : v['default']
145
+ elsif v.keys.size == 1
146
+ v.values.first
147
+ else
148
+ v
149
+ end
150
+ else
151
+ v[args.shift.to_s]
152
+ end
153
+ else
154
+ v
155
+ end
142
156
  if out.is_a?(Hash) && !args.empty?
143
- collapse_hashes(out, args)
144
- elsif out.is_a?(Hash) && out.has_key?('default')
157
+ collapse_hashes(out, args)
158
+ elsif out.is_a?(Hash) && out.key?('default')
145
159
  out['default']
146
160
  else
147
161
  out
148
162
  end
149
163
  end
150
-
164
+
151
165
  def loaded?
152
166
  @loaded
153
167
  end
154
168
 
155
- def load(params)
169
+ def load(**params)
156
170
  # reset settings hash
157
171
  @available_settings = {}
158
- @loaded = false
172
+ @loaded = false
159
173
 
160
174
  files = []
161
175
  path = params[:path] || Dir.pwd
162
176
  params[:files].each do |file|
163
177
  files << File.join(path, file)
164
178
  end
165
-
166
179
  if params[:local]
167
180
  files << Dir.glob(File.join(path, 'local', '*.yml')).sort
168
181
  end
169
182
 
170
183
  files.flatten.each do |file|
171
- begin
172
- @available_settings.recursive_merge!(YAML::load(ERB.new(IO.read(file)).result) || {}) if File.exists?(file)
173
- rescue Exception => e
174
- raise FileError.new("Error parsing file #{file}, with: #{e.message}")
184
+ if File.exist?(file)
185
+ @available_settings.recursive_merge! load_file(file)
175
186
  end
187
+ rescue StandardError => e
188
+ raise FileError, "Error parsing file #{file}, with: #{e.message}"
176
189
  end
177
190
 
178
191
  @loaded = true
179
192
  @available_settings
180
193
  end
194
+
195
+ private
196
+
197
+ def load_file(file)
198
+ ::YAML.load(
199
+ ::ERB.new(::IO.read(file)).result,
200
+ fallback: {},
201
+ )
202
+ end
181
203
  end
@@ -1,72 +1,33 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require_relative 'lib/mc-settings/version'
5
7
 
6
8
  Gem::Specification.new do |s|
7
- s.name = %q{mc-settings}
8
- s.version = "0.1.6"
9
+ s.name = 'mc-settings'
10
+ s.version = Setting::VERSION
9
11
 
10
12
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Edwin Cruz", "Colin Shield"]
12
- s.date = %q{2011-09-06}
13
- s.description = %q{implement custom keys indenendently of environment}
14
- s.email = %q{rubydev@modcloth.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE.txt",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- ".document",
21
- "Gemfile",
22
- "Gemfile.lock",
23
- "LICENSE.txt",
24
- "README.rdoc",
25
- "Rakefile",
26
- "VERSION",
27
- "lib/mc-settings.rb",
28
- "lib/setting.rb",
29
- "mc-settings.gemspec",
30
- "spec/fixtures/joes-colors.yml",
31
- "spec/fixtures/sample.yml",
32
- "spec/fixtures/shipping.yml",
33
- "spec/mc_settings_spec.rb",
34
- "spec/spec_helper.rb",
35
- "spec/support/settings_helper.rb"
36
- ]
37
- s.homepage = %q{http://github.com/modcloth/mc-settings}
38
- s.licenses = ["MIT"]
39
- s.require_paths = ["lib"]
40
- s.rubygems_version = %q{1.5.3}
41
- s.summary = %q{Manage settings per environment}
42
- s.test_files = [
43
- "spec/mc_settings_spec.rb",
44
- "spec/spec_helper.rb",
45
- "spec/support/settings_helper.rb"
46
- ]
13
+ s.authors = ["Edwin Cruz", "Colin Shield", "Konstantin Gredeskoul"]
14
+ s.date = '2020-09-01'
15
+ s.description = 'Manage application configuration and settings per deployment environment'
16
+ s.summary = 'Manage application configuration and settings per deployment environment'
17
+ s.email = %w[softr8@gmail.com kigster@gmail.com]
18
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ s.extra_rdoc_files = %w[LICENSE.txt README.adoc]
20
+ s.homepage = 'https://github.com/kigster/mc-settings'
21
+ s.licenses = ["MIT"]
22
+ s.require_paths = ["lib"]
23
+ s.test_files = %w[spec/mc_settings_spec.rb spec/spec_helper.rb spec/support/settings_helper.rb]
47
24
 
48
- if s.respond_to? :specification_version then
49
- s.specification_version = 3
50
-
51
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
- s.add_development_dependency(%q<rspec>, [">= 0"])
53
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
54
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
55
- s.add_development_dependency(%q<rcov>, [">= 0"])
56
- s.add_development_dependency(%q<ruby-debug>, [">= 0"])
57
- else
58
- s.add_dependency(%q<rspec>, [">= 0"])
59
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
- s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
61
- s.add_dependency(%q<rcov>, [">= 0"])
62
- s.add_dependency(%q<ruby-debug>, [">= 0"])
63
- end
64
- else
65
- s.add_dependency(%q<rspec>, [">= 0"])
66
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
67
- s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
68
- s.add_dependency(%q<rcov>, [">= 0"])
69
- s.add_dependency(%q<ruby-debug>, [">= 0"])
70
- end
25
+ s.add_development_dependency('bundler')
26
+ s.add_development_dependency('rspec', '~> 3.0')
27
+ s.add_development_dependency('simplecov')
28
+ s.add_development_dependency('rake')
29
+ s.add_development_dependency('pry-byebug')
30
+ s.add_development_dependency('rspec-mocks')
31
+ s.add_development_dependency('asciidoctor')
32
+ s.add_development_dependency('rspec-expectations')
71
33
  end
72
-
@@ -1,70 +1,76 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+ require 'mc-settings'
2
5
  describe Setting do
3
- subject { Setting }
6
+ subject { described_class }
4
7
 
5
8
  context "Test with stubs" do
6
9
  before :each do
7
10
  stub_setting_files
8
- Setting.reload(
9
- :path => "config/settings",
10
- :files => ["default.yml", "environments/test.yml"],
11
- :local => true)
11
+ subject.reload(
12
+ path: "config/settings",
13
+ files: %w[default.yml environments/test.yml],
14
+ local: true
15
+ )
12
16
  end
13
17
 
14
18
  it 'should return test specific values' do
15
- Setting.available_settings['one'].should == "test"
16
- Setting.one.should == "test"
17
- Setting['one'].should == "test"
19
+ expect(subject.available_settings['one']).to eql "test"
20
+ expect(subject.one).to eql "test"
21
+ expect(subject['one']).to eql "test"
18
22
  end
19
23
 
20
24
  it "should handle custom values overriding everything else" do
21
- Setting.seven.should == "seven from custom"
25
+ expect(subject.seven).to eql "seven from custom"
22
26
  end
23
27
 
28
+ let(:six) { { "default" => "default value", "extra" => "recursively overriden", "deep_level" => { "value" => "even deeper level" } } }
29
+
24
30
  it "handles multiple values" do
25
- Setting[:six].should == {"default"=>"default value", "extra"=>"recursively overriden", "deep_level"=>{"value"=>"even deeper level"}}
26
- Setting.available_settings['six']['default'].should == "default value"
27
- Setting.seven.should == "seven from custom"
31
+ expect(subject[:six]).to eql six
32
+ expect(subject.available_settings['six']['default']).to eql "default value"
33
+ expect(subject.seven).to eql "seven from custom"
28
34
  end
29
35
 
30
36
  it "handles default key" do
31
- Setting.default_setting.should == 1
32
- Setting['seven']['default'].should == "seven from custom"
37
+ expect(subject.default_setting).to eql 1
38
+ expect(subject['seven']['default']).to eql "seven from custom"
33
39
  end
34
40
 
35
41
  it "should handle empty strings" do
36
- Setting.empty.should == ""
42
+ expect(subject.empty).to eql ""
37
43
  end
38
44
 
39
45
  it "should responds to ? mark" do
40
- Setting.autologin?.should == true
46
+ expect(subject.autologin?).to eql true
41
47
  end
42
48
 
43
49
  it "should returns false correctly" do
44
- Setting.flag_false.should be(false)
50
+ expect(subject.flag_false).to be_falsey
45
51
  end
46
52
 
47
- it "should merge keys recursivelly" do
48
- Setting.six(:extra).should == "recursively overriden"
49
- Setting.six(:deep_level, :value).should == "even deeper level"
53
+ it "should merge keys recursively" do
54
+ expect(subject.six(:extra)).to eql "recursively overriden"
55
+ expect(subject.six(:deep_level, :value)).to eql "even deeper level"
50
56
  end
51
57
 
52
58
  it "should create keys if it does not exist" do
53
- Setting.test_specific.should == "exist"
59
+ expect(subject.test_specific).to eql "exist"
54
60
  end
55
61
 
56
62
  context "working with arrays" do
57
63
  it "should replace the whole array instead of appending new values" do
58
- Setting.nested_array.should == ['first', 'four', 'five']
64
+ expect(subject.nested_array).to eql %w[first four five]
59
65
  end
60
66
  end
61
67
  end
62
68
 
63
69
  context "When running with threads" do
64
70
  it "should keep its values" do
65
- 3.times do |time|
71
+ 3.times do |_time|
66
72
  Thread.new {
67
- Setting.available_settings.shoud_not be_empty
73
+ expect(subject.available_settings).not_to be_empty
68
74
  }
69
75
  end
70
76
  end
@@ -72,89 +78,84 @@ describe Setting do
72
78
 
73
79
  context "Test from file" do
74
80
  before :each do
75
- Setting.reload(
76
- :path => File.join(File.dirname(__FILE__)) + '/fixtures',
77
- :files => ['sample.yml']
81
+ subject.reload(
82
+ path: File.join(File.dirname(__FILE__)) + '/fixtures',
83
+ files: ['sample.yml']
78
84
  )
79
85
  end
80
86
 
81
87
  it 'should support [] syntax' do
82
- Setting['tax']['default'].should == 0.0
83
- Setting['tax'].should == { 'default' => 0.0, 'california' => 7.5 }
88
+ expect(subject['tax']['default']).to eql 0.0
89
+ expect(subject['tax']).to eql( 'default' => 0.0, 'california' => 7.5 )
84
90
  end
85
91
 
86
92
  it 'should support method invocation syntax' do
87
- Setting.tax.should == 0.0
88
-
89
- Setting.tax(:default).should == Setting.tax
90
- Setting.tax('default').should == Setting.tax
91
- Setting.tax(:california).should == 7.5
92
-
93
- Setting.states.should == ['CA', 'WA', 'NY']
94
- Setting.states(:default).should == Setting.states
95
- Setting.states(:ship_to).should == ['CA', 'NY']
93
+ expect(subject.tax).to eql 0.0
94
+ expect(subject.states).to eql %w[CA WA NY]
95
+ expect(subject.tax(:default)).to eql subject.tax
96
+ expect(subject.tax('default')).to eql subject.tax
97
+ expect(subject.tax(:california)).to eql 7.5
98
+ expect(subject.states(:default)).to eql subject.states
99
+ expect(subject.states(:ship_to)).to eql %w[CA NY]
96
100
  end
97
101
 
98
102
  it 'should correctly process Boolean values' do
99
- Setting.boolean_true?.should be(true)
100
- Setting.boolean_true.should == 4
101
- Setting.boolean_false?.should be(false)
102
- Setting.boolean_false?(:default).should be(false)
103
- Setting.boolean_false?(:negated).should be(true)
103
+ expect(subject.boolean_true?).to be_truthy
104
+ expect(subject.boolean_true).to eql 4
105
+ expect(subject.boolean_false?).to be_falsey
106
+ expect(subject.boolean_false?(:default)).to be_falsey
107
+ expect(subject.boolean_false?(:negated)).to be_truthy
104
108
  end
105
109
  end
106
110
 
107
111
  context "Test recursive overrides and nested hashes" do
108
112
  before :each do
109
- Setting.reload(
110
- :path => File.join(File.dirname(__FILE__)) + '/fixtures',
111
- :files => ['sample.yml', 'joes-colors.yml']
113
+ subject.reload(
114
+ path: File.join(File.dirname(__FILE__)) + '/fixtures',
115
+ files: %w[sample.yml joes-colors.yml]
112
116
  )
113
117
  end
114
118
 
115
119
  it 'should override colors with Joes and support nested hashes' do
116
- Setting.color.should == :grey # default
117
- Setting.color(:pants).should == :purple # default
118
-
119
- Setting.color(:pants, :school).should == :blue # in sample
120
- Setting.color(:pants, :favorite).should == :orange # joes override
121
-
122
- Setting.color(:shorts, :school).should == :black # in sample
123
- Setting.color(:shorts, :favorite).should == :white # joe's override
124
-
125
- Setting.color(:shorts).should == :stripes # joe's override of default
120
+ expect(subject.color).to eql :grey # default
121
+ expect(subject.color(:pants)).to eql :purple # default
122
+ expect(subject.color(:pants, :school)).to eql :blue # in sample
123
+ expect(subject.color(:pants, :favorite)).to eql :orange # joes override
124
+ expect(subject.color(:shorts,:school)).to eql :black # in sample
125
+ expect(subject.color(:shorts, :favorite)).to eql :white # joe's override
126
+ expect(subject.color(:shorts)).to eql :stripes # joe's override of default
126
127
  end
127
-
128
128
  end
129
+
129
130
  context "Complex nested configs" do
130
131
  before :each do
131
- Setting.reload(
132
- :path => File.join(File.dirname(__FILE__)) + '/fixtures',
133
- :files => ['shipping.yml']
132
+ subject.reload(
133
+ path: File.join(File.dirname(__FILE__)) + '/fixtures',
134
+ files: ['shipping.yml']
134
135
  )
135
136
  end
137
+
136
138
  it "should build correct tree with arrays and default values " do
137
- Setting.shipping_config.should == "Defaulted"
138
- Setting.shipping_config(:domestic, :non_shippable_regions).first.should == "US-AS"
139
- Setting.shipping_config(:international, :service).should == 'Foo'
140
- Setting.shipping_config(:international, :countries).size.should > 0
141
- Setting.shipping_config(:international, :shipping_carrier).should == 'Bar'
142
- #backward compatibility:
143
- Setting.shipping_config(:domestic)['non_shippable_regions'].size.should > 0
139
+ expect(subject.shipping_config).to eql "Defaulted"
140
+ expect(subject.shipping_config(:domestic, :non_shippable_regions).first).to eql "US-AS"
141
+ expect(subject.shipping_config(:international, :service)).to eql 'Foo'
142
+ expect(subject.shipping_config(:international, :countries).size).to be > 0
143
+ expect(subject.shipping_config(:international, :shipping_carrier)).to eql 'Bar'
144
+ expect(subject.shipping_config(:domestic)['non_shippable_regions'].size).to be > 0
144
145
  end
145
146
  end
146
147
 
147
148
  context "Ruby code inside yml file" do
148
149
  before :each do
149
- Setting.reload(
150
- :path => File.join(File.dirname(__FILE__)) + '/fixtures',
151
- :files => ['shipping.yml']
150
+ subject.reload(
151
+ path: File.join(File.dirname(__FILE__)) + '/fixtures',
152
+ files: ['shipping.yml']
152
153
  )
153
154
  end
154
155
  it "should interpret ruby code and put correct values" do
155
- Setting.shipping_config.should == "Defaulted"
156
- Setting.number == 5
157
- Setting.stringified == "stringified"
156
+ expect(subject.shipping_config).to eql "Defaulted"
157
+ expect(subject.number).to eql 5
158
+ expect(subject.stringified).to eql "stringified"
158
159
  end
159
160
  end
160
- end
161
+ end