mc-settings 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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