dry-configurable 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88d85d11d0d2a3b9ad67cd8e6edf2873f4e77976
4
- data.tar.gz: 5ed14500e8ab67b2b87e5e8cae5d8c5826b52649
3
+ metadata.gz: 24d1584b6f9d1d9fbe7fe62ef7b7c0afbd558176
4
+ data.tar.gz: b94c4d0b877b2093de30add176fc70e413c28ef0
5
5
  SHA512:
6
- metadata.gz: 2a79bb4089fb04ab2efab2572b74d7dda4d0564bef8f796cb4a6d0fda4bbfa5f45f2237604eea590bfeafbfbd4245fcce3f96acbcc1b71ced5a94d6e15821549
7
- data.tar.gz: 2ef0507f31d224efa6f0d0c378a2bbfaed11854671c106a5a840083e9ae7e08124beaff2fe3093ce9e992be5f8e6f2842bf9da652548ed0a013908d3a4c64c27
6
+ metadata.gz: 796c1b446cb4e31947f2274409325e4ec21ae2c8c8a34d5100723f74063c410756bd96a0327844f140a49bf0fd0e489194684c1876ed03c748e6e85a6c177c64
7
+ data.tar.gz: 44e36011d7d0c59f15d1dfb59b6e800c094be7a02ac60c781e8410f7ecfc151ed660ba4cec7928c6955f873ee215d7ef9ab2eb54d7ea237ca9e98027e1fff7ba
data/README.md CHANGED
@@ -25,6 +25,12 @@ class App
25
25
  end
26
26
  # Defaults to nil if no default value is given
27
27
  setting :adapter
28
+ # Passing the reader option as true will create reader method for the class
29
+ setting :pool, 5, reader: true
30
+ # Passing the reader attributes works with nested configuration
31
+ setting :uploader, reader: true do
32
+ setting :bucket, 'dev'
33
+ end
28
34
  end
29
35
 
30
36
  App.configure do |config|
@@ -34,6 +40,8 @@ end
34
40
  App.config.database.dsn
35
41
  # => 'jdbc:sqlite:memory'
36
42
  App.config.adapter # => nil
43
+ App.pool # => 5
44
+ App.uploader.bucket # => 'dev'
37
45
  ```
38
46
 
39
47
  ## Links
@@ -1,6 +1,8 @@
1
1
  require 'concurrent'
2
2
  require 'dry/configurable/config'
3
+ require 'dry/configurable/error'
3
4
  require 'dry/configurable/nested_config'
5
+ require 'dry/configurable/argument_parser'
4
6
  require 'dry/configurable/config/value'
5
7
  require 'dry/configurable/version'
6
8
 
@@ -33,6 +35,7 @@ module Dry
33
35
  base.class_eval do
34
36
  @_config_mutex = ::Mutex.new
35
37
  @_settings = ::Concurrent::Array.new
38
+ @_reader_attributes = ::Concurrent::Array.new
36
39
  end
37
40
  end
38
41
 
@@ -40,6 +43,7 @@ module Dry
40
43
  def inherited(subclass)
41
44
  subclass.instance_variable_set(:@_config_mutex, ::Mutex.new)
42
45
  subclass.instance_variable_set(:@_settings, @_settings.clone)
46
+ subclass.instance_variable_set(:@_reader_attributes, @_reader_attributes.clone)
43
47
  subclass.instance_variable_set(:@_config, @_config.clone) if defined?(@_config)
44
48
  super
45
49
  end
@@ -79,7 +83,9 @@ module Dry
79
83
  # @return [Dry::Configurable::Config]
80
84
  #
81
85
  # @api public
82
- def setting(key, value = ::Dry::Configurable::Config::Value::NONE, &block)
86
+ def setting(key, *args, &block)
87
+ raise_already_defined_config(key) if defined?(@_config)
88
+ value, options = ArgumentParser.call(args)
83
89
  if block
84
90
  if block.parameters.empty?
85
91
  value = _config_for(&block)
@@ -90,9 +96,10 @@ module Dry
90
96
 
91
97
  _settings << ::Dry::Configurable::Config::Value.new(
92
98
  key,
93
- value,
99
+ value || ::Dry::Configurable::Config::Value::NONE,
94
100
  processor || ::Dry::Configurable::Config::DEFAULT_PROCESSOR
95
101
  )
102
+ store_reader_options(key, options) if options.any?
96
103
  end
97
104
 
98
105
  # Return an array of setting names
@@ -109,6 +116,10 @@ module Dry
109
116
  @_settings
110
117
  end
111
118
 
119
+ def _reader_attributes
120
+ @_reader_attributes
121
+ end
122
+
112
123
  private
113
124
 
114
125
  # @private
@@ -126,12 +137,33 @@ module Dry
126
137
 
127
138
  # @private
128
139
  def create_config_for_nested_configurations
129
- nested_configs.map { |nested_config| nested_config.create_config }
140
+ nested_configs.map(&:create_config)
130
141
  end
131
142
 
132
143
  # @private
133
144
  def nested_configs
134
- _settings.select { |setting| setting.value.kind_of?(::Dry::Configurable::NestedConfig) }.map(&:value)
145
+ _settings.select { |setting| setting.value.is_a?(::Dry::Configurable::NestedConfig) }.map(&:value)
146
+ end
147
+
148
+ # @private
149
+ def raise_already_defined_config(key)
150
+ raise AlreadyDefinedConfig,
151
+ "Cannot add setting +#{key}+, #{self} is already configured"
152
+ end
153
+
154
+ # @private
155
+ def store_reader_options(key, options)
156
+ _reader_attributes << key if options.fetch(:reader, false)
157
+ end
158
+
159
+ # @private
160
+ def method_missing(method, *args, &block)
161
+ _reader_attributes.include?(method) ? config.public_send(method, *args, &block) : super
162
+ end
163
+
164
+ # @private
165
+ def respond_to_missing?(method, _include_private = false)
166
+ _reader_attributes.include?(method) || super
135
167
  end
136
168
  end
137
169
  end
@@ -0,0 +1,91 @@
1
+ module Dry
2
+ # Argument parser
3
+ #
4
+ # Passing and array or arguments, it will decide wich one are arguments
5
+ # and which one are options.
6
+ #
7
+ # We have a limitation if setting the value without options, as a hash
8
+ # having the same key as one of the valid options, will parse the value
9
+ # as options.
10
+ #
11
+ # @example
12
+ # p = Dry::Configurable::ArgumentParser.new(['db:sqlite', { reader: true })
13
+ #
14
+ # p.value # => 'db:sqlite'
15
+ # p.options # => { reader: true }
16
+ #
17
+ # Dry::Configurable::ArgumentParser.call(['db:sqlite', { reader: true })
18
+ # # => [ 'db:sqlite', { reader: true } ]
19
+ module Configurable
20
+ # @private
21
+ class ArgumentParser
22
+ VALID_OPTIONS = %i(reader).freeze
23
+
24
+ def self.call(data)
25
+ parsed = new(data)
26
+ [parsed.value, parsed.options]
27
+ end
28
+
29
+ def initialize(data)
30
+ @data = data
31
+ end
32
+
33
+ def value
34
+ parse_args[:value]
35
+ end
36
+
37
+ def options
38
+ parse_args[:options]
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :data
44
+
45
+ # @private
46
+ def default_args
47
+ { value: nil, options: {} }
48
+ end
49
+
50
+ # @private
51
+ def parse_args
52
+ return default_args if data.empty?
53
+ if data.size > 1
54
+ { value: data.first, options: check_options(data.last) }
55
+ else
56
+ default_args.merge(check_for_value_or_options(data.first))
57
+ end
58
+ end
59
+
60
+ # @private
61
+ def check_options(opts)
62
+ return {} if opts.empty?
63
+ opts.select { |k, _| VALID_OPTIONS.include?(k) }
64
+ end
65
+
66
+ # @private
67
+ def check_for_value_or_options(args)
68
+ case args
69
+ when Hash
70
+ parse_hash(args)
71
+ else
72
+ { value: args }
73
+ end
74
+ end
75
+
76
+ # @private
77
+ def parse_hash(args)
78
+ if hash_include_options_key(args)
79
+ { options: check_options(args) }
80
+ else
81
+ { value: args }
82
+ end
83
+ end
84
+
85
+ # @private
86
+ def hash_include_options_key(hash)
87
+ hash.any? { |k, _| VALID_OPTIONS.include?(k) }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -48,10 +48,10 @@ module Dry
48
48
  @config.each_with_object({}) do |tuple, hash|
49
49
  key, value = tuple
50
50
 
51
- if value.kind_of?(::Dry::Configurable::Config)
51
+ if value.is_a?(::Dry::Configurable::Config)
52
52
  hash[key] = value.to_h
53
53
  else
54
- hash[key] = value
54
+ hash[key] = value
55
55
  end
56
56
  end
57
57
  end
@@ -9,7 +9,9 @@ module Dry
9
9
  attr_reader :name, :processor
10
10
 
11
11
  def initialize(name, value, processor)
12
- @name, @value, @processor = name.to_sym, value, processor
12
+ @name = name.to_sym
13
+ @value = value
14
+ @processor = processor
13
15
  end
14
16
 
15
17
  def value
@@ -0,0 +1,8 @@
1
+ # A collection of micro-libraries, each intended to encapsulate
2
+ # a common task in Ruby
3
+ module Dry
4
+ module Configurable
5
+ Error = Class.new(::StandardError)
6
+ AlreadyDefinedConfig = ::Class.new(Error)
7
+ end
8
+ end
@@ -8,7 +8,7 @@ module Dry
8
8
  @klass = klass
9
9
  end
10
10
 
11
- # @private no, really...
11
+ # @private no, really...
12
12
  def create_config
13
13
  if @klass.instance_variables.include?(:@_config)
14
14
  @klass.__send__(:create_config)
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  module Configurable
3
3
  # @api public
4
- VERSION = '0.6.1'.freeze
4
+ VERSION = '0.6.2'.freeze
5
5
  end
6
6
  end
data/spec/spec_helper.rb CHANGED
@@ -43,7 +43,7 @@ RSpec.configure do |config|
43
43
  # Allows RSpec to persist some state between runs in order to support
44
44
  # the `--only-failures` and `--next-failure` CLI options. We recommend
45
45
  # you configure your source control system to ignore this file.
46
- config.example_status_persistence_file_path = "spec/examples.txt"
46
+ config.example_status_persistence_file_path = 'spec/examples.txt'
47
47
 
48
48
  # Limits the available syntax to the non-monkey patched syntax that is
49
49
  # recommended. For more details, see:
@@ -50,6 +50,80 @@ RSpec.shared_examples 'a configurable class' do
50
50
  end
51
51
  end
52
52
 
53
+ context 'reader option' do
54
+ context 'without passing option' do
55
+ before do
56
+ klass.setting :dsn
57
+ end
58
+
59
+ before do
60
+ klass.configure do |config|
61
+ config.dsn = 'jdbc:sqlite:memory'
62
+ end
63
+ end
64
+
65
+ it 'will not create a getter method' do
66
+ expect(klass.respond_to?(:dsn)).to be_falsey
67
+ end
68
+ end
69
+
70
+ context 'with hash as value ' do
71
+ before do
72
+ klass.setting :dsn, { foo: 'bar' }, reader: true
73
+ end
74
+
75
+ it 'will create a getter method' do
76
+ expect(klass.dsn).to eq(foo: 'bar')
77
+ expect(klass.respond_to?(:dsn)).to be_truthy
78
+ end
79
+ end
80
+
81
+ context 'with option set to true' do
82
+ before do
83
+ klass.setting :dsn, 'testing', reader: true
84
+ end
85
+
86
+ it 'will create a getter method' do
87
+ expect(klass.dsn).to eq 'testing'
88
+ expect(klass.respond_to?(:dsn)).to be_truthy
89
+ end
90
+ end
91
+
92
+ context 'with nested configuration' do
93
+ before do
94
+ klass.setting :dsn, reader: true do
95
+ setting :pool, 5
96
+ end
97
+ end
98
+
99
+ it 'will create a nested getter method' do
100
+ expect(klass.dsn.pool).to eq 5
101
+ end
102
+ end
103
+
104
+ context 'with processor' do
105
+ context 'with default value' do
106
+ before do
107
+ klass.setting(:dsn, 'memory', reader: true) { |dsn| "sqlite:#{dsn}" }
108
+ end
109
+
110
+ it 'returns the default value' do
111
+ expect(klass.dsn).to eq('sqlite:memory')
112
+ end
113
+ end
114
+
115
+ context 'without default value' do
116
+ before do
117
+ klass.setting(:dsn, reader: true) { |dsn| "sqlite:#{dsn}" }
118
+ end
119
+
120
+ it 'returns the default value' do
121
+ expect(klass.dsn).to eq(nil)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
53
127
  context 'nested configuration' do
54
128
  before do
55
129
  klass.setting :database do
@@ -118,7 +192,7 @@ RSpec.shared_examples 'a configurable class' do
118
192
 
119
193
  context 'with processor' do
120
194
  before do
121
- klass.setting(:dsn, 'sqlite') { |dsn| "#{dsn}:memory"}
195
+ klass.setting(:dsn, 'sqlite') { |dsn| "#{dsn}:memory" }
122
196
  end
123
197
 
124
198
  before do
@@ -153,7 +227,7 @@ RSpec.shared_examples 'a configurable class' do
153
227
  context 'with processor' do
154
228
  before do
155
229
  klass.setting :database do
156
- setting(:dsn, 'sqlite') { |dsn| "#{dsn}:memory"}
230
+ setting(:dsn, 'sqlite') { |dsn| "#{dsn}:memory" }
157
231
  end
158
232
 
159
233
  klass.configure do |config|
@@ -227,11 +301,10 @@ RSpec.shared_examples 'a configurable class' do
227
301
  context 'when the inherited settings are modified' do
228
302
  before do
229
303
  klass.setting :dsn
304
+ subclass.setting :db
230
305
  klass.configure do |config|
231
306
  config.dsn = 'jdbc:sqlite:memory'
232
307
  end
233
-
234
- subclass.setting :db
235
308
  end
236
309
 
237
310
  subject!(:subclass) { Class.new(klass) }
@@ -267,5 +340,18 @@ RSpec.shared_examples 'a configurable class' do
267
340
  end
268
341
  end
269
342
  end
343
+
344
+ context 'Try to set new value after config has been created' do
345
+ before do
346
+ klass.setting :dsn, 'sqlite:memory'
347
+ klass.config
348
+ end
349
+
350
+ it 'raise an exception' do
351
+ expect { klass.setting :pool, 5 }.to raise_error(
352
+ Dry::Configurable::AlreadyDefinedConfig
353
+ )
354
+ end
355
+ end
270
356
  end
271
357
  end
@@ -0,0 +1,114 @@
1
+ RSpec.describe Dry::Configurable::ArgumentParser do
2
+ let(:klass) { Dry::Configurable::ArgumentParser }
3
+
4
+ context 'with no args' do
5
+ let(:parsed) { klass.new([]) }
6
+
7
+ it 'return default values' do
8
+ expect(parsed.value).to eq nil
9
+ expect(parsed.options).to eq({})
10
+ end
11
+ end
12
+
13
+ context 'with value and options' do
14
+ let(:parsed) { klass.new([value, options]) }
15
+
16
+ context 'valid options' do
17
+ let(:value) { 'dry-rb' }
18
+ let(:options) do
19
+ { reader: true }
20
+ end
21
+
22
+ it 'returns correct value and options' do
23
+ expect(parsed.value).to eq 'dry-rb'
24
+ expect(parsed.options).to eq(reader: true)
25
+ end
26
+ end
27
+
28
+ context 'invalid options' do
29
+ let(:value) { 'dry-rb' }
30
+ let(:options) do
31
+ { writer: true }
32
+ end
33
+
34
+ it 'returns correct values and empty options' do
35
+ expect(parsed.value).to eq 'dry-rb'
36
+ expect(parsed.options).to eq({})
37
+ end
38
+ end
39
+
40
+ context 'values as hash' do
41
+ let(:value) do
42
+ { db: 'dry-rb' }
43
+ end
44
+ let(:options) do
45
+ { reader: true }
46
+ end
47
+
48
+ it 'returns correct values and empty options' do
49
+ expect(parsed.value).to eq(db: 'dry-rb')
50
+ expect(parsed.options).to eq(reader: true)
51
+ end
52
+ end
53
+
54
+ context 'values as array' do
55
+ let(:value) { [1, 2, 3] }
56
+ let(:options) do
57
+ { reader: true }
58
+ end
59
+
60
+ it 'returns correct values and empty options' do
61
+ expect(parsed.value).to eq([1, 2, 3])
62
+ expect(parsed.options).to eq(reader: true)
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'with value only' do
68
+ let(:parsed) { klass.new([value]) }
69
+ context 'valid options' do
70
+ let(:value) { 'dry-rb' }
71
+
72
+ it 'returns correct value and options' do
73
+ expect(parsed.value).to eq 'dry-rb'
74
+ expect(parsed.options).to eq({})
75
+ end
76
+ end
77
+
78
+ context 'with hash with non option key' do
79
+ let(:value) do
80
+ { writer: true }
81
+ end
82
+
83
+ it 'returns correct value and options' do
84
+ expect(parsed.value).to eq(writer: true)
85
+ expect(parsed.options).to eq({})
86
+ end
87
+ end
88
+
89
+ context 'with hash with option key' do
90
+ let(:value) do
91
+ { reader: true, writer: true }
92
+ end
93
+
94
+ it 'returns correct value and options' do
95
+ expect(parsed.value).to eq nil
96
+ expect(parsed.options).to eq(reader: true)
97
+ end
98
+ end
99
+ end
100
+
101
+ context 'with options only' do
102
+ let(:parsed) { klass.new([options]) }
103
+ context 'valid options' do
104
+ let(:options) do
105
+ { reader: true }
106
+ end
107
+
108
+ it 'returns correct value and options' do
109
+ expect(parsed.value).to eq nil
110
+ expect(parsed.options).to eq(reader: true)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -3,7 +3,7 @@ RSpec.describe Dry::Configurable::Config::Value do
3
3
  let(:config) { klass.new(name, value, processor) }
4
4
  let(:name) { :db }
5
5
  let(:value) { 'test' }
6
- let(:processor) { ->(v) { v }}
6
+ let(:processor) { ->(v) { v } }
7
7
 
8
8
  describe '#initialize' do
9
9
  it 'coerces string name to symbol' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Holland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-21 00:00:00.000000000 Z
11
+ date: 2017-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -85,8 +85,10 @@ files:
85
85
  - dry-configurable.gemspec
86
86
  - lib/dry-configurable.rb
87
87
  - lib/dry/configurable.rb
88
+ - lib/dry/configurable/argument_parser.rb
88
89
  - lib/dry/configurable/config.rb
89
90
  - lib/dry/configurable/config/value.rb
91
+ - lib/dry/configurable/error.rb
90
92
  - lib/dry/configurable/nested_config.rb
91
93
  - lib/dry/configurable/test_interface.rb
92
94
  - lib/dry/configurable/version.rb
@@ -94,6 +96,7 @@ files:
94
96
  - spec/integration/configurable_spec.rb
95
97
  - spec/spec_helper.rb
96
98
  - spec/support/shared_examples/configurable.rb
99
+ - spec/unit/dry/configurable/argument_parser_spec.rb
97
100
  - spec/unit/dry/configurable/config/value_spec.rb
98
101
  - spec/unit/dry/configurable/config_spec.rb
99
102
  homepage: https://github.com/dryrb/dry-configurable
@@ -116,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
119
  version: '0'
117
120
  requirements: []
118
121
  rubyforge_project:
119
- rubygems_version: 2.4.8
122
+ rubygems_version: 2.6.10
120
123
  signing_key:
121
124
  specification_version: 4
122
125
  summary: A mixin to add configuration functionality to your classes
@@ -124,5 +127,6 @@ test_files:
124
127
  - spec/integration/configurable_spec.rb
125
128
  - spec/spec_helper.rb
126
129
  - spec/support/shared_examples/configurable.rb
130
+ - spec/unit/dry/configurable/argument_parser_spec.rb
127
131
  - spec/unit/dry/configurable/config/value_spec.rb
128
132
  - spec/unit/dry/configurable/config_spec.rb