unifig 0.3.2 → 0.4.0

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
  SHA256:
3
- metadata.gz: 89378b794de9cdd9511c52a145c4458317713e357634690201b3af379fd19e23
4
- data.tar.gz: 51642a4f0f6e7bf60fbc6715427678344b81b21c8b3cbe8bbd51d392169c0413
3
+ metadata.gz: 5a663a7911df321fea42c56973ea1d194c678e3fa54c7856decaec28df550619
4
+ data.tar.gz: ada57aa2f5292246c16498218e869be155532dcbd9ab72544abbd06b5d9692b4
5
5
  SHA512:
6
- metadata.gz: 5c0ff6dd223e7999c825c2f63894ade93b687927b3473b277a65f382526cb811f8588726b530ec7d5ed4dda8fc33d2b849453b17edf562a6c8850e828d60b1f3
7
- data.tar.gz: fed3b1b88b3f3e2b8397e29d6cd191047e8239a63983e34fb5b34d61b3eee7f1c65906fbcd0ab6e32fb1f19f83a5badfeafc3f9ee6cc05c074e5c0877eae1b37
6
+ metadata.gz: 1dd696cd7f734a3fc9e041d61291f2d70796e00ceb069ed5697dca7d1f8f29856ebb6b0c17722e04b2a198fd89aa3e6b7edca635c0e3abb04f256195cc684fc1
7
+ data.tar.gz: c9f9728f4ebca40748944c096a87f2e2f2bf0fe4aad4ca5706757cbedb45bd24c2dc571124460893b96d4ecee9d3b2238c56fe2cddb0536f73be516e7759f99c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [0.4.0][] (2022-09-01)
2
+
3
+ ## Changed
4
+
5
+ - All values are strings by default regardless of how they arrived.
6
+ - The `config` key is now `unifig`.
7
+
8
+ ## Added
9
+
10
+ - Values can now be converted to built-in or custom types.
11
+ - Providers can now be configured.
12
+
1
13
  # [0.3.2][] (2022-08-01)
2
14
 
3
15
  ## Fixed
@@ -34,6 +46,7 @@
34
46
 
35
47
  Initial release.
36
48
 
49
+ [0.4.0]: https://github.com/AaronLasseigne/unifig/compare/v0.3.2...v0.4.0
37
50
  [0.3.2]: https://github.com/AaronLasseigne/unifig/compare/v0.3.1...v0.3.2
38
51
  [0.3.1]: https://github.com/AaronLasseigne/unifig/compare/v0.3.0...v0.3.1
39
52
  [0.3.0]: https://github.com/AaronLasseigne/unifig/compare/v0.2.0...v0.3.0
data/README.md CHANGED
@@ -18,13 +18,13 @@ If you want to use Unifig outside of a framework listed above you can manually a
18
18
  Add it to your Gemfile:
19
19
 
20
20
  ``` rb
21
- gem 'unifig', '~> 0.3.2'
21
+ gem 'unifig', '~> 0.4.0'
22
22
  ```
23
23
 
24
24
  Or install it manually:
25
25
 
26
26
  ``` sh
27
- $ gem install unifig --version '~> 0.3.2'
27
+ $ gem install unifig --version '~> 0.4.0'
28
28
  ```
29
29
 
30
30
  This project uses [Semantic Versioning][].
@@ -32,44 +32,146 @@ Check out [GitHub releases][] for a detailed list of changes.
32
32
 
33
33
  ## Usage
34
34
 
35
- ### Basics
35
+ ### Basic
36
36
 
37
- Unifig loads a [YAML configuration][] that instructs it on how to retrieve various external variables.
38
- These variable values come from providers.
37
+ Unifig works by loading a YAML configuration that instructs it on how to retrieve and treat external variables.
38
+ Variable values are retrieved from an ordered list of providers.
39
+
40
+ A provider is any source where you would retrieve variable values.
39
41
  Unifig comes with a `local` provider which reads values straight from the configuration file.
40
- Additional providers may be installed.
42
+ Additional providers may be installed:
43
+
44
+ | Provider | Gem |
45
+ | -------- | -------------- |
46
+ | local | Built-in |
47
+ | env | [unifig-env][] |
48
+
49
+ Providers are checked in order to find the variable values.
50
+ If a variable is not found in the first provider, it will be requested from the second provider and so on until it is found.
51
+
52
+ The YAML configuration should begin with a `unifig` key which lists the providers in the order you would like them checked:
53
+
54
+ ```yml
55
+ unifig:
56
+ providers: [local, env]
57
+ ```
41
58
 
42
- The most minimal configuration would be:
59
+ You can list a single provider or an ordered array to check.
60
+
61
+ Variables should be listed after the `unifig` key as their own keys.
62
+ Here's a mininal example YAML:
43
63
 
44
64
  ```yml
45
- config:
65
+ unifig:
46
66
  providers: local
47
67
 
68
+ HELLO: "world"
69
+ ```
70
+
71
+ When this YAML is loaded, Unifig will add two methods to it's core `Unifig` class.
72
+ The first, `Unifig.hello` will return `"hello"` as the value it found from the `local` provider.
73
+ The second method, `Unifig.hello?` allow you to check to see if the value was able to be retrived at all.
74
+
75
+ ```yml
76
+ > Unifig.hello?
77
+ # true
78
+ > Unifig.hello
79
+ # => "world"
80
+ ```
81
+
82
+ Each variable listed in the YAML configuration will receive its own pair of methods on `Unifig`.
83
+
84
+ Variables can be listed with the `local` value immediately following as seen above.
85
+ They can also be defined with no `local` value:
86
+
87
+ ```yml
88
+ HELLO:
89
+ ```
90
+
91
+ Or they can be declared with the verbose syntax:
92
+
93
+ ```yaml
48
94
  HELLO:
49
95
  value: "world"
50
96
  ```
51
97
 
52
- Given that configuration, Unifig will attach two new methods to the `Unifig` class.
53
- From in your code you can call `Unifig.hello` to get the value of the variable and `Unifig.hello?` to see if the value was retrieved (for optional values).
98
+ The verbose syntax is useful when additional configration is need as seen in the [Advanced](#advanced) section.
99
+
100
+ Loading a YAML configuration can be accomplished using the `Unifig::Init` class.
101
+ From `Unifig::Init` you can load the YAML as a string with `.load` or as a file with `.load_file`.
102
+
103
+ *NOTE: If you're using one of the framework gems, loading may be handled for you.*
104
+
105
+ Loading from a string:
106
+
107
+ ```rb
108
+ Unifig::Init.load(<<~YML)
109
+ unifig:
110
+ providers: local
111
+
112
+ HELLO: "world"
113
+ YML
114
+ ```
115
+
116
+ Loading from a file:
117
+
118
+ ```rb
119
+ Unifig::Init.load_file('unifig.yml')
120
+ ```
121
+
122
+ ### Advanced
123
+
124
+ #### Environments
125
+
126
+ Different working environments may require different setups.
127
+ This can be accomplished in the `unifig` key or within variable keys with the `envs` key.
54
128
 
55
- Unifig also allows you to override the overall configuration or the individual configuration by using environments.
56
- Here is an example where the `production` environment only pull from the `env` provider and in the `test` environment `Unifig.hello` will return `dlrow`:
129
+ Assuming two environments, `developement` and `production`, let's say we want to use different providers for each.
130
+ Whatever we set at the top level will operate as the default.
131
+ From there we can use the `envs` key to override that behavior:
57
132
 
58
133
  ```yml
59
- config:
60
- providers: [local, env]
134
+ unifig:
135
+ providers: local
136
+ envs:
137
+ production:
138
+ providers: env
139
+
140
+ HELLO: "world"
141
+ ```
142
+
143
+ In the `development` environment we'll check the `local` provider but in `production` we'll use `env`.
144
+ This will result with `Unifig.hello` returning `"world"` in `development` and whatever the value of the `HELLO` environment variable is in `production`.
145
+ To select an environment, add it to `Unifig::Init.load` or `Unifig::Init.load_file`:
146
+
147
+ ```rb
148
+ Unifig::Init.load(<<~YML, env: :development)
149
+ unifig:
150
+ providers: local
61
151
  envs:
62
152
  production:
63
153
  providers: env
64
154
 
155
+ HELLO: "world"
156
+ YML
157
+ ```
158
+
159
+ *NOTE: If you're using one of the framework gems, environments may be loaded automatically depending on the framework.*
160
+
161
+ In addition to changing `unifig`, `envs` may be used inside variables.
162
+ Any variable configuration may be overridden using `envs`:
163
+
164
+ ```yml
65
165
  HELLO:
66
166
  value: "world"
67
167
  envs:
68
- test:
69
- value: "dlrow"
168
+ production:
169
+ value: "universe"
70
170
  ```
71
171
 
72
- ### Variable Substitutions
172
+ Here we've set the `local` provider value to `"world"` for all environments with an override setting it to `"universe"` in the `production` environment.
173
+
174
+ #### Variable Substitutions
73
175
 
74
176
  Variables can be used in other variables with `${VAR}`.
75
177
 
@@ -80,86 +182,71 @@ SERVICE_USERNAME: "${USER}_at_service"
80
182
 
81
183
  Order of the variables does not matter but cyclical dependencies will throw an error.
82
184
 
83
- ### Loading
185
+ #### Type Conversion
84
186
 
85
- Loading a configuration is handled through the `Unifig::Init` class.
86
- From `Unifig::Init` you can load the YAML as a string with `.load` or a file with `.load_file`.
187
+ By default, all values are converted to strings.
188
+ If you want the value to be something other than a string you can assign a specific conversion.
189
+ Unifig comes with some basic built-in conversions or you can convert to any class available.
87
190
 
88
- All variables are assumed to be required (not `nil` or a blank string).
89
- Variables can be made optional by using the `:optional` setting.
90
- If there is a required variable without a value, Unifig will throw an error when loading.
91
-
92
- ```rb
93
- Unifig::Init.load(<<~YAML, env: :production)
94
- config:
95
- providers: local
96
-
97
- HOST:
98
- value: github.com
99
- envs:
100
- development:
101
- value: localhost
102
- PORT:
103
- optional: true
104
- envs:
105
- development:
106
- value: 8080
107
- YAML
191
+ In order to convert a value you can provide a type to `convert`:
108
192
 
109
- > Unifig.host?
110
- # true
111
- > Unifig.host
112
- # => "localhost"
113
- > Unifig.port?
114
- # true
115
- > Unifig.port
116
- # => 8080
193
+ ```yml
194
+ THREADS:
195
+ value: 5
196
+ convert: integer
117
197
  ```
118
198
 
119
- If we replaced `:development` with `:production` inside the `load` call we'd get:
199
+ Conversion works regardless of the provider of the value.
120
200
 
121
- ```rb
122
- > Unifig.host?
123
- # true
124
- > Unifig.host
125
- # => "github.com"
126
- > Unifig.port?
127
- # false
128
- > Unifig.port
129
- # => nil
130
- ```
201
+ ##### Built-in
131
202
 
132
- You can load from a configuration file by using `load_file`.
203
+ There are a number of built-in types available.
204
+ Basic types that have no additional options include `string` (the default), `symbol`, `float`, and `decimal` (i.e. `BigDecimal`).
133
205
 
134
- ```rb
135
- Unifig::Init.load_file('unifig.yml', env: :production)
136
- ```
206
+ The `integer` type assumes base 10.
207
+ This can be overridden by providing a `base` option:
137
208
 
138
- [API Documentation][]
209
+ ```yml
210
+ BINARY_INPUT:
211
+ convert:
212
+ type: integer
213
+ base: 2
214
+ ```
139
215
 
140
- ### YAML Configuration
216
+ Unifig also provides `date`, `date_time`, and `time` all of which use `parse` by default.
217
+ Any of them can be provided with a `format` option if you want to specify the format of the input.
218
+ The `format` option uses `strptime` for the conversion.
219
+ You can find all valid formats by looking at the standard Ruby documentation.
141
220
 
142
- The configuration YAML must contain a `config` key.
143
- A list of one or more providers must be given.
144
- They are are checked in order to find the value for each variable.
145
- Variables are then listed by name.
221
+ ```yml
222
+ STARTS_ON:
223
+ convert:
224
+ type: date
225
+ format: %Y-%m-%d
226
+ ```
146
227
 
147
- The first level of configurations apply to all environments.
148
- These can be overridden by setting the `envs` key and a key with the name of then environment and then redefining any settings.
149
- This is the case for the overall configuration as well as any variable configurations.
228
+ ##### Custom
150
229
 
151
- ### Providers
230
+ Any available class can be used as a converter by providing the class name as the `converter`.
231
+ By default, `new` will be called on the class with the value passed in.
232
+ To use a different method provide it via the `method` option.
152
233
 
153
- | Provider | Gem |
154
- | -------- | -------------- |
155
- | Local | Built-in |
156
- | ENV | [unifig-env][] |
234
+ ```yml
235
+ IP_ADDRESS:
236
+ convert: IPAddr
237
+ ENCODING:
238
+ convert:
239
+ type: Encoding
240
+ method: find
241
+ ```
157
242
 
158
- ### Unifig::Vars
243
+ #### Unifig::Vars
159
244
 
160
245
  After loading the configuration you can use `Unifig::Vars` to check on what happened.
161
246
  It will return a list of `Unifig::Var`s with information such as which provider supplied the value.
162
247
 
248
+ [API Documentation][]
249
+
163
250
  ## Contributing
164
251
 
165
252
  If you want to contribute to Unifig, please read [our contribution guidelines][].
@@ -173,7 +260,6 @@ Unifig is licensed under [the MIT License][].
173
260
  [unifig-rails]: https://github.com/AaronLasseigne/unifig-rails
174
261
  [Semantic Versioning]: http://semver.org/spec/v2.0.0.html
175
262
  [GitHub releases]: https://github.com/AaronLasseigne/unifig/releases
176
- [YAML configuration]: #yaml-configuration
177
263
  [API Documentation]: http://rubydoc.info/github/AaronLasseigne/unifig
178
264
  [our contribution guidelines]: CONTRIBUTING.md
179
265
  [unifig-env]: https://github.com/AaronLasseigne/unifig-env
data/lib/unifig/config.rb CHANGED
@@ -14,7 +14,22 @@ module Unifig
14
14
  end
15
15
 
16
16
  def providers
17
- @providers ||= Array(@env_config[:providers]).map(&:to_sym).freeze
17
+ return @providers if defined?(@providers)
18
+
19
+ providers =
20
+ if @env_config[:providers].is_a?(Hash)
21
+ @env_config.dig(:providers, :list)
22
+ else
23
+ @env_config[:providers]
24
+ end
25
+
26
+ @providers = Array(providers).map(&:to_sym).freeze
27
+ end
28
+
29
+ def provider_config(name)
30
+ return {} unless @env_config[:providers].is_a?(Hash)
31
+
32
+ @env_config.dig(:providers, :config, name) || {}
18
33
  end
19
34
  end
20
35
  end
data/lib/unifig/errors.rb CHANGED
@@ -24,4 +24,7 @@ module Unifig
24
24
 
25
25
  # Raised if a substitution does not exist.
26
26
  MissingSubstitutionError = Class.new(Error)
27
+
28
+ # Raised if a type does not exist.
29
+ InvalidTypeError = Class.new(Error)
27
30
  end
data/lib/unifig/init.rb CHANGED
@@ -10,7 +10,7 @@ module Unifig
10
10
  #
11
11
  # @example
12
12
  # Unifig::Init.load(<<~YML, env: :development)
13
- # config:
13
+ # unifig:
14
14
  # envs:
15
15
  # development:
16
16
  # providers: local
@@ -54,14 +54,14 @@ module Unifig
54
54
  # @raise (see Unifig::Var.load!)
55
55
  # @raise (see .complete_substitutions!)
56
56
  def exec!(yml, env: nil)
57
- config = Config.new(yml.delete(:config), env: env)
57
+ config = Config.new(yml.delete(:unifig), env: env)
58
58
 
59
59
  providers = Providers.list(config.providers)
60
60
  return if providers.empty?
61
61
 
62
62
  Vars.load!(yml, env)
63
63
 
64
- fetch_from_providers!(providers)
64
+ fetch_from_providers!(providers, config)
65
65
 
66
66
  check_required_vars
67
67
 
@@ -72,10 +72,10 @@ module Unifig
72
72
  attach_missing_optional_methods!(missing_vars)
73
73
  end
74
74
 
75
- def fetch_from_providers!(providers)
75
+ def fetch_from_providers!(providers, config)
76
76
  providers.each do |provider|
77
77
  remaining_vars = Vars.list.filter_map { |var| var.name if var.value.nil? }
78
- result = provider.retrieve(remaining_vars)
78
+ result = provider.retrieve(remaining_vars, config.provider_config(provider.name))
79
79
 
80
80
  Vars.write_results!(result, provider.name)
81
81
  end
@@ -92,6 +92,7 @@ module Unifig
92
92
 
93
93
  # @raise [CyclicalSubstitutionError] - Subtitutions resulted in a cyclical dependency.
94
94
  # @raise [MissingSubstitutionError] - A substitution does not exist.
95
+ # @raise (see Unifig::Var#value=)
95
96
  def complete_substitutions!
96
97
  Vars.tsort.each do |name|
97
98
  var = Vars[name]
@@ -8,7 +8,7 @@ module Unifig
8
8
  :local
9
9
  end
10
10
 
11
- def self.retrieve(var_names)
11
+ def self.retrieve(var_names, _config)
12
12
  var_names.to_h do |name|
13
13
  [name, Vars[name].local_value]
14
14
  end
data/lib/unifig/var.rb CHANGED
@@ -1,8 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bigdecimal'
4
+ require 'date'
5
+ require 'time'
6
+
3
7
  module Unifig
4
8
  # A variable created after loading a configuration.
5
9
  class Var
10
+ DEFAULT_INTEGER_BASE = 10
11
+ private_constant :DEFAULT_INTEGER_BASE
12
+
6
13
  # @private
7
14
  def initialize(name, config, env)
8
15
  @name = name
@@ -31,9 +38,10 @@ module Unifig
31
38
  attr_reader :value
32
39
 
33
40
  # @private
41
+ # @raise (see #convert)
34
42
  def value=(obj)
35
- value = blank?(obj) ? nil : obj
36
- value = value.dup.freeze unless value.frozen?
43
+ value = blank?(obj) ? nil : obj.dup
44
+ value = convert(value).freeze unless value.nil?
37
45
  @value = value
38
46
  end
39
47
 
@@ -46,7 +54,7 @@ module Unifig
46
54
 
47
55
  # @private
48
56
  def local_value
49
- @local_value ||= env_config(:value) || @config[:value]
57
+ @local_value ||= config(:value)
50
58
  end
51
59
 
52
60
  # Returns whether or not this is a required variable.
@@ -55,7 +63,7 @@ module Unifig
55
63
  def required?
56
64
  return @required if defined?(@required)
57
65
 
58
- optional = env_config(:optional)
66
+ optional = config(:optional)
59
67
  optional = @config[:optional] if optional.nil?
60
68
  optional = false if optional.nil?
61
69
  @required = !optional
@@ -63,8 +71,81 @@ module Unifig
63
71
 
64
72
  private
65
73
 
66
- def env_config(key)
67
- @config.dig(:envs, @env, key)
74
+ # @raise [InvalidTypeError] - A type does not exist.
75
+ def convert(value)
76
+ convert = config(:convert)
77
+ type, options =
78
+ if convert.is_a?(Hash)
79
+ [convert[:type], convert.slice(*(convert.keys - [:type]))]
80
+ else
81
+ [convert, nil]
82
+ end
83
+ type = 'string' if type.nil?
84
+ options = {} if options.nil?
85
+
86
+ if built_in?(type)
87
+ convert_built_in(type, options, value)
88
+ else
89
+ convert_custom_type(type, options, value)
90
+ end
91
+ end
92
+
93
+ def convert_built_in(type, options, value) # rubocop:disable Metrics
94
+ case type
95
+ when 'date'
96
+ time_convert(Date, options, value)
97
+ when 'date_time'
98
+ time_convert(DateTime, options, value)
99
+ when 'decimal'
100
+ BigDecimal(value)
101
+ when 'integer'
102
+ base = options.fetch(:base, DEFAULT_INTEGER_BASE)
103
+ Integer(value, base)
104
+ when 'float'
105
+ Float(value)
106
+ when 'string'
107
+ String(value)
108
+ when 'symbol'
109
+ String(value).to_sym
110
+ when 'time'
111
+ time_convert(Time, options, value)
112
+ else
113
+ raise InvalidTypeError, %(unknown built-in type "#{type}")
114
+ end
115
+ end
116
+
117
+ def convert_custom_type(type, options, value)
118
+ klass =
119
+ begin
120
+ Kernel.const_get(type)
121
+ rescue NameError
122
+ raise InvalidTypeError, %(unknown custom type "#{type}")
123
+ end
124
+
125
+ klass.public_send(options.fetch(:method, :new), value)
126
+ end
127
+
128
+ def integer(value)
129
+ Integer(value, DEFAULT_INTEGER_BASE)
130
+ rescue ArgumentError
131
+ nil
132
+ end
133
+
134
+ def built_in?(type)
135
+ /\A\p{Ll}/.match?(type) # \p{Ll} = unicode lowercase
136
+ end
137
+
138
+ def time_convert(klass, options, value)
139
+ return klass.strptime(value, options[:format]) if options[:format]
140
+
141
+ klass.parse(value)
142
+ end
143
+
144
+ def config(key)
145
+ env_conf = @config.dig(:envs, @env, key)
146
+ return env_conf unless env_conf.nil?
147
+
148
+ @config[key]
68
149
  end
69
150
 
70
151
  def blank?(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unifig
4
- VERSION = '0.3.2'
4
+ VERSION = '0.4.0'
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -41,9 +41,11 @@ RSpec.configure do |config|
41
41
  :forty_two
42
42
  end
43
43
 
44
- def self.retrieve(var_names)
44
+ def self.retrieve(var_names, config)
45
+ num = config.fetch(:num, 42).to_s.freeze
46
+
45
47
  var_names.to_h do |var_name|
46
- [var_name, '42'.freeze]
48
+ [var_name, num]
47
49
  end
48
50
  end
49
51
  end
@@ -24,7 +24,7 @@ RSpec.describe Unifig::Config do
24
24
  end
25
25
 
26
26
  describe '#providers' do
27
- it 'returns a list of providers for the selected env' do
27
+ it 'returns a list of providers' do
28
28
  expect(config.providers).to eql %i[local]
29
29
  end
30
30
 
@@ -35,5 +35,47 @@ RSpec.describe Unifig::Config do
35
35
  expect(config.providers).to eql %i[local forty_two]
36
36
  end
37
37
  end
38
+
39
+ context 'with a :list' do
40
+ let(:config_hash) do
41
+ {
42
+ providers: {
43
+ list: 'local'
44
+ }
45
+ }
46
+ end
47
+
48
+ it 'returns a list of providers' do
49
+ expect(config.providers).to eql %i[local]
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#provider_config' do
55
+ it 'returns an empty config if none is provided' do
56
+ expect(config.provider_config(:local)).to eql({})
57
+ end
58
+
59
+ context 'with a configuration' do
60
+ let(:local_config) do
61
+ {
62
+ here: true
63
+ }
64
+ end
65
+ let(:config_hash) do
66
+ {
67
+ providers: {
68
+ list: 'local',
69
+ config: {
70
+ local: local_config
71
+ }
72
+ }
73
+ }
74
+ end
75
+
76
+ it 'returns the config info' do
77
+ expect(config.provider_config(:local)).to eql(local_config)
78
+ end
79
+ end
38
80
  end
39
81
  end
@@ -20,7 +20,7 @@ RSpec.shared_examples 'basic load tests' do
20
20
  context 'with a valid config' do
21
21
  let(:str) do
22
22
  <<~YML
23
- config:
23
+ unifig:
24
24
  providers: local
25
25
 
26
26
  ONE:
@@ -32,7 +32,7 @@ RSpec.shared_examples 'basic load tests' do
32
32
  subject
33
33
 
34
34
  expect(Unifig).to respond_to(:one)
35
- expect(Unifig.one).to be 1
35
+ expect(Unifig.one).to eql '1'
36
36
  end
37
37
  end
38
38
  end
@@ -48,7 +48,7 @@ RSpec.describe Unifig::Init do
48
48
  context 'from multiple providers' do
49
49
  let(:str) do
50
50
  <<~YML
51
- config:
51
+ unifig:
52
52
  providers: [local, forty_two]
53
53
 
54
54
  ONE:
@@ -61,7 +61,31 @@ RSpec.describe Unifig::Init do
61
61
  load
62
62
 
63
63
  expect(Unifig.one).to eql '42'
64
- expect(Unifig.two).to be 2
64
+ expect(Unifig.two).to eql '2'
65
+ end
66
+ end
67
+
68
+ context 'with provider configuration' do
69
+ let(:str) do
70
+ <<~YML
71
+ unifig:
72
+ providers:
73
+ list: [local, forty_two]
74
+ config:
75
+ forty_two:
76
+ num: 24
77
+
78
+ ONE:
79
+ TWO:
80
+ value: 2
81
+ YML
82
+ end
83
+
84
+ it 'returns the values from the providers in order' do
85
+ load
86
+
87
+ expect(Unifig.one).to eql '24'
88
+ expect(Unifig.two).to eql '2'
65
89
  end
66
90
  end
67
91
 
@@ -71,7 +95,7 @@ RSpec.describe Unifig::Init do
71
95
  context 'that is available' do
72
96
  let(:str) do
73
97
  <<~YML
74
- config:
98
+ unifig:
75
99
  providers: local
76
100
 
77
101
  ONE:
@@ -81,7 +105,7 @@ RSpec.describe Unifig::Init do
81
105
  end
82
106
 
83
107
  it 'loads the var' do
84
- expect(Unifig.one).to be 1
108
+ expect(Unifig.one).to eql '1'
85
109
  end
86
110
 
87
111
  it 'sets the predicate to true' do
@@ -92,7 +116,7 @@ RSpec.describe Unifig::Init do
92
116
  context 'that is not available' do
93
117
  let(:str) do
94
118
  <<~YML
95
- config:
119
+ unifig:
96
120
  providers: local
97
121
 
98
122
  ONE:
@@ -112,7 +136,7 @@ RSpec.describe Unifig::Init do
112
136
  context 'that is not blank' do
113
137
  let(:str) do
114
138
  <<~YML
115
- config:
139
+ unifig:
116
140
  providers: local
117
141
 
118
142
  ONE:
@@ -137,7 +161,7 @@ RSpec.describe Unifig::Init do
137
161
 
138
162
  let(:str) do
139
163
  <<~YML
140
- config:
164
+ unifig:
141
165
  providers: local
142
166
 
143
167
  ONE:
@@ -146,7 +170,7 @@ RSpec.describe Unifig::Init do
146
170
  end
147
171
 
148
172
  it 'loads the var' do
149
- expect(Unifig.one).to be 1
173
+ expect(Unifig.one).to eql '1'
150
174
  end
151
175
 
152
176
  it 'sets the predicate to true' do
@@ -157,7 +181,7 @@ RSpec.describe Unifig::Init do
157
181
  context 'that is not available' do
158
182
  let(:str) do
159
183
  <<~YML
160
- config:
184
+ unifig:
161
185
  providers: local
162
186
 
163
187
  ONE:
@@ -173,7 +197,7 @@ RSpec.describe Unifig::Init do
173
197
  context 'that is blank' do
174
198
  let(:str) do
175
199
  <<~YML
176
- config:
200
+ unifig:
177
201
  providers: local
178
202
 
179
203
  ONE:
@@ -190,7 +214,7 @@ RSpec.describe Unifig::Init do
190
214
  context 'with substitutions' do
191
215
  let(:str) do
192
216
  <<~YML
193
- config:
217
+ unifig:
194
218
  providers: local
195
219
 
196
220
  NAME:
@@ -209,7 +233,7 @@ RSpec.describe Unifig::Init do
209
233
  context 'when they are out of order' do
210
234
  let(:str) do
211
235
  <<~YML
212
- config:
236
+ unifig:
213
237
  providers: local
214
238
 
215
239
  GREETING:
@@ -229,7 +253,7 @@ RSpec.describe Unifig::Init do
229
253
  context 'when they chain' do
230
254
  let(:str) do
231
255
  <<~YML
232
- config:
256
+ unifig:
233
257
  providers: local
234
258
 
235
259
  INTRO:
@@ -251,7 +275,7 @@ RSpec.describe Unifig::Init do
251
275
  context 'when they cause a cycle' do
252
276
  let(:str) do
253
277
  <<~YML
254
- config:
278
+ unifig:
255
279
  providers: local
256
280
 
257
281
  A:
@@ -271,7 +295,7 @@ RSpec.describe Unifig::Init do
271
295
  context 'when they do not exist' do
272
296
  let(:str) do
273
297
  <<~YML
274
- config:
298
+ unifig:
275
299
  providers: local
276
300
 
277
301
  A:
@@ -1,3 +1,5 @@
1
+ require 'ipaddr'
2
+
1
3
  RSpec.describe Unifig::Var do
2
4
  subject(:var) { described_class.new(name, config, env) }
3
5
 
@@ -32,6 +34,263 @@ RSpec.describe Unifig::Var do
32
34
  expect(var.value).to eql 'a'
33
35
  expect(var.value).to be_frozen
34
36
  end
37
+
38
+ context 'without a type' do
39
+ it 'defaults to string' do
40
+ var.value = 1
41
+
42
+ expect(var.value).to eql '1'
43
+ end
44
+ end
45
+
46
+ context 'with a type of' do
47
+ context 'string' do
48
+ before do
49
+ config.merge!(convert: 'string')
50
+ end
51
+
52
+ it 'converts to a String' do
53
+ var.value = 1
54
+
55
+ expect(var.value).to eql '1'
56
+ end
57
+ end
58
+
59
+ context 'integer' do
60
+ before do
61
+ config.merge!(convert: 'integer')
62
+ end
63
+
64
+ it 'converts to an Integer' do
65
+ var.value = '01'
66
+
67
+ expect(var.value).to be 1
68
+ end
69
+
70
+ context 'with option' do
71
+ context 'base' do
72
+ before do
73
+ config.merge!(convert: { type: 'integer', base: 2 })
74
+ end
75
+
76
+ it 'converts it using the specified base' do
77
+ var.value = '11'
78
+
79
+ expect(var.value).to be 3
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'float' do
86
+ before do
87
+ config.merge!(convert: 'float')
88
+ end
89
+
90
+ it 'converts to a Float' do
91
+ var.value = '01.1'
92
+
93
+ expect(var.value).to be 1.1
94
+ end
95
+ end
96
+
97
+ context 'decimal' do
98
+ before do
99
+ config.merge!(convert: 'decimal')
100
+ end
101
+
102
+ it 'converts to a BigDecimal' do
103
+ var.value = '01.1'
104
+
105
+ expect(var.value).to eql BigDecimal('01.1')
106
+ end
107
+ end
108
+
109
+ context 'symbol' do
110
+ before do
111
+ config.merge!(convert: 'symbol')
112
+ end
113
+
114
+ it 'converts to a Symbol' do
115
+ var.value = 'one'
116
+
117
+ expect(var.value).to be :one
118
+ end
119
+ end
120
+
121
+ context 'date' do
122
+ before do
123
+ config.merge!(convert: 'date')
124
+ end
125
+
126
+ it 'converts to a Date' do
127
+ var.value = '2022-01-02'
128
+
129
+ value = var.value
130
+ expect(value).to be_an_instance_of Date
131
+ expect(value.year).to be 2022
132
+ expect(value.month).to be 1
133
+ expect(value.day).to be 2
134
+ end
135
+
136
+ context 'with option' do
137
+ context 'format' do
138
+ before do
139
+ config.merge!(convert: { type: 'date', format: '%Y-%m-%d' })
140
+ end
141
+
142
+ it 'converts it using the specified format' do
143
+ var.value = '2022-01-02'
144
+
145
+ value = var.value
146
+ expect(value).to be_an_instance_of Date
147
+ expect(value.year).to be 2022
148
+ expect(value.month).to be 1
149
+ expect(value.day).to be 2
150
+ end
151
+
152
+ it 'throws an error on the wrong input' do
153
+ expect { var.value = '2022-01' }.to raise_error Date::Error
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'date_time' do
160
+ before do
161
+ config.merge!(convert: 'date_time')
162
+ end
163
+
164
+ it 'converts to a DateTime' do
165
+ var.value = '2022-01-02T03:04:05'
166
+
167
+ value = var.value
168
+ expect(value).to be_an_instance_of DateTime
169
+ expect(value.year).to be 2022
170
+ expect(value.month).to be 1
171
+ expect(value.day).to be 2
172
+ expect(value.hour).to be 3
173
+ expect(value.minute).to be 4
174
+ expect(value.second).to be 5
175
+ end
176
+
177
+ context 'with option' do
178
+ context 'format' do
179
+ before do
180
+ config.merge!(convert: { type: 'date_time', format: '%Y-%m-%dT%H:%M:%S' })
181
+ end
182
+
183
+ it 'converts it using the specified format' do
184
+ var.value = '2022-01-02T03:04:05'
185
+
186
+ value = var.value
187
+ expect(value).to be_an_instance_of DateTime
188
+ expect(value.year).to be 2022
189
+ expect(value.month).to be 1
190
+ expect(value.day).to be 2
191
+ expect(value.hour).to be 3
192
+ expect(value.minute).to be 4
193
+ expect(value.second).to be 5
194
+ end
195
+
196
+ it 'throws an error on the wrong input' do
197
+ expect { var.value = '2022-01' }.to raise_error DateTime::Error
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ context 'time' do
204
+ before do
205
+ config.merge!(convert: 'time')
206
+ end
207
+
208
+ it 'converts to a Time' do
209
+ var.value = '2022-01-02T03:04:05'
210
+
211
+ value = var.value
212
+ expect(value).to be_an_instance_of Time
213
+ expect(value.year).to be 2022
214
+ expect(value.month).to be 1
215
+ expect(value.day).to be 2
216
+ expect(value.hour).to be 3
217
+ expect(value.min).to be 4
218
+ expect(value.sec).to be 5
219
+ end
220
+
221
+ context 'with option' do
222
+ context 'format' do
223
+ before do
224
+ config.merge!(convert: { type: 'time', format: '%Y-%m-%dT%H:%M:%S' })
225
+ end
226
+
227
+ it 'converts it using the specified format' do
228
+ var.value = '2022-01-02T03:04:05'
229
+
230
+ value = var.value
231
+ expect(value).to be_an_instance_of Time
232
+ expect(value.year).to be 2022
233
+ expect(value.month).to be 1
234
+ expect(value.day).to be 2
235
+ expect(value.hour).to be 3
236
+ expect(value.min).to be 4
237
+ expect(value.sec).to be 5
238
+ end
239
+
240
+ it 'throws an error on the wrong input' do
241
+ expect { var.value = '2022-01' }.to raise_error ArgumentError
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ context 'invalid' do
248
+ before do
249
+ config.merge!(convert: 'invalid')
250
+ end
251
+
252
+ it 'throws an error' do
253
+ expect { var.value = 1 }.to raise_error Unifig::InvalidTypeError
254
+ end
255
+ end
256
+
257
+ context 'custom' do
258
+ before do
259
+ config.merge!(convert: 'IPAddr')
260
+ end
261
+
262
+ it 'converts to the class provided using .new' do
263
+ var.value = '127.0.0.1'
264
+
265
+ value = var.value
266
+ expect(value).to be_an_instance_of IPAddr
267
+ expect(value).to be_ipv4
268
+ expect(value.to_s).to eql '127.0.0.1'
269
+ end
270
+
271
+ context 'with a custom method' do
272
+ before do
273
+ config.merge!(convert: { type: 'Encoding', method: 'find' })
274
+ end
275
+
276
+ it 'uses the method provided' do
277
+ var.value = 'ascii'
278
+
279
+ expect(var.value).to be Encoding::US_ASCII
280
+ end
281
+ end
282
+
283
+ context 'with an invalid class' do
284
+ before do
285
+ config.merge!(convert: 'Invalid')
286
+ end
287
+
288
+ it 'throws an error' do
289
+ expect { var.value = 'invalid' }.to raise_error Unifig::InvalidTypeError
290
+ end
291
+ end
292
+ end
293
+ end
35
294
  end
36
295
 
37
296
  describe '#local_value' do
@@ -24,7 +24,7 @@ RSpec.describe Unifig::Vars do
24
24
  expect(vars.size).to be 2
25
25
  vars.each.with_index(1) do |var, value|
26
26
  expect(var).to be_an_instance_of Unifig::Var
27
- expect(var.value).to be value
27
+ expect(var.value).to eql String(value)
28
28
  expect(var.provider).to be :local
29
29
  end
30
30
  end
@@ -35,7 +35,7 @@ RSpec.describe Unifig::Vars do
35
35
  var = described_class[:one]
36
36
 
37
37
  expect(var).to be_an_instance_of Unifig::Var
38
- expect(var.value).to be 1
38
+ expect(var.value).to eql '1'
39
39
  end
40
40
  end
41
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unifig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Lasseigne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-01 00:00:00.000000000 Z
11
+ date: 2022-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tsort