unifig 0.2.0 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +32 -1
- data/README.md +35 -8
- data/lib/unifig/config.rb +3 -0
- data/lib/unifig/errors.rb +12 -3
- data/lib/unifig/init.rb +106 -100
- data/lib/unifig/providers/local.rb +2 -9
- data/lib/unifig/providers.rb +14 -4
- data/lib/unifig/var.rb +42 -5
- data/lib/unifig/vars.rb +79 -0
- data/lib/unifig/version.rb +1 -1
- data/lib/unifig.rb +4 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/unifig/config_spec.rb +10 -0
- data/spec/unifig/init_spec.rb +102 -17
- data/spec/unifig/providers_spec.rb +8 -0
- data/spec/unifig/var_spec.rb +21 -0
- data/spec/unifig/vars_spec.rb +41 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89378b794de9cdd9511c52a145c4458317713e357634690201b3af379fd19e23
|
4
|
+
data.tar.gz: 51642a4f0f6e7bf60fbc6715427678344b81b21c8b3cbe8bbd51d392169c0413
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c0ff6dd223e7999c825c2f63894ade93b687927b3473b277a65f382526cb811f8588726b530ec7d5ed4dda8fc33d2b849453b17edf562a6c8850e828d60b1f3
|
7
|
+
data.tar.gz: fed3b1b88b3f3e2b8397e29d6cd191047e8239a63983e34fb5b34d61b3eee7f1c65906fbcd0ab6e32fb1f19f83a5badfeafc3f9ee6cc05c074e5c0877eae1b37
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,32 @@
|
|
1
|
-
# [0.2
|
1
|
+
# [0.3.2][] (2022-08-01)
|
2
|
+
|
3
|
+
## Fixed
|
4
|
+
|
5
|
+
- Provider in Unifig::Var returned incorrectly
|
6
|
+
- Fix early calls to Unifig::Vars.list
|
7
|
+
|
8
|
+
# [0.3.1][] (2022-07-31)
|
9
|
+
|
10
|
+
## Fixed
|
11
|
+
|
12
|
+
- Frozen strings returned from providers caused an error.
|
13
|
+
|
14
|
+
# [0.3.0][] (2022-07-31)
|
15
|
+
|
16
|
+
## Changed
|
17
|
+
|
18
|
+
- Renamed some errors so they all end in `Error`.
|
19
|
+
|
20
|
+
## Added
|
21
|
+
|
22
|
+
- Raise an error if a two or more variable names result in the same method name.
|
23
|
+
- Any `nil` values are now handled here so providers don't have to worry about it.
|
24
|
+
- Variable substituion
|
25
|
+
- `Unifig::Vars` will now contain information about the loaded variables.
|
26
|
+
|
27
|
+
# [0.2.0][] (2022-07-24)
|
28
|
+
|
29
|
+
## Changed
|
2
30
|
|
3
31
|
- An environment is no longer required.
|
4
32
|
|
@@ -6,5 +34,8 @@
|
|
6
34
|
|
7
35
|
Initial release.
|
8
36
|
|
37
|
+
[0.3.2]: https://github.com/AaronLasseigne/unifig/compare/v0.3.1...v0.3.2
|
38
|
+
[0.3.1]: https://github.com/AaronLasseigne/unifig/compare/v0.3.0...v0.3.1
|
39
|
+
[0.3.0]: https://github.com/AaronLasseigne/unifig/compare/v0.2.0...v0.3.0
|
9
40
|
[0.2.0]: https://github.com/AaronLasseigne/unifig/compare/v0.1.0...v0.2.0
|
10
41
|
[0.1.0]: https://github.com/AaronLasseigne/unifig/compare/v0.0.0...v0.1.0
|
data/README.md
CHANGED
@@ -7,16 +7,24 @@ Unifig is a pluggable system for loading external variables from one or more pro
|
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
10
|
+
If you are using a framework you should install the associated gem:
|
11
|
+
|
12
|
+
| Framework | Gem |
|
13
|
+
| --------- | ---------------- |
|
14
|
+
| Rails | [unifig-rails][] |
|
15
|
+
|
16
|
+
If you want to use Unifig outside of a framework listed above you can manually add it to your project.
|
17
|
+
|
10
18
|
Add it to your Gemfile:
|
11
19
|
|
12
20
|
``` rb
|
13
|
-
gem 'unifig', '~> 0.2
|
21
|
+
gem 'unifig', '~> 0.3.2'
|
14
22
|
```
|
15
23
|
|
16
24
|
Or install it manually:
|
17
25
|
|
18
26
|
``` sh
|
19
|
-
$ gem install unifig --version '~> 0.2
|
27
|
+
$ gem install unifig --version '~> 0.3.2'
|
20
28
|
```
|
21
29
|
|
22
30
|
This project uses [Semantic Versioning][].
|
@@ -61,6 +69,17 @@ HELLO:
|
|
61
69
|
value: "dlrow"
|
62
70
|
```
|
63
71
|
|
72
|
+
### Variable Substitutions
|
73
|
+
|
74
|
+
Variables can be used in other variables with `${VAR}`.
|
75
|
+
|
76
|
+
```rb
|
77
|
+
USER:
|
78
|
+
SERVICE_USERNAME: "${USER}_at_service"
|
79
|
+
```
|
80
|
+
|
81
|
+
Order of the variables does not matter but cyclical dependencies will throw an error.
|
82
|
+
|
64
83
|
### Loading
|
65
84
|
|
66
85
|
Loading a configuration is handled through the `Unifig::Init` class.
|
@@ -70,7 +89,7 @@ All variables are assumed to be required (not `nil` or a blank string).
|
|
70
89
|
Variables can be made optional by using the `:optional` setting.
|
71
90
|
If there is a required variable without a value, Unifig will throw an error when loading.
|
72
91
|
|
73
|
-
```
|
92
|
+
```rb
|
74
93
|
Unifig::Init.load(<<~YAML, env: :production)
|
75
94
|
config:
|
76
95
|
providers: local
|
@@ -99,7 +118,7 @@ YAML
|
|
99
118
|
|
100
119
|
If we replaced `:development` with `:production` inside the `load` call we'd get:
|
101
120
|
|
102
|
-
```
|
121
|
+
```rb
|
103
122
|
> Unifig.host?
|
104
123
|
# true
|
105
124
|
> Unifig.host
|
@@ -112,7 +131,7 @@ If we replaced `:development` with `:production` inside the `load` call we'd get
|
|
112
131
|
|
113
132
|
You can load from a configuration file by using `load_file`.
|
114
133
|
|
115
|
-
```
|
134
|
+
```rb
|
116
135
|
Unifig::Init.load_file('unifig.yml', env: :production)
|
117
136
|
```
|
118
137
|
|
@@ -131,9 +150,15 @@ This is the case for the overall configuration as well as any variable configura
|
|
131
150
|
|
132
151
|
### Providers
|
133
152
|
|
134
|
-
| Provider | Gem
|
135
|
-
| -------- |
|
136
|
-
| Local | Built-in
|
153
|
+
| Provider | Gem |
|
154
|
+
| -------- | -------------- |
|
155
|
+
| Local | Built-in |
|
156
|
+
| ENV | [unifig-env][] |
|
157
|
+
|
158
|
+
### Unifig::Vars
|
159
|
+
|
160
|
+
After loading the configuration you can use `Unifig::Vars` to check on what happened.
|
161
|
+
It will return a list of `Unifig::Var`s with information such as which provider supplied the value.
|
137
162
|
|
138
163
|
## Contributing
|
139
164
|
|
@@ -145,10 +170,12 @@ A [complete list of contributors][] is available on GitHub.
|
|
145
170
|
Unifig is licensed under [the MIT License][].
|
146
171
|
|
147
172
|
[Unifig]: https://github.com/AaronLasseigne/unifig
|
173
|
+
[unifig-rails]: https://github.com/AaronLasseigne/unifig-rails
|
148
174
|
[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
|
149
175
|
[GitHub releases]: https://github.com/AaronLasseigne/unifig/releases
|
150
176
|
[YAML configuration]: #yaml-configuration
|
151
177
|
[API Documentation]: http://rubydoc.info/github/AaronLasseigne/unifig
|
152
178
|
[our contribution guidelines]: CONTRIBUTING.md
|
179
|
+
[unifig-env]: https://github.com/AaronLasseigne/unifig-env
|
153
180
|
[complete list of contributors]: https://github.com/AaronLasseigne/unifig/graphs/contributors
|
154
181
|
[the MIT License]: LICENSE.txt
|
data/lib/unifig/config.rb
CHANGED
@@ -3,7 +3,10 @@
|
|
3
3
|
module Unifig
|
4
4
|
# @private
|
5
5
|
class Config
|
6
|
+
# @raise [MissingConfigError] - No config section was provided.
|
6
7
|
def initialize(config, env: nil)
|
8
|
+
raise MissingConfigError, 'no configuration provided' unless config
|
9
|
+
|
7
10
|
@env_config = config.slice(:providers)
|
8
11
|
@env = env
|
9
12
|
|
data/lib/unifig/errors.rb
CHANGED
@@ -8,11 +8,20 @@ module Unifig
|
|
8
8
|
YAMLSyntaxError = Class.new(Error)
|
9
9
|
|
10
10
|
# Raised if there is no config at the start of the YAML.
|
11
|
-
|
11
|
+
MissingConfigError = Class.new(Error)
|
12
12
|
|
13
13
|
# Raised if there is no provider that matches the one given in the config.
|
14
|
-
|
14
|
+
MissingProviderError = Class.new(Error)
|
15
15
|
|
16
16
|
# Raised if a required var is blank.
|
17
|
-
|
17
|
+
MissingRequiredError = Class.new(Error)
|
18
|
+
|
19
|
+
# Raised if a variable produces a duplicate method name.
|
20
|
+
DuplicateNameError = Class.new(Error)
|
21
|
+
|
22
|
+
# Raised if substitutions result in a cyclical dependency.
|
23
|
+
CyclicalSubstitutionError = Class.new(Error)
|
24
|
+
|
25
|
+
# Raised if a substitution does not exist.
|
26
|
+
MissingSubstitutionError = Class.new(Error)
|
18
27
|
end
|
data/lib/unifig/init.rb
CHANGED
@@ -4,128 +4,134 @@ require 'yaml'
|
|
4
4
|
|
5
5
|
module Unifig
|
6
6
|
# Initializes Unifig with methods based on YAML.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
7
|
+
module Init
|
8
|
+
class << self
|
9
|
+
# Loads a string of YAML to configure Unifig.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# Unifig::Init.load(<<~YML, env: :development)
|
13
|
+
# config:
|
14
|
+
# envs:
|
15
|
+
# development:
|
16
|
+
# providers: local
|
17
|
+
#
|
18
|
+
# FOO_BAR:
|
19
|
+
# value: "baz"
|
20
|
+
# YML
|
21
|
+
#
|
22
|
+
# @param str [String] A YAML config.
|
23
|
+
# @param env [Symbol] An environment name to load.
|
24
|
+
#
|
25
|
+
# @raise [YAMLSyntaxError] - Invalid YAML
|
26
|
+
# @raise (see .exec!)
|
27
|
+
def load(str, env: nil)
|
28
|
+
yml = Psych.load(str, symbolize_names: true)
|
29
|
+
exec!(yml, env: env)
|
30
|
+
rescue Psych::SyntaxError, Psych::BadAlias => e
|
31
|
+
raise YAMLSyntaxError, e.message
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
# Loads a YAML file to configure Unifig.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# Unifig::Init.load_file('config.yml', env: :development)
|
38
|
+
#
|
39
|
+
# @param file_path [String] The path to a YAML config file.
|
40
|
+
# @param env [Symbol] An environment name to load.
|
41
|
+
#
|
42
|
+
# @raise (see Unifig::Init.load)
|
43
|
+
def load_file(file_path, env: nil)
|
44
|
+
# Ruby 2.7 Psych.load_file doesn't support the :symbolize_names flag.
|
45
|
+
# After Ruby 2.7 this can be changed to Psych.load_file if that's faster.
|
46
|
+
load(File.read(file_path), env: env)
|
47
|
+
end
|
48
48
|
|
49
|
-
|
50
|
-
#
|
51
|
-
# @raise [MissingConfig] - No config section was provided in the YAML.
|
52
|
-
def initialize(yml, env: nil)
|
53
|
-
@yml = yml
|
54
|
-
@env = env
|
49
|
+
private
|
55
50
|
|
56
|
-
|
57
|
-
raise
|
51
|
+
# @raise [MissingRequiredError] - One or more required variables are missing values.
|
52
|
+
# @raise (see Unifig::Config#initialize)
|
53
|
+
# @raise (see Unifig::Providers.list)
|
54
|
+
# @raise (see Unifig::Var.load!)
|
55
|
+
# @raise (see .complete_substitutions!)
|
56
|
+
def exec!(yml, env: nil)
|
57
|
+
config = Config.new(yml.delete(:config), env: env)
|
58
58
|
|
59
|
-
|
60
|
-
|
59
|
+
providers = Providers.list(config.providers)
|
60
|
+
return if providers.empty?
|
61
61
|
|
62
|
-
|
63
|
-
def exec!
|
64
|
-
providers = Providers.list(@config.providers)
|
65
|
-
return if providers.empty?
|
62
|
+
Vars.load!(yml, env)
|
66
63
|
|
67
|
-
|
64
|
+
fetch_from_providers!(providers)
|
68
65
|
|
69
|
-
|
70
|
-
vars = fetch_and_set_methods(provider, vars)
|
71
|
-
end
|
66
|
+
check_required_vars
|
72
67
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
68
|
+
complete_substitutions!
|
69
|
+
|
70
|
+
missing_vars, vars = Vars.list.partition { |var| var.value.nil? }
|
71
|
+
attach_methods!(vars)
|
72
|
+
attach_missing_optional_methods!(missing_vars)
|
78
73
|
end
|
79
74
|
|
80
|
-
|
81
|
-
|
75
|
+
def fetch_from_providers!(providers)
|
76
|
+
providers.each do |provider|
|
77
|
+
remaining_vars = Vars.list.filter_map { |var| var.name if var.value.nil? }
|
78
|
+
result = provider.retrieve(remaining_vars)
|
82
79
|
|
83
|
-
|
80
|
+
Vars.write_results!(result, provider.name)
|
81
|
+
end
|
82
|
+
end
|
84
83
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@yml.each do |name, local_config|
|
89
|
-
local_config = {} if local_config.nil?
|
84
|
+
def check_required_vars
|
85
|
+
missing_required_vars = Vars.list.select { |var| var.required? && var.value.nil? }
|
86
|
+
return if missing_required_vars.empty?
|
90
87
|
|
91
|
-
|
92
|
-
|
88
|
+
raise MissingRequiredError, <<~MSG
|
89
|
+
variables without a value: #{missing_required_vars.map(&:name).join(', ')}
|
90
|
+
MSG
|
93
91
|
end
|
94
|
-
Unifig::Providers::Local.load(local_values)
|
95
|
-
vars
|
96
|
-
end
|
97
92
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
93
|
+
# @raise [CyclicalSubstitutionError] - Subtitutions resulted in a cyclical dependency.
|
94
|
+
# @raise [MissingSubstitutionError] - A substitution does not exist.
|
95
|
+
def complete_substitutions!
|
96
|
+
Vars.tsort.each do |name|
|
97
|
+
var = Vars[name]
|
98
|
+
next unless var.value.is_a?(String)
|
99
|
+
|
100
|
+
var.value = var.value.gsub(/\${[^}]+}/) do |match|
|
101
|
+
name = match[2..-2].to_sym
|
102
|
+
Vars[name].value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue TSort::Cyclic => e
|
106
|
+
names = e.message.scan(/:([^ \],]+)/).flatten
|
107
|
+
|
108
|
+
raise CyclicalSubstitutionError, "cyclical dependency: #{names.join(', ')}"
|
105
109
|
end
|
106
|
-
vars.slice(*(vars.keys - values.keys)) # switch to except after 2.7
|
107
|
-
end
|
108
110
|
|
109
|
-
|
110
|
-
|
111
|
-
|
111
|
+
def attach_methods!(vars)
|
112
|
+
vars.each do |var|
|
113
|
+
attach_method!(var)
|
114
|
+
attach_predicate!(var, true)
|
115
|
+
end
|
116
|
+
end
|
112
117
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
118
|
+
def attach_missing_optional_methods!(vars)
|
119
|
+
vars.each do |var|
|
120
|
+
attach_method!(var)
|
121
|
+
attach_predicate!(var, false)
|
122
|
+
end
|
117
123
|
end
|
118
|
-
end
|
119
124
|
|
120
|
-
|
121
|
-
|
122
|
-
|
125
|
+
def attach_method!(var)
|
126
|
+
Unifig.define_singleton_method(var.method) do
|
127
|
+
var.value
|
128
|
+
end
|
123
129
|
end
|
124
|
-
end
|
125
130
|
|
126
|
-
|
127
|
-
|
128
|
-
|
131
|
+
def attach_predicate!(var, bool)
|
132
|
+
Unifig.define_singleton_method(:"#{var.method}?") do
|
133
|
+
bool
|
134
|
+
end
|
129
135
|
end
|
130
136
|
end
|
131
137
|
end
|
@@ -8,17 +8,10 @@ module Unifig
|
|
8
8
|
:local
|
9
9
|
end
|
10
10
|
|
11
|
-
# @private
|
12
|
-
def self.load(data)
|
13
|
-
@data = data
|
14
|
-
end
|
15
|
-
|
16
11
|
def self.retrieve(var_names)
|
17
|
-
|
18
|
-
[name,
|
12
|
+
var_names.to_h do |name|
|
13
|
+
[name, Vars[name].local_value]
|
19
14
|
end
|
20
|
-
local_values.compact!
|
21
|
-
local_values
|
22
15
|
end
|
23
16
|
end
|
24
17
|
end
|
data/lib/unifig/providers.rb
CHANGED
@@ -3,14 +3,24 @@
|
|
3
3
|
module Unifig
|
4
4
|
# @private
|
5
5
|
module Providers
|
6
|
-
# @raise [
|
6
|
+
# @raise [MissingProviderError] - The given provider is not in the list of available providers.
|
7
7
|
def self.list(providers = nil)
|
8
8
|
return all if providers.nil?
|
9
9
|
|
10
10
|
providers.map do |provider|
|
11
|
-
all
|
12
|
-
|
13
|
-
|
11
|
+
all
|
12
|
+
.detect { |pp| pp.name == provider }
|
13
|
+
.tap do |found|
|
14
|
+
unless found
|
15
|
+
msg = %("#{provider}" is not in the list of available providers)
|
16
|
+
|
17
|
+
dym = DidYouMean::SpellChecker.new(dictionary: all.map(&:name))
|
18
|
+
correction = dym.correct(provider).first
|
19
|
+
msg += "\nDid you mean? #{correction}" if correction
|
20
|
+
|
21
|
+
raise MissingProviderError, msg
|
22
|
+
end
|
23
|
+
end
|
14
24
|
end
|
15
25
|
end
|
16
26
|
|
data/lib/unifig/var.rb
CHANGED
@@ -1,29 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Unifig
|
4
|
-
#
|
4
|
+
# A variable created after loading a configuration.
|
5
5
|
class Var
|
6
|
+
# @private
|
6
7
|
def initialize(name, config, env)
|
7
8
|
@name = name
|
8
9
|
@config = config
|
9
10
|
@env = env
|
11
|
+
@value = nil
|
12
|
+
@provider = nil
|
10
13
|
end
|
11
14
|
|
12
|
-
|
15
|
+
# The variable name.
|
16
|
+
#
|
17
|
+
# @return [Symbol]
|
18
|
+
attr_reader :name
|
13
19
|
|
20
|
+
# The provider that supplied the value.
|
21
|
+
#
|
22
|
+
# @return [Symbol]
|
23
|
+
attr_reader :provider # rubocop:disable Style/BisectedAttrAccessor
|
24
|
+
|
25
|
+
# @private
|
26
|
+
attr_writer :provider # rubocop:disable Style/BisectedAttrAccessor
|
27
|
+
|
28
|
+
# The value of the variable.
|
29
|
+
#
|
30
|
+
# @return [Object]
|
31
|
+
attr_reader :value
|
32
|
+
|
33
|
+
# @private
|
34
|
+
def value=(obj)
|
35
|
+
value = blank?(obj) ? nil : obj
|
36
|
+
value = value.dup.freeze unless value.frozen?
|
37
|
+
@value = value
|
38
|
+
end
|
39
|
+
|
40
|
+
# The name of the method this variable can be found using.
|
41
|
+
#
|
42
|
+
# @return [Symbol]
|
14
43
|
def method
|
15
44
|
@method ||= name.to_s.downcase.tr('-', '_').to_sym
|
16
45
|
end
|
17
46
|
|
47
|
+
# @private
|
18
48
|
def local_value
|
19
|
-
@local_value ||= env_config(:value) || config[:value]
|
49
|
+
@local_value ||= env_config(:value) || @config[:value]
|
20
50
|
end
|
21
51
|
|
52
|
+
# Returns whether or not this is a required variable.
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
22
55
|
def required?
|
23
56
|
return @required if defined?(@required)
|
24
57
|
|
25
58
|
optional = env_config(:optional)
|
26
|
-
optional = config[:optional] if optional.nil?
|
59
|
+
optional = @config[:optional] if optional.nil?
|
27
60
|
optional = false if optional.nil?
|
28
61
|
@required = !optional
|
29
62
|
end
|
@@ -31,7 +64,11 @@ module Unifig
|
|
31
64
|
private
|
32
65
|
|
33
66
|
def env_config(key)
|
34
|
-
config.dig(:envs, env, key)
|
67
|
+
@config.dig(:envs, @env, key)
|
68
|
+
end
|
69
|
+
|
70
|
+
def blank?(value)
|
71
|
+
value.nil? || (value.respond_to?(:to_str) && value.to_str.strip.empty?)
|
35
72
|
end
|
36
73
|
end
|
37
74
|
end
|
data/lib/unifig/vars.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unifig
|
4
|
+
# Information about variables after loading a configuration.
|
5
|
+
module Vars
|
6
|
+
class << self
|
7
|
+
include TSort
|
8
|
+
|
9
|
+
# @raise [DuplicateNameError] - A variable produces a duplicate method name.
|
10
|
+
# @private
|
11
|
+
def load!(yml, env)
|
12
|
+
vars = yml.to_h do |name, config|
|
13
|
+
[name, Var.new(name, config || {}, env)]
|
14
|
+
end
|
15
|
+
|
16
|
+
vars
|
17
|
+
.values
|
18
|
+
.group_by(&:method)
|
19
|
+
.each do |method_name, list|
|
20
|
+
next unless list.size > 1
|
21
|
+
|
22
|
+
names = list.map { |var| %("#{var.name}") }.join(', ')
|
23
|
+
raise DuplicateNameError, "variables all result in the same method name (Unifig.#{method_name}): #{names}"
|
24
|
+
end
|
25
|
+
|
26
|
+
@map = vars
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
def write_results!(results, provider)
|
31
|
+
results.each do |name, value|
|
32
|
+
@map[name].value = value
|
33
|
+
@map[name].provider = provider unless @map[name].value.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a list the variables.
|
38
|
+
#
|
39
|
+
# @return [Array<Unifig::Var>]
|
40
|
+
def list
|
41
|
+
(@map || {}).values
|
42
|
+
end
|
43
|
+
|
44
|
+
# Retrieve a variable by name unless it does not exist.
|
45
|
+
#
|
46
|
+
# @param name [Symbol] The name of the variable.
|
47
|
+
#
|
48
|
+
# @return [Unifig::Var or nil]
|
49
|
+
def [](name)
|
50
|
+
@map ||= {}
|
51
|
+
@map[name]
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def tsort_each_node(&block)
|
57
|
+
@map.each_key(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def tsort_each_child(node, &block)
|
61
|
+
var = @map.fetch(node) { raise_missing_substituion_error(node) }
|
62
|
+
|
63
|
+
return unless var.value.is_a?(String)
|
64
|
+
|
65
|
+
var.value.scan(/\${([^}]+)}/).flatten.map(&:to_sym).each(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def raise_missing_substituion_error(name)
|
69
|
+
msg = "variable not found: #{name}"
|
70
|
+
|
71
|
+
dym = DidYouMean::SpellChecker.new(dictionary: @map.keys)
|
72
|
+
correction = dym.correct(name).first
|
73
|
+
msg += "\nDid you mean? #{correction}" if correction
|
74
|
+
|
75
|
+
raise MissingSubstitutionError, msg
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/unifig/version.rb
CHANGED
data/lib/unifig.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'tsort'
|
4
|
+
|
3
5
|
require_relative 'unifig/version'
|
4
6
|
|
5
7
|
require_relative 'unifig/errors'
|
6
8
|
require_relative 'unifig/config'
|
7
9
|
require_relative 'unifig/var'
|
8
|
-
require_relative 'unifig/
|
10
|
+
require_relative 'unifig/vars'
|
9
11
|
require_relative 'unifig/providers'
|
10
12
|
require_relative 'unifig/providers/local'
|
13
|
+
require_relative 'unifig/init'
|
11
14
|
|
12
15
|
# Handle all your configuration variables.
|
13
16
|
module Unifig
|
data/spec/spec_helper.rb
CHANGED
data/spec/unifig/config_spec.rb
CHANGED
@@ -13,6 +13,16 @@ RSpec.describe Unifig::Config do
|
|
13
13
|
}
|
14
14
|
end
|
15
15
|
|
16
|
+
describe '#new' do
|
17
|
+
context 'without a config' do
|
18
|
+
let(:config_hash) { nil }
|
19
|
+
|
20
|
+
it 'raises an error' do
|
21
|
+
expect { config }.to raise_error Unifig::MissingConfigError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
16
26
|
describe '#providers' do
|
17
27
|
it 'returns a list of providers for the selected env' do
|
18
28
|
expect(config.providers).to eql %i[local]
|
data/spec/unifig/init_spec.rb
CHANGED
@@ -17,20 +17,7 @@ RSpec.shared_examples 'basic load tests' do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
context '
|
21
|
-
let(:str) do
|
22
|
-
<<~YML
|
23
|
-
ONE:
|
24
|
-
value: 1
|
25
|
-
YML
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'throws an error' do
|
29
|
-
expect { subject }.to raise_error Unifig::MissingConfig
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context 'with a config' do
|
20
|
+
context 'with a valid config' do
|
34
21
|
let(:str) do
|
35
22
|
<<~YML
|
36
23
|
config:
|
@@ -73,7 +60,7 @@ RSpec.describe Unifig::Init do
|
|
73
60
|
it 'returns the values from the providers in order' do
|
74
61
|
load
|
75
62
|
|
76
|
-
expect(Unifig.one).to
|
63
|
+
expect(Unifig.one).to eql '42'
|
77
64
|
expect(Unifig.two).to be 2
|
78
65
|
end
|
79
66
|
end
|
@@ -179,7 +166,7 @@ RSpec.describe Unifig::Init do
|
|
179
166
|
end
|
180
167
|
|
181
168
|
it 'throws an error' do
|
182
|
-
expect { load }.to raise_error Unifig::
|
169
|
+
expect { load }.to raise_error Unifig::MissingRequiredError
|
183
170
|
end
|
184
171
|
end
|
185
172
|
|
@@ -195,7 +182,105 @@ RSpec.describe Unifig::Init do
|
|
195
182
|
end
|
196
183
|
|
197
184
|
it 'throws an error' do
|
198
|
-
expect { load }.to raise_error Unifig::
|
185
|
+
expect { load }.to raise_error Unifig::MissingRequiredError
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'with substitutions' do
|
191
|
+
let(:str) do
|
192
|
+
<<~YML
|
193
|
+
config:
|
194
|
+
providers: local
|
195
|
+
|
196
|
+
NAME:
|
197
|
+
value: "world"
|
198
|
+
GREETING:
|
199
|
+
value: "Hello, ${NAME}!"
|
200
|
+
YML
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'replaces the substitution' do
|
204
|
+
load
|
205
|
+
|
206
|
+
expect(Unifig.greeting).to eql 'Hello, world!'
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'when they are out of order' do
|
210
|
+
let(:str) do
|
211
|
+
<<~YML
|
212
|
+
config:
|
213
|
+
providers: local
|
214
|
+
|
215
|
+
GREETING:
|
216
|
+
value: "Hello, ${NAME}!"
|
217
|
+
NAME:
|
218
|
+
value: "world"
|
219
|
+
YML
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'replaces the substitution' do
|
223
|
+
load
|
224
|
+
|
225
|
+
expect(Unifig.greeting).to eql 'Hello, world!'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context 'when they chain' do
|
230
|
+
let(:str) do
|
231
|
+
<<~YML
|
232
|
+
config:
|
233
|
+
providers: local
|
234
|
+
|
235
|
+
INTRO:
|
236
|
+
value: "${GREETING} I'm Aaron."
|
237
|
+
NAME:
|
238
|
+
value: "world"
|
239
|
+
GREETING:
|
240
|
+
value: "Hello, ${NAME}!"
|
241
|
+
YML
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'replaces the substitutions in order' do
|
245
|
+
load
|
246
|
+
|
247
|
+
expect(Unifig.intro).to eql "Hello, world! I'm Aaron."
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'when they cause a cycle' do
|
252
|
+
let(:str) do
|
253
|
+
<<~YML
|
254
|
+
config:
|
255
|
+
providers: local
|
256
|
+
|
257
|
+
A:
|
258
|
+
value: "${B}"
|
259
|
+
B:
|
260
|
+
value: "${C}"
|
261
|
+
C:
|
262
|
+
value: "${A}"
|
263
|
+
YML
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'raises an error' do
|
267
|
+
expect { load }.to raise_error Unifig::CyclicalSubstitutionError, 'cyclical dependency: A, B, C'
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'when they do not exist' do
|
272
|
+
let(:str) do
|
273
|
+
<<~YML
|
274
|
+
config:
|
275
|
+
providers: local
|
276
|
+
|
277
|
+
A:
|
278
|
+
value: "${B}"
|
279
|
+
YML
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'raises an error' do
|
283
|
+
expect { load }.to raise_error Unifig::MissingSubstitutionError
|
199
284
|
end
|
200
285
|
end
|
201
286
|
end
|
@@ -32,5 +32,13 @@ RSpec.describe Unifig::Providers do
|
|
32
32
|
list = described_class.list([Unifig::Providers::FortyTwo.name, Unifig::Providers::Local.name])
|
33
33
|
expect(list).to eql [Unifig::Providers::FortyTwo, Unifig::Providers::Local]
|
34
34
|
end
|
35
|
+
|
36
|
+
context 'with an invalid provider' do
|
37
|
+
it 'raises an error' do
|
38
|
+
expect do
|
39
|
+
described_class.list([Unifig::Providers::Local.name, :invalid])
|
40
|
+
end.to raise_error Unifig::MissingProviderError
|
41
|
+
end
|
42
|
+
end
|
35
43
|
end
|
36
44
|
end
|
data/spec/unifig/var_spec.rb
CHANGED
@@ -13,6 +13,27 @@ RSpec.describe Unifig::Var do
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
describe '#value=' do
|
17
|
+
it 'writes the value' do
|
18
|
+
var.value = 'a'
|
19
|
+
|
20
|
+
expect(var.value).to eql 'a'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'write blank strings as nil' do
|
24
|
+
var.value = ' '
|
25
|
+
|
26
|
+
expect(var.value).to be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'freezes the value if no frozen' do
|
30
|
+
var.value = 'a'
|
31
|
+
|
32
|
+
expect(var.value).to eql 'a'
|
33
|
+
expect(var.value).to be_frozen
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
16
37
|
describe '#local_value' do
|
17
38
|
context 'with no value' do
|
18
39
|
it 'returns nil' do
|
@@ -0,0 +1,41 @@
|
|
1
|
+
RSpec.describe Unifig::Vars do
|
2
|
+
let(:yml) do
|
3
|
+
{
|
4
|
+
one: {
|
5
|
+
value: 1
|
6
|
+
},
|
7
|
+
two: {
|
8
|
+
value: 2
|
9
|
+
}
|
10
|
+
}
|
11
|
+
end
|
12
|
+
let(:env) { :development }
|
13
|
+
let(:results) { { one: 1, two: 2 } }
|
14
|
+
|
15
|
+
before do
|
16
|
+
described_class.load!(yml, env)
|
17
|
+
described_class.write_results!(results, :local)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.list' do
|
21
|
+
it 'returns all of the vars' do
|
22
|
+
vars = described_class.list
|
23
|
+
|
24
|
+
expect(vars.size).to be 2
|
25
|
+
vars.each.with_index(1) do |var, value|
|
26
|
+
expect(var).to be_an_instance_of Unifig::Var
|
27
|
+
expect(var.value).to be value
|
28
|
+
expect(var.provider).to be :local
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '.[]' do
|
34
|
+
it 'returns the Var based on the name' do
|
35
|
+
var = described_class[:one]
|
36
|
+
|
37
|
+
expect(var).to be_an_instance_of Unifig::Var
|
38
|
+
expect(var.value).to be 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unifig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.3.2
|
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-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-08-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tsort
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.0
|
13
27
|
description: A pluggable system for loading external variables from one or more providers
|
14
28
|
(e.g. ENV).
|
15
29
|
email:
|
@@ -29,12 +43,14 @@ files:
|
|
29
43
|
- lib/unifig/providers.rb
|
30
44
|
- lib/unifig/providers/local.rb
|
31
45
|
- lib/unifig/var.rb
|
46
|
+
- lib/unifig/vars.rb
|
32
47
|
- lib/unifig/version.rb
|
33
48
|
- spec/spec_helper.rb
|
34
49
|
- spec/unifig/config_spec.rb
|
35
50
|
- spec/unifig/init_spec.rb
|
36
51
|
- spec/unifig/providers_spec.rb
|
37
52
|
- spec/unifig/var_spec.rb
|
53
|
+
- spec/unifig/vars_spec.rb
|
38
54
|
homepage: https://github.com/AaronLasseigne/unifig
|
39
55
|
licenses:
|
40
56
|
- MIT
|
@@ -69,3 +85,4 @@ test_files:
|
|
69
85
|
- spec/unifig/init_spec.rb
|
70
86
|
- spec/unifig/providers_spec.rb
|
71
87
|
- spec/unifig/var_spec.rb
|
88
|
+
- spec/unifig/vars_spec.rb
|