const_conf 0.0.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 +7 -0
- data/Gemfile +5 -0
- data/LICENSE +19 -0
- data/README.md +789 -0
- data/Rakefile +35 -0
- data/const_conf.gemspec +35 -0
- data/lib/const_conf/dir_plugin.rb +175 -0
- data/lib/const_conf/env_dir_extension.rb +83 -0
- data/lib/const_conf/errors.rb +93 -0
- data/lib/const_conf/file_plugin.rb +33 -0
- data/lib/const_conf/json_plugin.rb +12 -0
- data/lib/const_conf/railtie.rb +13 -0
- data/lib/const_conf/setting.rb +382 -0
- data/lib/const_conf/setting_accessor.rb +103 -0
- data/lib/const_conf/tree.rb +265 -0
- data/lib/const_conf/version.rb +8 -0
- data/lib/const_conf/yaml_plugin.rb +30 -0
- data/lib/const_conf.rb +401 -0
- data/spec/assets/.env/API_KEY +1 -0
- data/spec/assets/config.json +3 -0
- data/spec/assets/config.yml +1 -0
- data/spec/assets/config_env.yml +7 -0
- data/spec/const_conf/dir_plugin_spec.rb +249 -0
- data/spec/const_conf/env_dir_extension_spec.rb +59 -0
- data/spec/const_conf/file_plugin_spec.rb +173 -0
- data/spec/const_conf/json_plugin_spec.rb +64 -0
- data/spec/const_conf/setting_accessor_spec.rb +114 -0
- data/spec/const_conf/setting_spec.rb +628 -0
- data/spec/const_conf/tree_spec.rb +185 -0
- data/spec/const_conf/yaml_plugin_spec.rb +84 -0
- data/spec/const_conf_spec.rb +216 -0
- data/spec/spec_helper.rb +140 -0
- metadata +240 -0
@@ -0,0 +1,382 @@
|
|
1
|
+
# A configuration setting definition that encapsulates the properties and
|
2
|
+
# behavior of a single environment variable-based setting.
|
3
|
+
#
|
4
|
+
# The Setting class provides a structured way to define, validate, and
|
5
|
+
# retrieve configuration values from environment variables, including support
|
6
|
+
# for default values, required validation, decoding logic, and descriptive
|
7
|
+
# metadata.
|
8
|
+
#
|
9
|
+
# @example Defining a configuration setting
|
10
|
+
# Setting.new(name: 'DATABASE_URL', prefix: 'APP') { |s|
|
11
|
+
# description = 'The database connection string'
|
12
|
+
# required true
|
13
|
+
# }
|
14
|
+
class ConstConf::Setting
|
15
|
+
extend ConstConf::SettingAccessor
|
16
|
+
include DSLKit::BlockSelf
|
17
|
+
|
18
|
+
# Initializes a new Setting instance with the given name and prefix.
|
19
|
+
#
|
20
|
+
# @param name [Array<String>, String] the name components for the setting
|
21
|
+
# @param prefix [String, nil] the optional upcassed prefix to use when
|
22
|
+
# constructing environment variable names
|
23
|
+
# @yield [] optional block to configure the setting
|
24
|
+
def initialize(name:, prefix: nil, &block)
|
25
|
+
@parent_namespace = block_self(&block)
|
26
|
+
@name = Array(name) * '::'
|
27
|
+
self.prefix(prefix)
|
28
|
+
|
29
|
+
block and self.class.enable_setter_mode { instance_eval(&block) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# The name reader accessor returns the name of the setting.
|
33
|
+
#
|
34
|
+
# @return [String] the constructed environment variable name
|
35
|
+
attr_reader :name
|
36
|
+
|
37
|
+
# The parent_namespace reader accessor returns the parent namespace of the
|
38
|
+
# setting.
|
39
|
+
#
|
40
|
+
# @return [Module, nil] the parent namespace module, or nil if not set
|
41
|
+
attr_reader :parent_namespace
|
42
|
+
|
43
|
+
# The prefix reader accessor returns the configured prefix for the setting.
|
44
|
+
#
|
45
|
+
# @return [String, nil] the prefix value, or nil if not set
|
46
|
+
setting_accessor :prefix,
|
47
|
+
transform: -> value { value.ask_and_send_or_self(:upcase) }
|
48
|
+
|
49
|
+
# The activated reader and writer accessor for configuration settings.
|
50
|
+
#
|
51
|
+
# This method provides access to the activated state of a configuration
|
52
|
+
# setting, which determines whether the setting should be considered active
|
53
|
+
# based on its current value or other conditions. It defaults to `:present?`
|
54
|
+
# of not set.
|
55
|
+
#
|
56
|
+
# @return [Object] the updated activated state value
|
57
|
+
# @see #active?
|
58
|
+
setting_accessor :activated, :present?
|
59
|
+
|
60
|
+
# Checks if the configuration setting is active based on its activated state.
|
61
|
+
#
|
62
|
+
# This method evaluates whether the configuration setting is considered
|
63
|
+
# active by examining its activated property. If activated is set to true, it
|
64
|
+
# checks if the setting's value is present. If activated is a Symbol, it
|
65
|
+
# sends that symbol as a message to the value and returns the result. If
|
66
|
+
# activated is a Proc, it evaluates the proc with or without the value
|
67
|
+
# depending on its arity. For any other value, it returns false.
|
68
|
+
#
|
69
|
+
# @return [Boolean] true if the setting is active according to its activated
|
70
|
+
# state, false otherwise
|
71
|
+
def active?
|
72
|
+
case activated
|
73
|
+
when true
|
74
|
+
value.present?
|
75
|
+
when Symbol
|
76
|
+
!!value.send(activated)
|
77
|
+
when Proc
|
78
|
+
if activated.arity == 1
|
79
|
+
!!activated.(value)
|
80
|
+
else
|
81
|
+
!!activated.()
|
82
|
+
end
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets or retrieves the confirmation check logic for the configuration
|
89
|
+
# setting.
|
90
|
+
#
|
91
|
+
# This method provides access to the check configuration, which is used to
|
92
|
+
# validate that a setting meets certain criteria beyond basic required and
|
93
|
+
# default value checks. The check can be a Proc that evaluates to true,
|
94
|
+
# :unchecked_true (truthy), or false, allowing for custom validation logic. It
|
95
|
+
# deffaults to true, if not set otherwise.
|
96
|
+
#
|
97
|
+
# @return [Proc, Object] the current check configuration value
|
98
|
+
setting_accessor :check, -> setting { :unchecked_true }
|
99
|
+
|
100
|
+
# Checks if the configuration setting passes its validation check.
|
101
|
+
#
|
102
|
+
# @return [Boolean, Symbol] true if the setting's check logic evaluates to true,
|
103
|
+
# false or false if not. I no check was defined, returns :unchecked_true.
|
104
|
+
# @see check
|
105
|
+
def checked?
|
106
|
+
instance_eval(&check)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Checks if a configuration setting has been provided with a valid value.
|
110
|
+
#
|
111
|
+
# @return [Boolean] true if the setting has either an environment variable
|
112
|
+
# value or a default value that is not nil, false otherwise
|
113
|
+
def value_provided?
|
114
|
+
!configured_value_or_default_value.nil?
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sets whether the setting is required.
|
118
|
+
#
|
119
|
+
# @param value [Boolean, Proc] the value to set for the required flag
|
120
|
+
# - true/false: Simple boolean requirement check
|
121
|
+
# - Proc: Dynamic validation logic that can be evaluated in two ways:
|
122
|
+
# * With arity 1: Called with the setting's value (e.g., `->(value) { value.present? }`)
|
123
|
+
# * With arity 0: Called without arguments (e.g., `-> { some_value.present? }`)
|
124
|
+
# @return [Boolean, Proc] returns the value that was set
|
125
|
+
# @method required(value = nil, &block)
|
126
|
+
# @see #required?
|
127
|
+
setting_accessor :required, false
|
128
|
+
|
129
|
+
# Checks if the setting has a required value configured or as a default
|
130
|
+
# value.
|
131
|
+
#
|
132
|
+
# This method evaluates whether the configuration setting is marked as required
|
133
|
+
# and determines if a valid value is present. It handles different forms of
|
134
|
+
# required specification including boolean flags and Proc objects that can
|
135
|
+
# perform dynamic validation based on the current value or context.
|
136
|
+
#
|
137
|
+
# @return [Boolean] true if the setting is marked as required and has a valid
|
138
|
+
# value according to its validation logic, false otherwise
|
139
|
+
def required?
|
140
|
+
!!case required
|
141
|
+
when Proc
|
142
|
+
if required.arity == 1
|
143
|
+
required.(configured_value_or_default_value)
|
144
|
+
else
|
145
|
+
required.()
|
146
|
+
end
|
147
|
+
else
|
148
|
+
required
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Checks if the setting has a required value configured.
|
153
|
+
#
|
154
|
+
# @return [Boolean] true if the setting is marked as required and has a valid
|
155
|
+
# value, false otherwise
|
156
|
+
setting_accessor :sensitive, false
|
157
|
+
|
158
|
+
alias sensitive? sensitive
|
159
|
+
|
160
|
+
# Sets or retrieves the description for the configuration setting.
|
161
|
+
#
|
162
|
+
# @return [String] the current description value
|
163
|
+
setting_accessor :description
|
164
|
+
|
165
|
+
# Sets or retrieves the decoding configuration for the setting.
|
166
|
+
#
|
167
|
+
# @param value [Proc, nil] the decoding configuration value
|
168
|
+
# - Proc: A function that transforms the raw environment variable value
|
169
|
+
# - nil: No decoding applied
|
170
|
+
# @return [Proc, nil] the current decoding configuration value
|
171
|
+
# @method decode(value = nil)
|
172
|
+
# @see #decoding?
|
173
|
+
setting_accessor :decode
|
174
|
+
|
175
|
+
# Checks if the setting has decoding logic configured.
|
176
|
+
|
177
|
+
# @return [Boolean] true if the setting has a Proc-based decoding configuration,
|
178
|
+
# false otherwise
|
179
|
+
def decoding?
|
180
|
+
!!decode&.is_a?(Proc)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Sets the default value for the configuration option.
|
184
|
+
#
|
185
|
+
# @param value [Object] the default value to use when no environment
|
186
|
+
# variable is set
|
187
|
+
setting_accessor :default
|
188
|
+
|
189
|
+
# Returns the default value for the configuration option, evaluating it if
|
190
|
+
# it's a Proc.
|
191
|
+
#
|
192
|
+
# @return [Object] the default value, or the result of evaluating the
|
193
|
+
# default if it's a Proc
|
194
|
+
def default_value
|
195
|
+
case default
|
196
|
+
when Proc
|
197
|
+
default.()
|
198
|
+
else
|
199
|
+
default
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Sets whether this configuration option should be ignored when reading
|
204
|
+
# values from environment variables with name env_var_name.
|
205
|
+
#
|
206
|
+
# @param value [Boolean] true if the configuration option should be
|
207
|
+
# ignored, false otherwise
|
208
|
+
setting_accessor :ignored
|
209
|
+
|
210
|
+
# Checks if the configuration setting is marked as ignored.
|
211
|
+
#
|
212
|
+
# This method returns true if the setting has been explicitly marked to be
|
213
|
+
# ignored when reading values from environment variables, indicating that
|
214
|
+
# it should be skipped during configuration processing.
|
215
|
+
#
|
216
|
+
# @return [Boolean] true if the setting is ignored, false otherwise
|
217
|
+
def ignored?
|
218
|
+
!!ignored
|
219
|
+
end
|
220
|
+
|
221
|
+
# Generates the environment variable name for this configuration option by
|
222
|
+
# constructing it from the configured prefix and name components, replacing
|
223
|
+
# namespace separators with underscores.
|
224
|
+
#
|
225
|
+
# @return [String] the constructed environment variable name
|
226
|
+
def env_var_name
|
227
|
+
prefix = @prefix.full? { "#{it}::" }.to_s
|
228
|
+
name.sub(/^#{parent_namespace}::/, prefix).gsub(/::/, ?_)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Retrieves the value of the environment variable associated with this
|
232
|
+
# configuration option.
|
233
|
+
#
|
234
|
+
# @return [String, nil] the value of the environment variable if it exists,
|
235
|
+
# or nil if not set
|
236
|
+
def env_var
|
237
|
+
ENV[env_var_name]
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns the configured value for the setting, considering ignore status and
|
241
|
+
# environment variable presence.
|
242
|
+
#
|
243
|
+
# @return [String, nil] the environment variable value if the setting is not
|
244
|
+
# ignored and the environment variable is set, nil otherwise
|
245
|
+
def configured_value
|
246
|
+
if ignored
|
247
|
+
nil
|
248
|
+
elsif env_var.nil?
|
249
|
+
nil
|
250
|
+
else
|
251
|
+
env_var
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Checks if the configuration setting has been configured with a value.
|
256
|
+
#
|
257
|
+
# This method determines whether the configuration setting has been provided
|
258
|
+
# with a value, either through an environment variable or a default value.
|
259
|
+
# It returns true if a valid value is present, and false otherwise.
|
260
|
+
#
|
261
|
+
# @return [Boolean] true if the setting has been configured with a value,
|
262
|
+
# false otherwise
|
263
|
+
def configured?
|
264
|
+
!configured_value.nil?
|
265
|
+
end
|
266
|
+
|
267
|
+
# Returns the configured value for the setting, or falls back to the default
|
268
|
+
# value if not configured.
|
269
|
+
#
|
270
|
+
# @return [Object, nil] the configured value if present, otherwise the
|
271
|
+
# default value, or nil if neither is set
|
272
|
+
def configured_value_or_default_value
|
273
|
+
configured_value || default_value
|
274
|
+
end
|
275
|
+
|
276
|
+
# Retrieves the effective value for the configuration setting.
|
277
|
+
#
|
278
|
+
# This method determines the appropriate value for the configuration
|
279
|
+
# setting by first checking if the setting is not ignored and an
|
280
|
+
# environment variable value is present. If so, it uses the environment
|
281
|
+
# variable value. Otherwise, it falls back to the default value. The
|
282
|
+
# resulting value is then passed through any configured decoding logic.
|
283
|
+
#
|
284
|
+
# @return [Object] the effective configuration value after applying any
|
285
|
+
# decoding logic, or the default value if no environment variable is set
|
286
|
+
def value
|
287
|
+
decoded_value(configured_value_or_default_value)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Confirms that the configuration setting and its parent module meet all
|
291
|
+
# required criteria.
|
292
|
+
#
|
293
|
+
# This method ensures that the setting has a description, that its parent
|
294
|
+
# module (if it's a ConstConf module) has a description, that any required
|
295
|
+
# values are provided, and that the setting passes its confirmation check. It
|
296
|
+
# raises appropriate exceptions if any of these validation rules are not
|
297
|
+
# satisfied.
|
298
|
+
#
|
299
|
+
# @return [ ConstConf::Setting ] returns self if all validations pass
|
300
|
+
#
|
301
|
+
# @raise [ ConstConf::RequiredDescriptionNotConfigured ] if the setting's description
|
302
|
+
# is blank or the parent module's description is missing
|
303
|
+
# @raise [ ConstConf::RequiredValueNotConfigured ] if the setting is required but no
|
304
|
+
# value is provided
|
305
|
+
# @raise [ ConstConf::SettingCheckFailed ] if the setting's check fails
|
306
|
+
def confirm!
|
307
|
+
if parent_namespace.is_a?(Module) && parent_namespace < ConstConf
|
308
|
+
parent_namespace.description.present? or
|
309
|
+
raise ConstConf::RequiredDescriptionNotConfigured,
|
310
|
+
"required description for ConstConf module #{parent_namespace} not configured"
|
311
|
+
end
|
312
|
+
if description.blank?
|
313
|
+
raise ConstConf::RequiredDescriptionNotConfigured,
|
314
|
+
"required description for setting #{env_var_name} not configured"
|
315
|
+
end
|
316
|
+
if required? && !value_provided?
|
317
|
+
raise ConstConf::RequiredValueNotConfigured,
|
318
|
+
"required value for #{env_var_name} not configured"
|
319
|
+
end
|
320
|
+
unless checked?
|
321
|
+
raise ConstConf::SettingCheckFailed, "check failed for #{name} setting"
|
322
|
+
end
|
323
|
+
self
|
324
|
+
end
|
325
|
+
|
326
|
+
# Displays a textual representation of this configuration setting.
|
327
|
+
#
|
328
|
+
# This method generates a formatted tree-like view of the current configuration
|
329
|
+
# setting, including its name, description, environment variable name, and
|
330
|
+
# associated metadata such as prefix, default value, and configuration status.
|
331
|
+
# The output can be directed to a specified IO object or displayed to the
|
332
|
+
# standard output.
|
333
|
+
#
|
334
|
+
# @param io [IO, nil] the IO object to write the output to; if nil, uses STDOUT
|
335
|
+
def view(io: nil)
|
336
|
+
parent_namespace.view(object: self, io:)
|
337
|
+
end
|
338
|
+
|
339
|
+
# Returns the string representation of the tree structure.
|
340
|
+
#
|
341
|
+
# This method generates a formatted string representation of the tree node
|
342
|
+
# and its children, including the node's name and any associated value, with
|
343
|
+
# proper indentation to show the hierarchical relationship between nodes.
|
344
|
+
#
|
345
|
+
# @return [String] a multi-line string representation of the tree structure
|
346
|
+
def to_s
|
347
|
+
io = StringIO.new
|
348
|
+
view(io:)
|
349
|
+
io.string
|
350
|
+
end
|
351
|
+
|
352
|
+
alias inspect_original inspect
|
353
|
+
|
354
|
+
# The inspect method returns a string representation of the object, with
|
355
|
+
# special handling for IRB colorization.
|
356
|
+
#
|
357
|
+
# @return [String] the string representation of the object,
|
358
|
+
# optionally stripped of ANSI color codes when used in IRB with
|
359
|
+
# colorization enabled
|
360
|
+
def inspect
|
361
|
+
if defined?(IRB) && IRB.conf[:USE_COLORIZE]
|
362
|
+
Term::ANSIColor.uncolor(to_s)
|
363
|
+
else
|
364
|
+
to_s
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
private
|
369
|
+
|
370
|
+
# Decodes a value using the configured decoder if present, otherwise returns the value as-is.
|
371
|
+
#
|
372
|
+
# @param value_arg [Object] the value to be decoded
|
373
|
+
# @return [Object] the decoded value or the original value if no decoding is
|
374
|
+
# configured
|
375
|
+
def decoded_value(value_arg)
|
376
|
+
if decode.is_a?(Proc)
|
377
|
+
decode.(value_arg)
|
378
|
+
else
|
379
|
+
value_arg
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# A module that provides functionality for defining setting accessors within
|
2
|
+
# configuration modules.
|
3
|
+
#
|
4
|
+
# The SettingAccessor module enables the creation of dynamic getter and setter
|
5
|
+
# methods for configuration settings, supporting features like default values,
|
6
|
+
# block evaluation, and lazy initialization. It is designed to be included in
|
7
|
+
# classes or modules that need to manage configuration properties with ease and
|
8
|
+
# consistency.
|
9
|
+
#
|
10
|
+
# @example Defining a setting accessor
|
11
|
+
# class MyClass
|
12
|
+
# extend ConstConf::SettingAccessor
|
13
|
+
# setting_accessor :my_setting, 'default_value'
|
14
|
+
# end
|
15
|
+
module ConstConf::SettingAccessor
|
16
|
+
# @!attribute [rw] setter_mode
|
17
|
+
# Thread-local flag that controls behavior during configuration block
|
18
|
+
# evaluation.
|
19
|
+
#
|
20
|
+
# When {setter_mode} is true (during {ConstConf::Setting} initialization),
|
21
|
+
# the accessor enforces that required settings must be explicitly provided
|
22
|
+
# rather than falling back to defaults. This prevents accidental
|
23
|
+
# configuration of settings with nil values when they should be explicitly
|
24
|
+
# set.
|
25
|
+
#
|
26
|
+
# @see ConstConf::SettingAccessor#setting_accessor
|
27
|
+
# @see ConstConf::Setting#initialize
|
28
|
+
# @return [Boolean] true when in setter mode, false otherwise
|
29
|
+
thread_local :setter_mode, false
|
30
|
+
|
31
|
+
# Enables setter mode for configuration block evaluation.
|
32
|
+
#
|
33
|
+
# This method temporarily sets the setter_mode flag to true, which affects
|
34
|
+
# how setting accessors behave during configuration block evaluation. When
|
35
|
+
# setter mode is active, certain validation rules are applied to ensure that
|
36
|
+
# required settings are explicitly provided rather than falling back to
|
37
|
+
# defaults.
|
38
|
+
#
|
39
|
+
# @return [Object] the return value of the block passed to this method
|
40
|
+
def enable_setter_mode
|
41
|
+
old, self.setter_mode = self.setter_mode, true
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
self.setter_mode = old
|
45
|
+
end
|
46
|
+
|
47
|
+
# Defines a setting accessor method that creates a dynamic getter/setter for
|
48
|
+
# configuration values.
|
49
|
+
#
|
50
|
+
# This method generates a singleton method that provides access to a
|
51
|
+
# configuration setting, supporting default values, block evaluation for
|
52
|
+
# defaults, and lazy initialization of the setting's value. The generated
|
53
|
+
# method can be used to retrieve or set the value of the configuration
|
54
|
+
# setting, with support for different types of default values including Proc
|
55
|
+
# objects which are evaluated when needed.
|
56
|
+
#
|
57
|
+
# @param name [Symbol] the name of the setting accessor to define
|
58
|
+
# @param default [Object] the default value for the setting, can be a Proc that gets evaluated
|
59
|
+
# @yield [] optional block to evaluate for default value when no explicit default is provided
|
60
|
+
# @return [Symbol] always returns the name as Symbol as it defines a method
|
61
|
+
def setting_accessor(name, default = nil, transform: nil, &block)
|
62
|
+
variable = "@#{name}"
|
63
|
+
define_method(name) do |arg = :not_set, &arg_block|
|
64
|
+
was_not_set = if arg.equal?(:not_set)
|
65
|
+
arg = nil
|
66
|
+
true
|
67
|
+
else
|
68
|
+
false
|
69
|
+
end
|
70
|
+
if arg_block
|
71
|
+
arg.nil? or raise ArgumentError,
|
72
|
+
'only either block or positional argument allowed'
|
73
|
+
arg = arg_block
|
74
|
+
end
|
75
|
+
if arg.nil?
|
76
|
+
if self.class.respond_to?(:setter_mode)
|
77
|
+
if self.class.setter_mode && was_not_set
|
78
|
+
raise ArgumentError,
|
79
|
+
"need an argument for the setting #{name.inspect} of #{self}, was nil"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
result =
|
83
|
+
if instance_variable_defined?(variable)
|
84
|
+
instance_variable_get(variable)
|
85
|
+
end
|
86
|
+
if result.nil?
|
87
|
+
result = if default.nil?
|
88
|
+
block && instance_eval(&block)
|
89
|
+
elsif default
|
90
|
+
default
|
91
|
+
end
|
92
|
+
instance_variable_set(variable, result)
|
93
|
+
result
|
94
|
+
else
|
95
|
+
result
|
96
|
+
end
|
97
|
+
else
|
98
|
+
arg = transform.(arg) if transform
|
99
|
+
instance_variable_set(variable, arg)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|