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,265 @@
|
|
1
|
+
module ConstConf
|
2
|
+
# A tree structure implementation for visualizing ConstConf configuration
|
3
|
+
# hierarchies.
|
4
|
+
#
|
5
|
+
# The Tree class provides a hierarchical representation of ConstConf modules
|
6
|
+
# and settings, allowing for formatted display of configuration data with
|
7
|
+
# metadata including prefixes, environment variable names, default values,
|
8
|
+
# and configuration status. It supports colored output and proper indentation
|
9
|
+
# to show the relationships between nested configuration elements.
|
10
|
+
class Tree
|
11
|
+
class << self
|
12
|
+
include Term::ANSIColor
|
13
|
+
|
14
|
+
# Converts a ConstConf configuration or setting into a tree structure for
|
15
|
+
# display purposes.
|
16
|
+
#
|
17
|
+
# This method takes either a ConstConf module or a specific setting and
|
18
|
+
# transforms it into a hierarchical tree representation that can be used
|
19
|
+
# for visualization or debugging. It delegates to specialized conversion
|
20
|
+
# methods based on the type of the input argument.
|
21
|
+
#
|
22
|
+
# @param configuration_or_setting [Object] the ConstConf module or
|
23
|
+
# setting to convert
|
24
|
+
#
|
25
|
+
# @return [ConstConf::Tree] a tree object representing the configuration
|
26
|
+
# hierarchy
|
27
|
+
#
|
28
|
+
# @raise [ArgumentError] if the argument is neither a ConstConf module
|
29
|
+
# nor a ConstConf::Setting instance
|
30
|
+
def from_const_conf(configuration_or_setting)
|
31
|
+
if configuration?(configuration_or_setting)
|
32
|
+
convert_module(configuration_or_setting)
|
33
|
+
elsif configuration_or_setting.is_a?(ConstConf::Setting)
|
34
|
+
convert_setting(configuration_or_setting)
|
35
|
+
else
|
36
|
+
raise ArgumentError,
|
37
|
+
"argument needs to have type ConstConf::Setting or ConstConf"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Checks whether the given object is a ConstConf module.
|
44
|
+
#
|
45
|
+
# This method determines if the provided object is a Module that includes
|
46
|
+
# the ConstConf concern. It returns true if the object meets these
|
47
|
+
# criteria, and false otherwise. In case a NameError occurs during the
|
48
|
+
# check, it gracefully returns false.
|
49
|
+
#
|
50
|
+
# @param object [ Object ] the object to be checked
|
51
|
+
#
|
52
|
+
# @return [ Boolean ] true if the object is a ConstConf module, false
|
53
|
+
# otherwise
|
54
|
+
def configuration?(object)
|
55
|
+
object.is_a?(Module) && object < ConstConf
|
56
|
+
rescue NameError
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Converts a ConstConf module into a tree-like structure for display
|
61
|
+
# purposes.
|
62
|
+
#
|
63
|
+
# This method transforms a module that includes ConstConf configuration
|
64
|
+
# settings into a hierarchical tree representation. It captures the
|
65
|
+
# module's name, description, prefix, and number of settings, then
|
66
|
+
# recursively processes nested modules and individual settings to build a
|
67
|
+
# complete configuration tree.
|
68
|
+
#
|
69
|
+
# @param modul [Module] the ConstConf module to convert into a tree
|
70
|
+
# structure
|
71
|
+
# @return [ConstConf::Tree] a tree object representing the module's
|
72
|
+
# configuration hierarchy
|
73
|
+
def convert_module(modul)
|
74
|
+
desc = "#{modul.description.full? { italic(' # %s' % it) }}"
|
75
|
+
obj = new("#{modul.name}#{desc}")
|
76
|
+
obj << new("prefix #{modul.prefix.inspect}")
|
77
|
+
obj << new("#{modul.settings.size} settings")
|
78
|
+
|
79
|
+
modul.settings.each do |env_var, setting|
|
80
|
+
obj << convert_setting(setting)
|
81
|
+
end
|
82
|
+
|
83
|
+
modul.constants.sort.each do |const_name|
|
84
|
+
begin
|
85
|
+
const = modul.const_get(const_name)
|
86
|
+
if const.is_a?(Module) && const < ConstConf
|
87
|
+
obj << convert_module(const)
|
88
|
+
end
|
89
|
+
rescue NameError
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
obj
|
94
|
+
end
|
95
|
+
|
96
|
+
# Converts a configuration setting into a tree node with detailed
|
97
|
+
# information.
|
98
|
+
#
|
99
|
+
# This method transforms a given configuration setting into a
|
100
|
+
# hierarchical representation, including its name, description, and
|
101
|
+
# various metadata such as environment variable name, prefix, default
|
102
|
+
# value, and configuration status.
|
103
|
+
#
|
104
|
+
# @param setting [ConstConf::Setting] the configuration setting to convert
|
105
|
+
#
|
106
|
+
# @return [ConstConf::Tree] a tree node representing the configuration setting
|
107
|
+
def convert_setting(setting)
|
108
|
+
desc = "#{setting.description.full? { italic(' # %s' % it) }}"
|
109
|
+
obj = new("#{bold(setting.name)}#{desc}")
|
110
|
+
|
111
|
+
length = (Tins::Terminal.columns / 4).clamp(20..)
|
112
|
+
truncater = -> v { v&.to_s&.truncate_bytes(length) }
|
113
|
+
|
114
|
+
censored = -> s { s.sensitive? ? Tins::NullPlus.new(inspect: '🤫') : s }
|
115
|
+
|
116
|
+
checker = -> r {
|
117
|
+
case r
|
118
|
+
when false, nil
|
119
|
+
'❌'
|
120
|
+
when :unchecked_true
|
121
|
+
'☑️ '
|
122
|
+
else
|
123
|
+
'✅'
|
124
|
+
end
|
125
|
+
}
|
126
|
+
|
127
|
+
setting_info = <<~EOT
|
128
|
+
prefix #{setting.prefix.inspect}
|
129
|
+
env var name #{setting.env_var_name}
|
130
|
+
env var (orig.) #{truncater.(censored.(setting).env_var.inspect)}
|
131
|
+
default #{truncater.(censored.(setting).default_value.inspect)}
|
132
|
+
value #{truncater.(censored.(setting).value.inspect)}
|
133
|
+
sensitive #{setting.sensitive? ? '🔒' : '⚪'}
|
134
|
+
required #{setting.required? ? '🔴' : '⚪'}
|
135
|
+
configured #{setting.configured? ? '🔧' : '⚪' }
|
136
|
+
ignored #{setting.ignored? ? '🙈' : '⚪'}
|
137
|
+
active #{setting.active? ? '🟢' : '⚪'}
|
138
|
+
decoding #{setting.decoding? ? '⚙️' : '⚪'}
|
139
|
+
checked #{checker.(setting.checked?)}
|
140
|
+
EOT
|
141
|
+
|
142
|
+
setting_info.each_line do |line|
|
143
|
+
obj << new(line.chomp)
|
144
|
+
end
|
145
|
+
|
146
|
+
obj
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Initializes a new tree node with the given name, and UTF-8 support
|
151
|
+
# flag.
|
152
|
+
#
|
153
|
+
# @param name [ String ] the name of the tree node
|
154
|
+
# @param utf8 [ Boolean ] flag indicating whether UTF-8 characters should
|
155
|
+
# be used for display
|
156
|
+
def initialize(name, utf8: default_utf8)
|
157
|
+
@name = name
|
158
|
+
@utf8 = utf8
|
159
|
+
@children = []
|
160
|
+
end
|
161
|
+
|
162
|
+
# Checks whether UTF-8 encoding is indicated in the LANG environment
|
163
|
+
# variable.
|
164
|
+
#
|
165
|
+
# This method examines the LANG environment variable to determine if it
|
166
|
+
# contains a UTF-8 encoding indicator, returning true if UTF-8 is detected
|
167
|
+
# and false otherwise.
|
168
|
+
#
|
169
|
+
# @return [Boolean] true if the LANG environment variable indicates UTF-8
|
170
|
+
# encoding, false otherwise
|
171
|
+
def default_utf8
|
172
|
+
!!(ENV['LANG'] =~ /utf-8\z/i)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Adds a child node to this tree node's collection of children.
|
176
|
+
#
|
177
|
+
# @param child [ ConstConf::Tree ] the child tree node to be added
|
178
|
+
#
|
179
|
+
# @return [ Array<ConstConf::Tree> ] the updated array of child nodes
|
180
|
+
def <<(child)
|
181
|
+
@children << child
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns an enumerator that yields the tree node's name,
|
185
|
+
# followed by its children in a hierarchical format.
|
186
|
+
#
|
187
|
+
# @return [Enumerator] an enumerator that provides the tree structure
|
188
|
+
# as a sequence of strings representing nodes and their relationships
|
189
|
+
def to_enum
|
190
|
+
Enumerator.new do |y|
|
191
|
+
y.yield @name
|
192
|
+
|
193
|
+
@children.each_with_index do |child, child_index|
|
194
|
+
children_enum = child.to_enum
|
195
|
+
if child_index < @children.size - 1
|
196
|
+
children_enum.each_with_index do |setting, i|
|
197
|
+
y.yield "#{inner_child_prefix(i)}#{setting}"
|
198
|
+
end
|
199
|
+
else
|
200
|
+
children_enum.each_with_index do |setting, i|
|
201
|
+
y.yield "#{last_child_prefix(i)}#{setting}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Converts the tree node into an array representation.
|
209
|
+
#
|
210
|
+
# @return [Array<String>] an array containing the string representations of
|
211
|
+
# the tree node and its children
|
212
|
+
def to_ary
|
213
|
+
to_enum.to_a
|
214
|
+
end
|
215
|
+
|
216
|
+
alias to_a to_ary
|
217
|
+
|
218
|
+
# Returns the string representation of the tree structure.
|
219
|
+
#
|
220
|
+
# This method converts the tree node and its children into a formatted
|
221
|
+
# string, where each node is represented on its own line with appropriate
|
222
|
+
# indentation to show the hierarchical relationship between nodes.
|
223
|
+
#
|
224
|
+
# @return [String] a multi-line string representation of the tree structure
|
225
|
+
def to_s
|
226
|
+
to_ary * ?\n
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
# Returns the appropriate prefix string for a child node in a tree display.
|
232
|
+
#
|
233
|
+
# This method determines the correct visual prefix to use when rendering
|
234
|
+
# tree nodes, based on whether UTF-8 encoding is enabled and if the current
|
235
|
+
# child is the first one in a group of siblings.
|
236
|
+
#
|
237
|
+
# @param i [ Integer ] the index of the child node in its parent's children list
|
238
|
+
# @return [ String ] the visual prefix string for the child node
|
239
|
+
def inner_child_prefix(i)
|
240
|
+
if @utf8
|
241
|
+
i.zero? ? "├─ " : "│ "
|
242
|
+
else
|
243
|
+
i.zero? ? "+- " : "| "
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns the appropriate prefix string for the last child node in a tree
|
248
|
+
# display.
|
249
|
+
#
|
250
|
+
# This method determines the correct visual prefix to use when rendering
|
251
|
+
# the final child node in a hierarchical tree structure, based on whether
|
252
|
+
# UTF-8 encoding is enabled and if the current child is the first one in a
|
253
|
+
# group of siblings.
|
254
|
+
#
|
255
|
+
# @param i [Integer] the index of the child node in its parent's children list
|
256
|
+
# @return [String] the visual prefix string for the last child node
|
257
|
+
def last_child_prefix(i)
|
258
|
+
if @utf8
|
259
|
+
i.zero? ? "└─ " : " "
|
260
|
+
else
|
261
|
+
i.zero? ? "`- " : " "
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'complex_config'
|
2
|
+
|
3
|
+
module ConstConf::YAMLPlugin
|
4
|
+
include ComplexConfig::Provider::Shortcuts
|
5
|
+
|
6
|
+
def yaml(path, required: false, env: false)
|
7
|
+
if File.exist?(path)
|
8
|
+
ConstConf.monitor.synchronize do
|
9
|
+
config_dir = File.dirname(path)
|
10
|
+
ComplexConfig::Provider.config_dir = config_dir
|
11
|
+
ext = File.extname(path)
|
12
|
+
name = File.basename(path, ext)
|
13
|
+
if env
|
14
|
+
env == true and env = ENV['RAILS_ENV']
|
15
|
+
if env
|
16
|
+
complex_config_with_env(name, env)
|
17
|
+
else
|
18
|
+
raise ConstConf::RequiredValueNotConfigured,
|
19
|
+
"need an environment string specified, via env var RAILS_ENV or manually"
|
20
|
+
end
|
21
|
+
else
|
22
|
+
complex_config(name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
elsif required
|
26
|
+
raise ConstConf::RequiredValueNotConfigured,
|
27
|
+
"YAML file required at path #{path.to_s.inspect}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/const_conf.rb
ADDED
@@ -0,0 +1,401 @@
|
|
1
|
+
require 'tins/xt'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
# A configuration management module that provides environment variable-based
|
5
|
+
# settings with validation and thread-safe operations.
|
6
|
+
#
|
7
|
+
# ConstConf enables defining configuration settings through environment
|
8
|
+
# variables, including support for default values, required validation,
|
9
|
+
# decoding logic, and descriptive metadata. It offers thread-safe registration
|
10
|
+
# and retrieval of configuration values while providing integration with Rails
|
11
|
+
# application initialization cycles.
|
12
|
+
module ConstConf
|
13
|
+
end
|
14
|
+
require 'const_conf/setting_accessor'
|
15
|
+
require 'const_conf/errors'
|
16
|
+
require 'const_conf/setting'
|
17
|
+
require 'const_conf/file_plugin'
|
18
|
+
require 'const_conf/dir_plugin'
|
19
|
+
require 'const_conf/json_plugin'
|
20
|
+
require 'const_conf/yaml_plugin'
|
21
|
+
require 'const_conf/env_dir_extension'
|
22
|
+
require 'const_conf/tree'
|
23
|
+
require 'const_conf/railtie'
|
24
|
+
|
25
|
+
module ConstConf
|
26
|
+
include ConstConf::Errors
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# Returns the monitor instance for thread synchronization.
|
30
|
+
#
|
31
|
+
# This method provides a singleton Monitor instance that can be used to
|
32
|
+
# synchronize access to shared resources across threads. It ensures that
|
33
|
+
# only one thread can execute critical sections of code at a time.
|
34
|
+
#
|
35
|
+
# @return [Monitor] the singleton Monitor instance used for thread synchronization
|
36
|
+
def monitor
|
37
|
+
@monitor ||= Monitor.new
|
38
|
+
end
|
39
|
+
|
40
|
+
# The module_files accessor provides read and write access to the
|
41
|
+
# module_files instance variable.
|
42
|
+
#
|
43
|
+
# This method serves as a getter and setter for the module_files attribute,
|
44
|
+
# which stores a hash mapping modules to their corresponding file paths.
|
45
|
+
#
|
46
|
+
# @return [Hash, nil] the current value of the module_files instance variable
|
47
|
+
attr_accessor :module_files
|
48
|
+
|
49
|
+
# Registers a module-file mapping in the global registry.
|
50
|
+
#
|
51
|
+
# This method associates a module with its corresponding file path in the
|
52
|
+
# global module_files hash. The registration is performed in a thread-safe
|
53
|
+
# manner using the monitor for synchronization.
|
54
|
+
#
|
55
|
+
# @param configuration [ Module ] the module to register
|
56
|
+
# @param file [ String ] the file path associated with the module
|
57
|
+
def register(configuration, file)
|
58
|
+
monitor.synchronize do
|
59
|
+
module_files[configuration] ||= file
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Destroys all registered configuration modules and returns their file
|
64
|
+
# paths.
|
65
|
+
#
|
66
|
+
# This method synchronizes access to the global configuration registry and
|
67
|
+
# removes all registered modules from their respective parent namespaces.
|
68
|
+
# It collects the file paths associated with each module before removing
|
69
|
+
# them, returning an array of the collected file paths.
|
70
|
+
#
|
71
|
+
# @return [Array<String>] an array containing the file paths of the destroyed modules
|
72
|
+
def destroy
|
73
|
+
monitor.synchronize do
|
74
|
+
files = []
|
75
|
+
while pair = ConstConf.module_files.shift
|
76
|
+
modul, file = pair
|
77
|
+
if modul.module_parent.const_defined?(modul.name)
|
78
|
+
modul.module_parent.send(:remove_const, modul.name)
|
79
|
+
end
|
80
|
+
files << file
|
81
|
+
end
|
82
|
+
files
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Reloads all registered configuration modules by destroying them and
|
87
|
+
# re-loading their associated files.
|
88
|
+
#
|
89
|
+
# This method ensures that all configuration modules currently registered
|
90
|
+
# with ConstConf are destroyed and then reloaded from their respective
|
91
|
+
# files. It performs this operation in a thread-safe manner using the
|
92
|
+
# monitor for synchronization.
|
93
|
+
def reload
|
94
|
+
monitor.synchronize { destroy.each { load it } }
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
self.module_files = {}
|
99
|
+
|
100
|
+
extend ActiveSupport::Concern
|
101
|
+
|
102
|
+
class_methods do
|
103
|
+
extend ConstConf::SettingAccessor
|
104
|
+
|
105
|
+
# The plugin method includes a module into the ConstConf::Setting class.
|
106
|
+
#
|
107
|
+
# This method allows for extending the functionality of configuration
|
108
|
+
# settings by including additional modules that provide extra behavior or
|
109
|
+
# methods. It is typically used to add custom validation, encoding, or
|
110
|
+
# other capabilities to settings defined within the ConstConf system.
|
111
|
+
#
|
112
|
+
# @param plugin [ Module ] the module to be included in ConstConf::Setting
|
113
|
+
#
|
114
|
+
# @return [ Class ] returns the current class (self) to allow for method chaining
|
115
|
+
def plugin(plugin)
|
116
|
+
ConstConf::Setting.class_eval { include plugin }
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handles the addition of constants to a module, configuring them as
|
121
|
+
# settings when appropriate.
|
122
|
+
#
|
123
|
+
# This method is invoked automatically when a constant is added to a module
|
124
|
+
# that includes ConstConf. It processes the constant by checking if it's a
|
125
|
+
# Module and including ConstConf in it. If there's a pending configuration
|
126
|
+
# block for the constant, it creates a Setting instance, registers it, and
|
127
|
+
# sets the constant's value accordingly. It also defines helper methods to
|
128
|
+
# query the setting and its configuration status.
|
129
|
+
#
|
130
|
+
# @param id [ Symbol ] the name of the constant being added
|
131
|
+
def const_added(id)
|
132
|
+
if const = const_get(id) and const.is_a?(Module)
|
133
|
+
const.class_eval do
|
134
|
+
include ConstConf
|
135
|
+
end
|
136
|
+
prefix = [ self, *module_parents ].find {
|
137
|
+
!it.prefix.nil? and break it.prefix
|
138
|
+
}
|
139
|
+
const.prefix [ prefix, const.name.sub(/.*::/, '') ].select(&:present?) * ?_
|
140
|
+
end
|
141
|
+
ConstConf.monitor.synchronize do
|
142
|
+
if setting_block = last_setting
|
143
|
+
self.last_setting = nil
|
144
|
+
remove_const(id)
|
145
|
+
prefix = [ self, *module_parents ].find {
|
146
|
+
!it.prefix.nil? and break it.prefix
|
147
|
+
}
|
148
|
+
setting = Setting.new(name: [ name, id ], prefix:, &setting_block)
|
149
|
+
if previous_setting = outer_configuration.setting_for(setting.env_var_name)
|
150
|
+
raise ConstConf::SettingAlreadyDefined,
|
151
|
+
"setting for env var #{setting.env_var_name} already defined in #{previous_setting.name}"
|
152
|
+
end
|
153
|
+
settings[setting.env_var_name] = setting
|
154
|
+
const_set id, setting.value
|
155
|
+
my_configuration = self
|
156
|
+
singleton_class.class_eval {
|
157
|
+
define_method("#{id}!") { setting }
|
158
|
+
my_configuration.send("#{id}!")
|
159
|
+
define_method("#{id}?") { (setting.value if setting.active?) }
|
160
|
+
my_configuration.send("#{id}?")
|
161
|
+
}
|
162
|
+
setting.confirm!
|
163
|
+
end
|
164
|
+
end
|
165
|
+
super if defined? super
|
166
|
+
end
|
167
|
+
|
168
|
+
# Sets or retrieves the description for a ConstConf module.
|
169
|
+
#
|
170
|
+
# This method provides access to the description attribute of a
|
171
|
+
# configuration setting, which can be used to document the purpose and
|
172
|
+
# usage of the module or nested module.
|
173
|
+
#
|
174
|
+
# @return [String, nil] the current description value
|
175
|
+
setting_accessor :description
|
176
|
+
|
177
|
+
# Declares a thread-local variable named last_setting.
|
178
|
+
#
|
179
|
+
# This method sets up a thread-local variable that can be used to store
|
180
|
+
# temporary state within the current thread. It is typically used to
|
181
|
+
# hold transient data that should not persist across threads.
|
182
|
+
#
|
183
|
+
# @param name [Symbol] the name of the thread-local variable to declare
|
184
|
+
# @return [Object]
|
185
|
+
thread_local :last_setting
|
186
|
+
|
187
|
+
# Returns the settings hash for the configuration module.
|
188
|
+
#
|
189
|
+
# This method provides access to the internal hash that stores all configuration
|
190
|
+
# settings defined within the module. It ensures the hash is initialized before
|
191
|
+
# returning it, guaranteeing that subsequent accesses will return the same hash
|
192
|
+
# instance.
|
193
|
+
#
|
194
|
+
# @return [Hash<String, ConstConf::Setting>] the hash containing all settings
|
195
|
+
# for this configuration module, keyed by their environment variable names
|
196
|
+
def settings
|
197
|
+
@settings ||= {}
|
198
|
+
end
|
199
|
+
|
200
|
+
# The prefix reader accessor returns the configured prefix for the setting.
|
201
|
+
#
|
202
|
+
# @return [String, nil] the prefix value, or nil if not set
|
203
|
+
setting_accessor(
|
204
|
+
:prefix,
|
205
|
+
transform: -> value { value.ask_and_send_or_self(:upcase) }
|
206
|
+
) do
|
207
|
+
if module_parent == Object
|
208
|
+
name.underscore.upcase
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Sets the configuration block for the next constant definition.
|
213
|
+
#
|
214
|
+
# @param block [Proc] the configuration block to be stored
|
215
|
+
# @return [nil] always returns nil
|
216
|
+
def set(&block)
|
217
|
+
if module_parent == Object and
|
218
|
+
file = caller_locations.first.absolute_path and
|
219
|
+
File.exist?(file)
|
220
|
+
then
|
221
|
+
ConstConf.register self, file
|
222
|
+
end
|
223
|
+
self.last_setting = block
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
|
227
|
+
# Finds the outer configuration module in the hierarchy.
|
228
|
+
#
|
229
|
+
# This method traverses up the module inheritance chain from the current
|
230
|
+
# module, examining each module in reverse order of its parents, until it
|
231
|
+
# finds a module that includes the ConstConf concern. This is useful for
|
232
|
+
# identifying the top-level configuration module that contains the current
|
233
|
+
# one.
|
234
|
+
#
|
235
|
+
# @return [ Module, nil ] the outer configuration module if found, or nil if none exists
|
236
|
+
def outer_configuration
|
237
|
+
[ self, *module_parents ].reverse_each.find { it < ConstConf }
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns an array containing all nested configuration modules recursively,
|
241
|
+
# including itself.
|
242
|
+
#
|
243
|
+
# This method collects all configuration modules within the current module
|
244
|
+
# and its hierarchy, returning them as an array. It utilizes the
|
245
|
+
# each_nested_configuration iterator to traverse the module structure and
|
246
|
+
# accumulate each configuration module into a result array.
|
247
|
+
#
|
248
|
+
# @return [Array<Module>] an array of all nested configuration modules
|
249
|
+
# found within the current module's hierarchy
|
250
|
+
def all_configurations
|
251
|
+
each_nested_configuration.reduce([]) { |array, configuration|
|
252
|
+
array << configuration
|
253
|
+
}
|
254
|
+
end
|
255
|
+
|
256
|
+
# Returns an array of the directly nested configuration modules that
|
257
|
+
# include ConstConf.
|
258
|
+
#
|
259
|
+
# This method iterates through all constants defined in the current module,
|
260
|
+
# identifies those that are modules and inherit from ConstConf, and returns
|
261
|
+
# them as an array. It filters out any non-module constants or modules that
|
262
|
+
# do not include the ConstConf concern.
|
263
|
+
#
|
264
|
+
# @return [ Array<Module> ] an array containing nested configuration
|
265
|
+
# modules that inherit from ConstConf
|
266
|
+
def nested_configurations
|
267
|
+
constants.map { |c|
|
268
|
+
begin
|
269
|
+
m = const_get(c)
|
270
|
+
rescue NameError
|
271
|
+
next
|
272
|
+
end
|
273
|
+
m.is_a?(Module) or next
|
274
|
+
m < ConstConf or next
|
275
|
+
m
|
276
|
+
}.compact
|
277
|
+
end
|
278
|
+
|
279
|
+
# Finds a configuration setting by its environment variable name across
|
280
|
+
# nested modules.
|
281
|
+
#
|
282
|
+
# This method searches through the specified modules and their nested
|
283
|
+
# modules to locate a configuration setting that matches the given
|
284
|
+
# environment variable name. It iterates through the module hierarchy to
|
285
|
+
# find the setting, checking each module's settings hash for a match.
|
286
|
+
#
|
287
|
+
# @param name [ String, Symbol ] the environment variable name to search for
|
288
|
+
# @param modules [ Array<Module> ] the array of modules to search within, defaults to [ self ]
|
289
|
+
#
|
290
|
+
# @return [ ConstConf::Setting, nil ] the matching setting object if found, or nil if not found
|
291
|
+
def setting_for(name)
|
292
|
+
name = name.to_s
|
293
|
+
each_nested_configuration do |modul,|
|
294
|
+
modul.settings.each { |env_var_name, setting|
|
295
|
+
env_var_name == name and return setting
|
296
|
+
}
|
297
|
+
end
|
298
|
+
nil
|
299
|
+
end
|
300
|
+
|
301
|
+
# Returns an array of all environment variable names used in the
|
302
|
+
# configuration.
|
303
|
+
#
|
304
|
+
# This method collects all the environment variable names from the settings
|
305
|
+
# defined within the current module and its nested modules. It traverses
|
306
|
+
# the module hierarchy to gather these names, ensuring that each setting's
|
307
|
+
# environment variable name is included in the final result.
|
308
|
+
#
|
309
|
+
# @return [Array<String>] an array containing all the environment variable names
|
310
|
+
# used in the configuration settings across the module and its nested modules
|
311
|
+
def env_var_names
|
312
|
+
names = Set[]
|
313
|
+
each_nested_configuration do |modul,|
|
314
|
+
names.merge(Array(modul.settings.keys))
|
315
|
+
end
|
316
|
+
names.to_a
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns a hash containing all environment variable values for the
|
320
|
+
# configuration settings.
|
321
|
+
#
|
322
|
+
# This method collects all the environment variable names from the settings
|
323
|
+
# defined within the current module and its nested modules, then retrieves
|
324
|
+
# the effective value for each setting. The result is a hash where keys are
|
325
|
+
# environment variable names and values are their corresponding
|
326
|
+
# configuration values.
|
327
|
+
#
|
328
|
+
# @return [ Hash<String, Object> ] a hash mapping environment variable names to their values
|
329
|
+
def env_vars
|
330
|
+
env_var_names.each_with_object({}) do |n, hash|
|
331
|
+
hash[n] = setting_value_for(n)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Retrieves the effective value for a configuration setting identified by its
|
336
|
+
# environment variable name.
|
337
|
+
#
|
338
|
+
# This method looks up a configuration setting using the provided environment
|
339
|
+
# variable name and returns its effective value, which is determined by
|
340
|
+
# checking the environment variable and falling back to the default value if
|
341
|
+
# not set.
|
342
|
+
#
|
343
|
+
# @param name [String, Symbol] the environment variable name used to identify the setting
|
344
|
+
#
|
345
|
+
# @return [Object] the effective configuration value for the specified setting,
|
346
|
+
# or the default value if the environment variable is not set
|
347
|
+
def setting_value_for(name)
|
348
|
+
setting = setting_for(name)
|
349
|
+
setting&.value
|
350
|
+
end
|
351
|
+
alias [] setting_value_for
|
352
|
+
|
353
|
+
# Displays a textual representation of a configuration object or setting.
|
354
|
+
#
|
355
|
+
# This method generates a formatted tree-like view of either a ConstConf
|
356
|
+
# module or a specific setting, including metadata such as names,
|
357
|
+
# descriptions, environment variable names, and configuration status. The
|
358
|
+
# output can be directed to a specified IO object or displayed to the
|
359
|
+
# standard output.
|
360
|
+
#
|
361
|
+
# @param object [Object] the ConstConf module or setting to display
|
362
|
+
# @param io [IO, nil] the IO object to write the output to; if nil, uses STDOUT
|
363
|
+
def view(object: self, io: nil)
|
364
|
+
output = ConstConf::Tree.from_const_conf(object).to_a
|
365
|
+
if io
|
366
|
+
io.puts output
|
367
|
+
elsif output.size < Tins::Terminal.lines
|
368
|
+
STDOUT.puts output
|
369
|
+
else
|
370
|
+
IO.popen(ENV.fetch('PAGER', 'less -r'), ?w) do |f|
|
371
|
+
f.puts output
|
372
|
+
f.close_write
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Iterates over a configuration module and its nested configurations in a
|
378
|
+
# depth-first manner.
|
379
|
+
#
|
380
|
+
# This method yields each configuration module, including the receiver and all nested modules,
|
381
|
+
# in a depth-first traversal order. It is used internally to process all configuration
|
382
|
+
# settings across a module hierarchy.
|
383
|
+
#
|
384
|
+
# @yield [ configuration ] yields each configuration module in the hierarchy
|
385
|
+
# @yieldparam configuration [ Module ] a configuration module from the hierarchy
|
386
|
+
#
|
387
|
+
# @return [ Enumerator ] returns an enumerator if no block is given,
|
388
|
+
# otherwise nil.
|
389
|
+
def each_nested_configuration(&block)
|
390
|
+
return enum_for(:each_nested_configuration) unless block_given?
|
391
|
+
configuration_modules = [ self ]
|
392
|
+
while configuration = configuration_modules.shift
|
393
|
+
yield configuration
|
394
|
+
configuration.nested_configurations.each do
|
395
|
+
configuration_modules.member?(it) and next
|
396
|
+
configuration_modules << it
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|