configsl 1.0.1 → 1.1.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 +4 -4
- data/CHANGELOG.md +40 -0
- data/Gemfile +9 -9
- data/README.md +45 -20
- data/lib/configsl/collection.rb +164 -0
- data/lib/configsl/config.rb +6 -0
- data/lib/configsl/dsl.rb +26 -7
- data/lib/configsl/exception.rb +1 -0
- data/lib/configsl/file_format/shared_spec.rb +19 -0
- data/lib/configsl/file_support.rb +25 -5
- data/lib/configsl/format.rb +4 -0
- data/lib/configsl/from_environment.rb +23 -4
- data/lib/configsl/from_file.rb +42 -4
- data/lib/configsl/merge.rb +124 -0
- data/lib/configsl/to_hash.rb +41 -0
- data/lib/configsl/version.rb +7 -0
- data/lib/configsl.rb +1 -4
- metadata +10 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28d4810afd0c6dec366b82f8d40776836293bdadf2af0d1ff9e263394ba926ba
|
|
4
|
+
data.tar.gz: f6a86bdad0443813820b1d43701ce0326711a9dd2835ac43eae06da6dc4a3da0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '06529ef7f89bbfc082be4ff621be81523f3971ee968a27a24a07033ca5fe4a206297b3d243ecc7f9835287ce44fceeb44e5af201c91c1117da7226c35c5df225'
|
|
7
|
+
data.tar.gz: db96d482cc77297ae7dca4fe20bd913e67679d764c99f1cfea5ff72765125a2327b31435f8dca9befd3f4ec07cae47abc5f7433bc5921fa086d301befa28adf5
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog][changelog], and this project adheres
|
|
6
6
|
to [Semantic Versioning][versioning].
|
|
7
7
|
|
|
8
|
+
## [1.1.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Support collections of configuration objects with the new `Collection` module
|
|
13
|
+
- Add `ToHash` module for converting configuration objects to hashes using
|
|
14
|
+
`to_h`
|
|
15
|
+
- Create setter methods for options
|
|
16
|
+
- Added `Merge` module to merge parameters from multiple sources
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Renamed `@params` to `@configsl_params` to avoid collisions
|
|
21
|
+
- Add `configsl_` prefix to more methods to avoid conflicts
|
|
22
|
+
|
|
23
|
+
### Deprecated
|
|
24
|
+
|
|
25
|
+
- `FileSupport.find_file` is deprecated; use `configsl_find_file` instead
|
|
26
|
+
- `FileSupport.find_file_format` is deprecated; use `configsl_find_file_format`
|
|
27
|
+
instead
|
|
28
|
+
|
|
29
|
+
## [1.0.2]
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Moved `ConfigSL::VERSION` to `configsl/version` for easier reference.
|
|
34
|
+
- Exposed the shared example for file formats. Include it in your specs with:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require 'configsl/file_format/shared_spec'
|
|
38
|
+
|
|
39
|
+
RSpec.describe MyFileFormat do
|
|
40
|
+
it_behaves_like 'a file format', 'spec-config.json', %i[json], {
|
|
41
|
+
format: 'JSON',
|
|
42
|
+
name: 'config.json',
|
|
43
|
+
nested: { title: 'JSON file for testing' }
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
8
48
|
## [1.0.1]
|
|
9
49
|
|
|
10
50
|
### Fixed
|
data/Gemfile
CHANGED
|
@@ -6,20 +6,20 @@ gemspec
|
|
|
6
6
|
|
|
7
7
|
group :development do
|
|
8
8
|
gem 'rake', '~> 13.2'
|
|
9
|
-
gem 'rubocop', '~> 1.
|
|
10
|
-
gem 'rubocop-factory_bot', '~> 2.
|
|
11
|
-
gem 'rubocop-
|
|
12
|
-
gem 'rubocop-
|
|
9
|
+
gem 'rubocop', '~> 1.75'
|
|
10
|
+
gem 'rubocop-factory_bot', '~> 2.27'
|
|
11
|
+
gem 'rubocop-md', '~> 2.0'
|
|
12
|
+
gem 'rubocop-performance', '~> 1.25'
|
|
13
|
+
gem 'rubocop-rake', '~> 0.7'
|
|
14
|
+
gem 'rubocop-rspec', '~> 3.5'
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
group :test do
|
|
16
|
-
|
|
17
|
-
# fail.
|
|
18
|
-
gem 'activesupport', '~> 7.1.0'
|
|
18
|
+
gem 'activesupport', '~> 8.1'
|
|
19
19
|
|
|
20
20
|
gem 'coveralls_reborn', '~> 0.28'
|
|
21
|
-
gem 'factory_bot', '~> 6.
|
|
21
|
+
gem 'factory_bot', '~> 6.5'
|
|
22
22
|
gem 'rspec', '~> 3.13'
|
|
23
|
-
gem 'rspec-github', '~>
|
|
23
|
+
gem 'rspec-github', '~> 3.0'
|
|
24
24
|
gem 'simplecov', '~> 0.22'
|
|
25
25
|
end
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ConfigSL [![Gem Version]
|
|
1
|
+
# ConfigSL [![Gem Version][badge-version]][rubygems] [![Coverage Status][badge-coverage]][coverage] [![Code Checks][badge-checks]][checks]
|
|
2
2
|
|
|
3
3
|
ConfigSL is a simple Domain-Specific Language (DSL) module for configuration.
|
|
4
4
|
It is designed to provide a declarative way to define configuration, with as few
|
|
@@ -10,7 +10,7 @@ extensible, so you can use as little or as much as you need.
|
|
|
10
10
|
Add this line to your application's Gemfile:
|
|
11
11
|
|
|
12
12
|
```ruby
|
|
13
|
-
gem 'configsl'
|
|
13
|
+
gem 'configsl', '~> 1.1'
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
And then execute:
|
|
@@ -37,26 +37,47 @@ You can start defining your configurations using two methods:
|
|
|
37
37
|
The `ConfigSL::Config` base class includes common functionality for working with
|
|
38
38
|
configurations. Currently, the class provides the following features:
|
|
39
39
|
|
|
40
|
+
- **Collections**: Supports arrays and hashes of other configurations
|
|
40
41
|
- **DSL**: The primary DSL for defining configuration options
|
|
41
42
|
- **Format**: A simple way to enforce option value formatting
|
|
42
43
|
- **FromEnvironment**: Load configuration from environment variables
|
|
43
44
|
- **FromFile**: Load configuration from a file
|
|
45
|
+
- **Merge**: Merge configuration from multiple sources
|
|
46
|
+
- **ToHash**: Recursively convert the configuration to a hash
|
|
44
47
|
- **Validation**: Built-in validation for configuration options
|
|
45
48
|
|
|
46
49
|
```ruby
|
|
47
50
|
require 'configsl'
|
|
48
51
|
|
|
49
52
|
class AppConfig < ConfigSL::Config
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
register_file_format :json
|
|
54
|
+
register_file_format :yaml
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
option :name, type: String, default: 'My App'
|
|
57
|
+
option :environment, type: Symbol, enum: %i[dev test prod], default: :dev,
|
|
58
|
+
env_variable: 'RACK_ENV'
|
|
59
|
+
option :database, type: DatabaseConfig, required: true
|
|
60
|
+
|
|
61
|
+
# Collect arrays or hashes into configuration objects. Automatically set a key
|
|
62
|
+
# on the collected configurations based on their index (arrays) or key
|
|
63
|
+
# (hashes).
|
|
64
|
+
option :hosts, type: Hash, collection: { type: HostConfig, key: :hostname }
|
|
65
|
+
|
|
66
|
+
# Use shorthand syntax if you don't need to set a key.
|
|
67
|
+
option :plugins, type: Array, collection: PluginConfig
|
|
57
68
|
end
|
|
58
69
|
```
|
|
59
70
|
|
|
71
|
+
You can load your configuration from different sources using the following
|
|
72
|
+
methods:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
AppConfig.new(params) # Load from parameters
|
|
76
|
+
AppConfig.from_file # Load from a file
|
|
77
|
+
AppConfig.from_environment # Load from environment variables
|
|
78
|
+
AppConfig.load # Merge from multiple sources
|
|
79
|
+
```
|
|
80
|
+
|
|
60
81
|
### Including modules
|
|
61
82
|
|
|
62
83
|
If you'd like to pick and choose the features you want to use, you can include
|
|
@@ -70,19 +91,19 @@ that sets the configuration values by calling `set_value` for each option.
|
|
|
70
91
|
require 'configsl'
|
|
71
92
|
|
|
72
93
|
class ApplicationConfig
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
end
|
|
94
|
+
include ConfigSL::DSL
|
|
95
|
+
include ConfigSL::Format
|
|
96
|
+
include ConfigSL::FromEnvironment
|
|
97
|
+
|
|
98
|
+
option :name, type: String, default: 'My App'
|
|
99
|
+
option :environment, type: Symbol, env_variable: 'RACK_ENV'
|
|
100
|
+
option :database, type: DatabaseConfig
|
|
101
|
+
|
|
102
|
+
def initialize(params = {})
|
|
103
|
+
params.each do |name, value|
|
|
104
|
+
set_value(name, value)
|
|
85
105
|
end
|
|
106
|
+
end
|
|
86
107
|
end
|
|
87
108
|
```
|
|
88
109
|
|
|
@@ -101,5 +122,9 @@ It's important to note that this is not limited to your defined configuration
|
|
|
101
122
|
options, but also methods such as `register_file_format` and
|
|
102
123
|
`config_file_path`.
|
|
103
124
|
|
|
125
|
+
[badge-checks]: https://github.com/jamesiarmes/configsl/actions/workflows/checks.yaml/badge.svg?branch=main
|
|
104
126
|
[badge-coverage]: https://coveralls.io/repos/github/jamesiarmes/configsl/badge.svg
|
|
127
|
+
[badge-version]: https://badge.fury.io/rb/configsl.svg
|
|
128
|
+
[checks]: https://github.com/jamesiarmes/configsl/actions/workflows/checks.yaml
|
|
105
129
|
[coverage]: https://coveralls.io/github/jamesiarmes/configsl
|
|
130
|
+
[rubygems]: https://rubygems.org/gems/configsl
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'format'
|
|
4
|
+
|
|
5
|
+
module ConfigSL
|
|
6
|
+
# Support options as collections.
|
|
7
|
+
#
|
|
8
|
+
# This will format the values of the defined options when they are set on the
|
|
9
|
+
# config object. It will also format the values when they are retrieved, if
|
|
10
|
+
# they don't match their defined type.
|
|
11
|
+
#
|
|
12
|
+
# The default behavior is to cast the value as the defined type. If the value
|
|
13
|
+
# is nil, it will be returned as is.
|
|
14
|
+
#
|
|
15
|
+
# @example Use the shorthand syntax to define a collection with no additional
|
|
16
|
+
# options
|
|
17
|
+
#
|
|
18
|
+
# class TestConfig < ConfigSL::Config
|
|
19
|
+
# include ConfigSL::Collection
|
|
20
|
+
#
|
|
21
|
+
# option :subarray, type: Array, collection: SubConfig
|
|
22
|
+
# option :subhash, type: Hash, collection: SubConfig
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @example Automatically set a key on the collection members based on their
|
|
26
|
+
# index (arrays) or keys (hashes), unless the key is already set
|
|
27
|
+
#
|
|
28
|
+
# class TestConfig < ConfigSL::Config
|
|
29
|
+
# include ConfigSL::Collection
|
|
30
|
+
#
|
|
31
|
+
# option :subarray, type: Array, collection: { type: SubConfig, key: :index }
|
|
32
|
+
# option :subhash, type: Hash, collection: { type: SubConfig, key: :name }
|
|
33
|
+
# end
|
|
34
|
+
module Collection
|
|
35
|
+
def self.included(base)
|
|
36
|
+
base.include(Format) unless base.include?(Format)
|
|
37
|
+
base.extend ClassMethods
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Class methods necessary for defining collection options.
|
|
41
|
+
module ClassMethods
|
|
42
|
+
# @see DSL#option
|
|
43
|
+
def option(name, opts = {})
|
|
44
|
+
# If the option was set using the shorthand syntax, convert it to an
|
|
45
|
+
# options hash for consistent handling.
|
|
46
|
+
opts[:collection] = { type: opts[:collection], key: nil } \
|
|
47
|
+
if opts[:collection].is_a?(Class)
|
|
48
|
+
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# @see DSL#set_value
|
|
56
|
+
#
|
|
57
|
+
# @raise [InvalidValueError] If the value is not of the expected type.
|
|
58
|
+
def set_value(name, value)
|
|
59
|
+
configsl_option_exists!(name)
|
|
60
|
+
if configsl_collection?(name) &&
|
|
61
|
+
!value.nil? &&
|
|
62
|
+
!value.is_a?(options[name][:type])
|
|
63
|
+
# If the option is a collection, we want to make sure the value is of the
|
|
64
|
+
# expected type.
|
|
65
|
+
raise InvalidValueError,
|
|
66
|
+
"Invalid value type (#{value.class}) for collection " \
|
|
67
|
+
"option #{name}; expected #{options[name][:type]}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Determines if an option is a collection.
|
|
74
|
+
#
|
|
75
|
+
# @param option [Symbol] Option to check.
|
|
76
|
+
# @return [Boolean]
|
|
77
|
+
def configsl_collection?(option)
|
|
78
|
+
options[option].fetch(:collection, false) &&
|
|
79
|
+
[Array, Hash].include?(options[option][:type])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Determines if the values in a collection have been collected.
|
|
83
|
+
#
|
|
84
|
+
# @param option [Symbol] Option being checked.
|
|
85
|
+
# @param values [Array, Hash] Values for the option.
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def configsl_collected?(option, values)
|
|
88
|
+
return true if values.empty?
|
|
89
|
+
|
|
90
|
+
value = options[option][:type] == Array ? values.first : values.values.first
|
|
91
|
+
value.is_a?(options[option][:collection][:type])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Collects values into an appropriate collection.
|
|
95
|
+
#
|
|
96
|
+
# @param option [Symbol] Option whose values are being collected.
|
|
97
|
+
# @param values [Array, Hash] Values to collect.
|
|
98
|
+
# @return [Array, Hash] Collected values as the defined type.
|
|
99
|
+
#
|
|
100
|
+
# @raise [InvalidValueError] If the values are not of the correct type.
|
|
101
|
+
def configsl_collect_values(option, values)
|
|
102
|
+
return values if configsl_collected?(option, values)
|
|
103
|
+
|
|
104
|
+
if values.is_a?(Hash)
|
|
105
|
+
configsl_collect_hash(option, values)
|
|
106
|
+
else
|
|
107
|
+
configsl_collect_array(option, values)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Collect values for an array collection.
|
|
112
|
+
#
|
|
113
|
+
# @param option [Symbol] Option being processed.
|
|
114
|
+
# @param values [Array] Values to collect.
|
|
115
|
+
# @return [Array] Collected values.
|
|
116
|
+
def configsl_collect_array(option, values)
|
|
117
|
+
values.map.with_index do |value, index|
|
|
118
|
+
configsl_collect_value(option, value, index)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Collect values for a hash collection.
|
|
123
|
+
#
|
|
124
|
+
# @param option [Symbol] Option being processed.
|
|
125
|
+
# @param values [Hash] Values to collect.
|
|
126
|
+
# @return [Hash] Collected values.
|
|
127
|
+
def configsl_collect_hash(option, values)
|
|
128
|
+
values.to_h do |key, value|
|
|
129
|
+
configsl_collect_value(option, [key, value], key)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Collect a single value to be added to the collection.
|
|
134
|
+
#
|
|
135
|
+
# @param option [Symbol] Option whose value is being collected.
|
|
136
|
+
# @param value [Object] The value to be added to the collection.
|
|
137
|
+
# @return [Array, Hash]
|
|
138
|
+
def configsl_collect_value(option, value, index = nil)
|
|
139
|
+
is_array = options[option][:type] == Array
|
|
140
|
+
params = is_array ? value : value[1]
|
|
141
|
+
configsl_collection_key(params, options[option][:collection][:key], index)
|
|
142
|
+
|
|
143
|
+
config = options[option][:collection][:type].new(params)
|
|
144
|
+
is_array ? config : [value[0], config]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def configsl_collection_key(value, key, index)
|
|
148
|
+
return if key.nil? || index.nil?
|
|
149
|
+
return value[key] unless value[key].nil?
|
|
150
|
+
|
|
151
|
+
value[key] = index
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @see Format#format_value
|
|
155
|
+
def format_value(option, value)
|
|
156
|
+
return super unless configsl_collection?(option)
|
|
157
|
+
|
|
158
|
+
return configsl_collect_values(option, value) unless value.nil? || value.empty?
|
|
159
|
+
|
|
160
|
+
formatter = Format::FORMATTERS[options[option][:type]]
|
|
161
|
+
value.send(formatter)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/configsl/config.rb
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'collection'
|
|
3
4
|
require_relative 'dsl'
|
|
4
5
|
require_relative 'file_format/json'
|
|
5
6
|
require_relative 'file_format/yaml'
|
|
6
7
|
require_relative 'format'
|
|
7
8
|
require_relative 'from_environment'
|
|
8
9
|
require_relative 'from_file'
|
|
10
|
+
require_relative 'merge'
|
|
11
|
+
require_relative 'to_hash'
|
|
9
12
|
require_relative 'validation'
|
|
10
13
|
|
|
11
14
|
module ConfigSL
|
|
@@ -15,7 +18,10 @@ module ConfigSL
|
|
|
15
18
|
include Format
|
|
16
19
|
include FromEnvironment
|
|
17
20
|
include FromFile
|
|
21
|
+
include ToHash
|
|
18
22
|
include Validation
|
|
23
|
+
include Collection
|
|
24
|
+
include Merge
|
|
19
25
|
|
|
20
26
|
def initialize(params = {})
|
|
21
27
|
params.each do |name, value|
|
data/lib/configsl/dsl.rb
CHANGED
|
@@ -46,13 +46,13 @@ module ConfigSL
|
|
|
46
46
|
# If the option is not set, it will return the default value.
|
|
47
47
|
#
|
|
48
48
|
# @param name [Symbol] The name of the option.
|
|
49
|
-
# @return [Object] The value of the option.
|
|
49
|
+
# @return [Object, nil] The value of the option.
|
|
50
50
|
#
|
|
51
51
|
# @raise [InvalidOptionError] If the option is not defined.
|
|
52
52
|
def get_value(name)
|
|
53
|
-
|
|
53
|
+
configsl_option_exists!(name)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
configsl_params.fetch(name, options[name]&.[](:default))
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Sets the value of an option.
|
|
@@ -63,10 +63,27 @@ module ConfigSL
|
|
|
63
63
|
#
|
|
64
64
|
# @raise [InvalidOptionError] If the option is not defined.
|
|
65
65
|
def set_value(name, value)
|
|
66
|
-
|
|
66
|
+
configsl_option_exists!(name)
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
configsl_params[name] = value
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Validates that an option exists for the current configuration class.
|
|
72
|
+
#
|
|
73
|
+
# @param name [Symbol] Name of the option to validate.
|
|
74
|
+
#
|
|
75
|
+
# @raise [InvalidOptionError] If the option is not defined.
|
|
76
|
+
def configsl_option_exists!(name)
|
|
77
|
+
return if options.key?(name)
|
|
78
|
+
|
|
79
|
+
raise InvalidOptionError, "Option #{name} is not defined"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the params hash for the class of the current instance.
|
|
83
|
+
#
|
|
84
|
+
# @return [Hash] The params hash.
|
|
85
|
+
def configsl_params
|
|
86
|
+
@configsl_params ||= {}
|
|
70
87
|
end
|
|
71
88
|
|
|
72
89
|
# Required class methods for the config DSL.
|
|
@@ -80,8 +97,10 @@ module ConfigSL
|
|
|
80
97
|
# @param opts [Hash] The options for the option.
|
|
81
98
|
# @return [void]
|
|
82
99
|
def option(name, opts = {})
|
|
83
|
-
options
|
|
100
|
+
options[name] = opts
|
|
101
|
+
|
|
84
102
|
define_method(name) { get_value(name) }
|
|
103
|
+
define_method("#{name}=") { |val| set_value(name, val) }
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
# Returns the options hash for the class.
|
data/lib/configsl/exception.rb
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
shared_examples 'a file format' do |filename, extensions, expected|
|
|
4
|
+
subject(:file) { described_class.new(path) }
|
|
5
|
+
|
|
6
|
+
let(:path) { "spec/support/fixtures/#{filename}" }
|
|
7
|
+
|
|
8
|
+
describe '.extensions' do
|
|
9
|
+
it 'returns the JSON extension' do
|
|
10
|
+
expect(described_class.extensions).to eq(extensions)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#read' do
|
|
15
|
+
it 'returns a hash of configuration values' do
|
|
16
|
+
expect(file.read).to eq(expected)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -72,8 +72,6 @@ module ConfigSL
|
|
|
72
72
|
@config_file_formats[format] = opts
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
private
|
|
76
|
-
|
|
77
75
|
# Find the configuration file based on the default path, name, and defined
|
|
78
76
|
# formats.
|
|
79
77
|
#
|
|
@@ -91,7 +89,7 @@ module ConfigSL
|
|
|
91
89
|
# provided, will only match files of the given format. Ignored if `path`
|
|
92
90
|
# is provided.
|
|
93
91
|
# @return [Array<String>] Array of matching files.
|
|
94
|
-
def
|
|
92
|
+
def configsl_find_file(path: nil, format: nil)
|
|
95
93
|
paths = Dir.glob(
|
|
96
94
|
path.nil? ? "#{config_file_name}.{#{file_extensions(format:).join(',')}}" : path,
|
|
97
95
|
base: path.nil? ? config_file_path : nil
|
|
@@ -99,7 +97,7 @@ module ConfigSL
|
|
|
99
97
|
|
|
100
98
|
raise FileNotFoundError, 'No configuration file found!' if paths.empty?
|
|
101
99
|
|
|
102
|
-
paths.map { |p| File.join(config_file_path, p) }
|
|
100
|
+
path.nil? ? paths.map { |p| File.join(config_file_path, p) } : paths
|
|
103
101
|
end
|
|
104
102
|
|
|
105
103
|
# Find the format for a file given its extension.
|
|
@@ -109,7 +107,7 @@ module ConfigSL
|
|
|
109
107
|
#
|
|
110
108
|
# @raise [FileFormatError] If no file formats have been defined.
|
|
111
109
|
# @raise [FileFormatError] If no file format is found for the extension.
|
|
112
|
-
def
|
|
110
|
+
def configsl_find_file_format(extension)
|
|
113
111
|
raise FileFormatError, 'No file formats have been defined' if @config_file_formats.nil?
|
|
114
112
|
|
|
115
113
|
extension = extension.sub(/^\./, '').to_sym
|
|
@@ -119,6 +117,28 @@ module ConfigSL
|
|
|
119
117
|
|
|
120
118
|
raise FileFormatError, "No file format found for extension: #{extension}"
|
|
121
119
|
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# Deprecated: Use configsl_find_file instead.
|
|
124
|
+
def find_file(path: nil, format: nil)
|
|
125
|
+
warn '[ConfigSL] DEPRECATION WARNING: ' \
|
|
126
|
+
'`find_file` is deprecated. ' \
|
|
127
|
+
'Use `configsl_find_file` instead. ' \
|
|
128
|
+
"Called from: #{caller(1..1).first}",
|
|
129
|
+
category: :deprecated
|
|
130
|
+
configsl_find_file(path:, format:)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Deprecated: Use configsl_find_file_format instead.
|
|
134
|
+
def find_file_format(extension)
|
|
135
|
+
warn '[ConfigSL] DEPRECATION WARNING: ' \
|
|
136
|
+
'`find_file_format` is deprecated. ' \
|
|
137
|
+
'Use `configsl_find_file_format` instead. ' \
|
|
138
|
+
"Called from: #{caller(1..1).first}",
|
|
139
|
+
category: :deprecated
|
|
140
|
+
configsl_find_file_format(extension)
|
|
141
|
+
end
|
|
122
142
|
end
|
|
123
143
|
end
|
|
124
144
|
end
|
data/lib/configsl/format.rb
CHANGED
|
@@ -39,6 +39,10 @@ module ConfigSL
|
|
|
39
39
|
super(name, format_value(name, value))
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Formats a value based on its option definition.
|
|
43
|
+
#
|
|
44
|
+
# @param option [Symbol] Option whose value is being formatted.
|
|
45
|
+
# @param value [Object] Value for the option.
|
|
42
46
|
def format_value(option, value)
|
|
43
47
|
return value if value.nil? || value.is_a?(options[option][:type])
|
|
44
48
|
|
|
@@ -21,6 +21,28 @@ module ConfigSL
|
|
|
21
21
|
base.from_environment_prefix ''
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def self.configsl_source_name
|
|
25
|
+
:environment
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Loads the configuration parameters from environment variables.
|
|
29
|
+
#
|
|
30
|
+
# @param klass [Class] The class loading the configuration.
|
|
31
|
+
# @param opts [Hash] Options for loading the configuration.
|
|
32
|
+
# @option opts [Boolean] :defaults Whether to populate default values,
|
|
33
|
+
# defaults to `true`
|
|
34
|
+
# @return [Hash] Loaded configuration parameters.
|
|
35
|
+
def self.configsl_load_params(klass, opts = {})
|
|
36
|
+
klass.options.each_with_object({}) do |(name, option_opts), hash|
|
|
37
|
+
env_var = option_opts[:env_variable] || name.to_s.upcase
|
|
38
|
+
if ENV.key?(env_var)
|
|
39
|
+
hash[name] = ENV[env_var]
|
|
40
|
+
elsif opts[:defaults]
|
|
41
|
+
hash[name] = option_opts[:default]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
24
46
|
# Class methods necessary for loading configuration from the environment.
|
|
25
47
|
module ClassMethods
|
|
26
48
|
# Set the prefix for environment variables.
|
|
@@ -34,10 +56,7 @@ module ConfigSL
|
|
|
34
56
|
#
|
|
35
57
|
# @return [self] The new config object
|
|
36
58
|
def from_environment
|
|
37
|
-
params =
|
|
38
|
-
ENV.fetch(opts[:env_variable], opts[:default])
|
|
39
|
-
end
|
|
40
|
-
|
|
59
|
+
params = FromEnvironment.configsl_load_params(self, defaults: true)
|
|
41
60
|
new(params)
|
|
42
61
|
end
|
|
43
62
|
|
data/lib/configsl/from_file.rb
CHANGED
|
@@ -32,6 +32,46 @@ module ConfigSL
|
|
|
32
32
|
base.extend(ClassMethods)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def self.configsl_source_name
|
|
36
|
+
:file
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Loads the configuration parameters from a file.
|
|
40
|
+
#
|
|
41
|
+
# @param klass [Class] The class loading the configuration.
|
|
42
|
+
# @param opts [Hash] Options for loading the configuration.
|
|
43
|
+
# @option opts [String] :path Optional path to the file to load; uses the
|
|
44
|
+
# default path and filename if not specified.
|
|
45
|
+
# @option opts [Symbol] :format Optional format to use for the file; uses
|
|
46
|
+
# the file extension if not specified.
|
|
47
|
+
# @option opts [Boolean] :defaults Whether to populate default values,
|
|
48
|
+
# defaults to `true`.
|
|
49
|
+
# @return [Hash] Loaded configuration parameters.
|
|
50
|
+
def self.configsl_load_params(klass, opts = {})
|
|
51
|
+
formats = klass.instance_variable_get(:@config_file_formats) || {}
|
|
52
|
+
return {} if formats.empty?
|
|
53
|
+
|
|
54
|
+
path = opts[:path] || klass.configsl_find_file.first
|
|
55
|
+
format = opts[:format] || klass.configsl_find_file_format(File.extname(path))
|
|
56
|
+
data = formats[format][:class].new(path).read
|
|
57
|
+
|
|
58
|
+
populate_defaults(klass, data) if opts[:defaults]
|
|
59
|
+
data
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Populates default values for unset options.
|
|
63
|
+
#
|
|
64
|
+
# @param klass [Class] The class loading the configuration.
|
|
65
|
+
# @param data [Hash] Configuration parameters.
|
|
66
|
+
def self.populate_defaults(klass, data)
|
|
67
|
+
klass.options.each do |name, option_opts|
|
|
68
|
+
name_sym = name.to_sym
|
|
69
|
+
next if data.key?(name_sym) || data.key?(name.to_s)
|
|
70
|
+
|
|
71
|
+
data[name_sym] = option_opts[:default]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
35
75
|
# Required class methods for loading config files.
|
|
36
76
|
module ClassMethods
|
|
37
77
|
# Loads configuration from a file.
|
|
@@ -46,10 +86,8 @@ module ConfigSL
|
|
|
46
86
|
# file extension if not specified.
|
|
47
87
|
# @return [self]
|
|
48
88
|
def from_file(path = nil, format: nil)
|
|
49
|
-
path
|
|
50
|
-
|
|
51
|
-
file = @config_file_formats[format][:class].new(path)
|
|
52
|
-
new(file.read)
|
|
89
|
+
params = FromFile.configsl_load_params(self, path:, format:, defaults: true)
|
|
90
|
+
new(params)
|
|
53
91
|
end
|
|
54
92
|
end
|
|
55
93
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConfigSL
|
|
4
|
+
# Support merging configurations from multiple sources based on precedence.
|
|
5
|
+
module Merge
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend ClassMethods
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Class methods to manage precedence and loading of sources.
|
|
11
|
+
module ClassMethods
|
|
12
|
+
# Load config from all available sources and merge them in order.
|
|
13
|
+
#
|
|
14
|
+
# @param params [Hash] Configuration parameters to merge in at the highest
|
|
15
|
+
# precedence.
|
|
16
|
+
# @param source_options [Hash] Optional per-source configuration options.
|
|
17
|
+
# @return [self] Configuration object with merged parameters.
|
|
18
|
+
def load(params = {}, source_options = {})
|
|
19
|
+
merged_params = configsl_load_merged_params(params, source_options)
|
|
20
|
+
new(merged_params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Set or get precedence order
|
|
24
|
+
#
|
|
25
|
+
# @param sources [Array<Symbol>] List of sources in order of priority,
|
|
26
|
+
# highest to lowest precedence.
|
|
27
|
+
# @return [Array<Symbol>] Full list of sources in precedence order.
|
|
28
|
+
def precedence(*sources)
|
|
29
|
+
sources = sources.flatten
|
|
30
|
+
if sources.any?
|
|
31
|
+
configsl_validate_precedence_sources(sources)
|
|
32
|
+
@precedence = sources
|
|
33
|
+
else
|
|
34
|
+
@precedence || configsl_default_precedence
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the available sources dynamically discovered from ancestors
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Symbol>]
|
|
41
|
+
def configsl_available_sources
|
|
42
|
+
[:params] + ancestors.select { |ancestor| ancestor.respond_to?(:configsl_source_name) }
|
|
43
|
+
.map(&:configsl_source_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Returns the default precedence order.
|
|
49
|
+
#
|
|
50
|
+
# +:params+ has the highest precedence, followed by included sources in
|
|
51
|
+
# the order they were included.
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Symbol>] Default source precedence order.
|
|
54
|
+
def configsl_default_precedence
|
|
55
|
+
# Ancestors are listed in reverse order of inclusion, so we filter
|
|
56
|
+
# modules that are config sources and reverse the array to order them by
|
|
57
|
+
# inclusion.
|
|
58
|
+
modules = ancestors.select { |ancestor| ancestor.respond_to?(:configsl_source_name) }
|
|
59
|
+
[:params] + modules.reverse.map(&:configsl_source_name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Loads configs from all available sources and merges them in order.
|
|
63
|
+
#
|
|
64
|
+
# @param params [Hash] Configuration parameters to merge in at the highest
|
|
65
|
+
# precedence.
|
|
66
|
+
# @param opts [Hash] Optional per-source configuration options.
|
|
67
|
+
# @return [Hash] Merged parameters.
|
|
68
|
+
def configsl_load_merged_params(params = {}, opts = {})
|
|
69
|
+
params = params.is_a?(Hash) ? params.transform_keys(&:to_sym) : {}
|
|
70
|
+
sources = { params: params }
|
|
71
|
+
|
|
72
|
+
configsl_load_sources(sources, opts)
|
|
73
|
+
configsl_merge_sources_by_precedence(sources)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Loads configuration data from all registered config sources.
|
|
77
|
+
#
|
|
78
|
+
# If an explicit precedence has been defined, only sources configured in
|
|
79
|
+
# the chain will be loaded.
|
|
80
|
+
#
|
|
81
|
+
# @param sources [Hash{Symbol => Hash}] Hash for loaded sources.
|
|
82
|
+
# @param opts [Hash] Optional per-source configuration options.
|
|
83
|
+
def configsl_load_sources(sources, opts = {})
|
|
84
|
+
ancestors.each do |ancestor|
|
|
85
|
+
next unless ancestor.respond_to?(:configsl_source_name)
|
|
86
|
+
|
|
87
|
+
source_name = ancestor.configsl_source_name.to_sym
|
|
88
|
+
next unless precedence.include?(source_name)
|
|
89
|
+
|
|
90
|
+
source_opts = opts[source_name] || {}
|
|
91
|
+
sources[source_name] ||=
|
|
92
|
+
ancestor.configsl_load_params(self, source_opts)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Merges all loaded configuration sources in precedence order.
|
|
97
|
+
#
|
|
98
|
+
# @param sources [Hash{Symbol => Hash}] Loaded configuration hashes.
|
|
99
|
+
# @return [Hash] Merged configuration parameters.
|
|
100
|
+
def configsl_merge_sources_by_precedence(sources)
|
|
101
|
+
merged = {}
|
|
102
|
+
precedence.reverse_each do |source|
|
|
103
|
+
merged.merge!(sources[source] || {})
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
merged
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Validates that all specified precedence sources are available.
|
|
110
|
+
#
|
|
111
|
+
# @param sources [Array<Symbol>] The sources to validate.
|
|
112
|
+
#
|
|
113
|
+
# @raise [ArgumentError] If any source is invalid.
|
|
114
|
+
def configsl_validate_precedence_sources(sources)
|
|
115
|
+
invalid = sources - configsl_available_sources
|
|
116
|
+
return if invalid.empty?
|
|
117
|
+
|
|
118
|
+
msg = "Invalid precedence sources: #{invalid.join(', ')}. " \
|
|
119
|
+
"Available: #{configsl_available_sources.join(', ')}"
|
|
120
|
+
raise ArgumentError, msg
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConfigSL
|
|
4
|
+
# Converts a configuration value to a hash structure.
|
|
5
|
+
module ToHash
|
|
6
|
+
def to_h
|
|
7
|
+
configsl_params.to_h { |k, v| [k, configsl_serialize_value(v)] }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Recursively serializes configuration values.
|
|
13
|
+
#
|
|
14
|
+
# @param val [Object] Value to serialize.
|
|
15
|
+
# @return [Object] Serialized value.
|
|
16
|
+
def configsl_serialize_value(val)
|
|
17
|
+
case val
|
|
18
|
+
when nil then nil
|
|
19
|
+
when Array then configsl_serialize_array(val)
|
|
20
|
+
when Hash then configsl_serialize_hash(val)
|
|
21
|
+
else val.respond_to?(:to_h) ? val.to_h : val
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Serializes array values.
|
|
26
|
+
#
|
|
27
|
+
# @param array [Array] Array to serialize.
|
|
28
|
+
# @return [Array] Serialized array.
|
|
29
|
+
def configsl_serialize_array(array)
|
|
30
|
+
array.map { |item| configsl_serialize_value(item) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Serializes hash values.
|
|
34
|
+
#
|
|
35
|
+
# @param hash [Hash] Hash to serialize.
|
|
36
|
+
# @return [Hash] Serialized hash.
|
|
37
|
+
def configsl_serialize_hash(hash)
|
|
38
|
+
hash.transform_values { |item| configsl_serialize_value(item) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/configsl.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: configsl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James I. Armes
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: facets
|
|
@@ -29,24 +28,29 @@ email: jamesiarmes@gmail.com
|
|
|
29
28
|
executables: []
|
|
30
29
|
extensions: []
|
|
31
30
|
extra_rdoc_files:
|
|
32
|
-
- README.md
|
|
33
31
|
- CHANGELOG.md
|
|
32
|
+
- README.md
|
|
34
33
|
files:
|
|
35
34
|
- CHANGELOG.md
|
|
36
35
|
- Gemfile
|
|
37
36
|
- README.md
|
|
38
37
|
- lib/configsl.rb
|
|
38
|
+
- lib/configsl/collection.rb
|
|
39
39
|
- lib/configsl/config.rb
|
|
40
40
|
- lib/configsl/dsl.rb
|
|
41
41
|
- lib/configsl/exception.rb
|
|
42
42
|
- lib/configsl/file_format/base.rb
|
|
43
43
|
- lib/configsl/file_format/json.rb
|
|
44
|
+
- lib/configsl/file_format/shared_spec.rb
|
|
44
45
|
- lib/configsl/file_format/yaml.rb
|
|
45
46
|
- lib/configsl/file_support.rb
|
|
46
47
|
- lib/configsl/format.rb
|
|
47
48
|
- lib/configsl/from_environment.rb
|
|
48
49
|
- lib/configsl/from_file.rb
|
|
50
|
+
- lib/configsl/merge.rb
|
|
51
|
+
- lib/configsl/to_hash.rb
|
|
49
52
|
- lib/configsl/validation.rb
|
|
53
|
+
- lib/configsl/version.rb
|
|
50
54
|
homepage: https://github.com/jamesiarmes/configsl
|
|
51
55
|
licenses:
|
|
52
56
|
- MIT
|
|
@@ -56,7 +60,6 @@ metadata:
|
|
|
56
60
|
homepage_uri: https://github.com/jamesiarmes/configsl
|
|
57
61
|
rubygems_mfa_required: 'true'
|
|
58
62
|
source_code_uri: https://github.com/jamesiarmes/configsl
|
|
59
|
-
post_install_message:
|
|
60
63
|
rdoc_options: []
|
|
61
64
|
require_paths:
|
|
62
65
|
- lib
|
|
@@ -64,15 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
64
67
|
requirements:
|
|
65
68
|
- - ">="
|
|
66
69
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '3.
|
|
70
|
+
version: '3.3'
|
|
68
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
72
|
requirements:
|
|
70
73
|
- - ">="
|
|
71
74
|
- !ruby/object:Gem::Version
|
|
72
75
|
version: '0'
|
|
73
76
|
requirements: []
|
|
74
|
-
rubygems_version:
|
|
75
|
-
signing_key:
|
|
77
|
+
rubygems_version: 4.0.10
|
|
76
78
|
specification_version: 4
|
|
77
79
|
summary: A simple DSL for declarative configuration in ruby.
|
|
78
80
|
test_files: []
|