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.
@@ -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,8 @@
1
+ module ConstConf
2
+ # ConstConf version
3
+ VERSION = '0.0.0'
4
+ VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
7
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
8
+ 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