unifig 0.3.2 → 0.4.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.
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