const_conf 0.8.1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bd3638ecc438cef844c87bf0cceeaa962a6863091724d71c3f8f278bada70f6
4
- data.tar.gz: bd088bffdd006036e972c563d7551fa487b715e44b22a245e4071faa511d246c
3
+ metadata.gz: bce56d92973da4818e0e6fcd37343ea5e27a3902d0dbec7f6ad352dec69705ba
4
+ data.tar.gz: 317afdeca380f7847900eb421f06ced5342b60776a1c370c194408a3536b8285
5
5
  SHA512:
6
- metadata.gz: 9f3909e1e564af3d3fc333d32e07f533642e5d2cac974beb0fb4479417621bc43a13e31a833d83889fa55fb900bbe105e839197385026431ac0cae90cdd22d1d
7
- data.tar.gz: 740272c69d8b046e08131c80d9df806dd12eb12939f6256e357a4815157cc6878cae076eba4835395d4bf271f59fa948e63c22311faf169036d70070db785ff4
6
+ metadata.gz: 8591d5703c6fbe796e220c8c3ecf31330e12981b3cf3806e191cde8efe0bd5410d762d37e4d7fcf7f55b746080155d4a795361470efbc4562553de48b0de9158
7
+ data.tar.gz: 628a002745c8cd17970496b51dc977bab3d6cabd662afe02c7d14286594bb2f75f58424fc8aa7eaac7f114ab1ebe6c1c0c077cf9790e92e6ab4e9000b52d69b6
data/CHANGES.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-10-01 v0.9.0
4
+
5
+ * Add `pager:` keyword argument to `ConstConf.view` and `Setting#view`,
6
+ allowing callers to override the pager command used for long output;
7
+ defaults to `ENV['PAGER']` or `less -r`
8
+ * Add YARD `@param pager` documentation to both `view` methods
9
+ * Add spec verifying that a custom `pager:` string is passed through to
10
+ `IO.popen`
11
+
12
+ ## 2026-08-20 v0.8.2
13
+
14
+ * Documented block calling contexts for `decode`, `required`, and `check` in
15
+ the README, clarifying differences between plain `Proc` calls and
16
+ `instance_eval` within the `Setting` context.
17
+ * Improved test result feedback by adding emoji-based status messages and
18
+ descriptive cleanup steps, including conditional checks using the `RESULT`
19
+ variable and cleaner file deletion commands.
20
+ * Updated setting validation documentation to clarify `instance_eval` arity
21
+ rules for the `check` block, fixed typos in `required?` and `sensitive`
22
+ accessor descriptions, and clarified that `sensitive` masks values in
23
+ `view` output.
24
+ * Corrected guidance for guarding absent values to use `value.blank?` instead
25
+ of `value.nil?`.
26
+ * Rewrote the `?` predicate section in the README to explain the `FOO::BAR`
27
+ (constant) and `FOO::BAR?` (predicate) access pattern, and added a "How
28
+ concepts compose" section with a scenario table for `confirm!`, constants,
29
+ and predicates.
30
+ * Fixed a typo in the README truthy value description, correcting "truty" to
31
+ `truthy`.
32
+ * Added a YARD note to `ConstConf::Setting#checked?` explaining that it is
33
+ evaluated unconditionally in `ConstConf::Setting#confirm!`, even when no
34
+ value is supplied.
35
+ * Clarified the distinction between `required` and blank values in the
36
+ README, noting that `required true` only guards against `nil`, and updated
37
+ the `@raise` YARD doc for `ConstConf::RequiredValueNotConfigured` to
38
+ explicitly state that blank values satisfy the check.
39
+
3
40
  ## 2026-08-04 v0.8.1
4
41
 
5
42
  ### Changed
data/README.md CHANGED
@@ -99,8 +99,18 @@ end
99
99
 
100
100
  ### Note that **Predicate Methods (`?`)** are defined
101
101
 
102
- If a setting is `active?`, the predicate method returns a truthy value, which
103
- then can be used like this:
102
+ For each setting, ConstConf defines a deliberate pair:
103
+
104
+ - **`FOO::BAR`** — the constant. Holds the resolved value from `ENV` or
105
+ `default`. Always returns whatever was configured (or `nil`). This is
106
+ what the environment variable *is*.
107
+ - **`FOO::BAR?`** — the predicate. Returns the value *only if* `active?`
108
+ is true; otherwise returns `nil`. This is what your code is *allowed to
109
+ use*.
110
+
111
+ They share the same name because they share the same source. The predicate
112
+ is an opt-in gate on the constant, letting you evaluate the configuration
113
+ conditionally:
104
114
 
105
115
  ```ruby
106
116
  # Check if active?
@@ -111,7 +121,8 @@ else
111
121
  end
112
122
  ```
113
123
 
114
- Or `nil` is returned, which can then be handled accordingly.
124
+ When `active?` is false (value is `nil` or blank under the default
125
+ `:present?` activation), the predicate short-circuits to `nil`.
115
126
 
116
127
  ### Note that **Getter Methods (`!`)** are also defined
117
128
 
@@ -158,7 +169,7 @@ Key indicators in the view:
158
169
  | 🔴 | ⚪ | Required settings that must be configured|
159
170
  | 🔧 | ⚪ | Configured values from ENV |
160
171
  | 🙈 | ⚪ | ENV var was ignored |
161
- | 🟢 | ⚪ | Setting is active (? method returns truty value) |
172
+ | 🟢 | ⚪ | Setting is active (? method returns truthy value) |
162
173
  | ⚙️ | ⚪ | Decoding logic applied to transform raw input |
163
174
  | ✅ ┊ ☑️ | ❌ | Validation checks have passed or failed |
164
175
 
@@ -357,6 +368,10 @@ functionality.
357
368
  - **Usage**: `decode(&:chomp)` removes whitespace, `decode { YAML.load(it) }`
358
369
  parses YAML
359
370
  - **Indicator**: Shows ⚙️ in view output when active
371
+ - **Block context**: Unlike `check`, the decode block is called as a plain
372
+ Proc (`decode.(raw_value)`). The block receives the raw (pre-decode) value
373
+ from the environment variable or default. Use `it` or a block parameter to
374
+ access the value.
360
375
 
361
376
  #### **Required** (`required?`)
362
377
 
@@ -366,6 +381,14 @@ functionality.
366
381
  - **Usage**: `required true` (always required) or `required { !Rails.env.test?
367
382
  }` (conditional)
368
383
  - **Indicator**: Shows 🔴 in view output when required and not satisfied
384
+ - **Note**: `required true` only guards against `nil`. A blank string like
385
+ `""` will pass `confirm!` but cause the `?` predicate to return `nil`.
386
+ Use `check { value.present? }` or `required ->(v) { v.present? }` if you
387
+ need non-blank validation.
388
+ - **Block context**: A Proc-based `required` is called with the raw
389
+ (pre-decode) value when arity is 1: `required ->(v) { v.present? }`.
390
+ An arity-0 Proc runs without arguments:
391
+ `required { !Rails.env.test? }`.
369
392
 
370
393
  #### **Configured** (`configured?`)
371
394
 
@@ -385,6 +408,13 @@ functionality.
385
408
  - **☑️** = No custom check defined (passes by default - `:unchecked_true`)
386
409
  - **✅** = Custom check explicitly passes (returns `true`)
387
410
  - **❌** = Custom check explicitly fails (returns `false`)
411
+ - **Note**: `checked?` is evaluated unconditionally in `confirm!`, even
412
+ when no value is supplied. If your check should pass for absent values,
413
+ guard with `value.nil? ||`.
414
+ - **Block context**: The check block is evaluated via `instance_eval` in the
415
+ Setting's context, so bare method calls like `value` and `configured?`
416
+ resolve to this setting. An arity-1 block (`->(s) { ... }`) also receives
417
+ the Setting instance as its argument.
388
418
 
389
419
  #### **Active** (`active?`)
390
420
 
@@ -409,6 +439,42 @@ These concepts work together to provide a comprehensive configuration
409
439
  management system that tracks the complete lifecycle and status of each setting
410
440
  from definition through validation and usage.
411
441
 
442
+ ### How concepts compose
443
+
444
+ `FOO::BAR?` is defined as `(setting.value if setting.active?)`. So it returns
445
+ a truthy value only when `active?` is true *and* `value` is truthy. With the
446
+ default `activated` of `:present?`, that means the resolved value must be
447
+ non-nil and non-blank.
448
+
449
+ This is a different question from `confirm!`. At definition time, `confirm!`
450
+ asks: "did you supply a non-nil value?" (`required` + `value_provided?`).
451
+ At usage time, `FOO::BAR?` asks: "is this value actually usable?" (`active?`).
452
+ A setting can pass `confirm!` (value exists) yet `FOO::BAR?` still returns
453
+ `nil` (value is blank). These are independent axes, not redundant ones.
454
+
455
+ For example:
456
+
457
+ ```ruby
458
+ module FOO
459
+ include ConstConf
460
+ description 'Foo config'
461
+
462
+ BAR = set do
463
+ description 'A bar setting'
464
+ required true
465
+ end
466
+ end
467
+ ```
468
+
469
+ | Scenario | `confirm!` | `FOO::BAR` | `FOO::BAR?` |
470
+ |---|---|---|---|
471
+ | `ENV['FOO_BAR']` unset | ❌ raises `RequiredValueNotConfigured` | — | — |
472
+ | `ENV['FOO_BAR'] = ""` | ✅ passes (non-nil) | `""` | `nil` (blank → not active) |
473
+ | `ENV['FOO_BAR'] = "hello"` | ✅ passes | `"hello"` | `"hello"` |
474
+
475
+ This shows how `required`, `check`, and `active?` each guard a different
476
+ aspect: existence, validity, and usability.
477
+
412
478
  ### Advanced Usage Examples
413
479
 
414
480
  #### Nested Configuration Modules
data/const_conf.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: const_conf 0.8.1 ruby lib
2
+ # stub: const_conf 0.9.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "const_conf".freeze
6
- s.version = "0.8.1".freeze
6
+ s.version = "0.9.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.licenses = ["MIT".freeze]
18
18
  s.rdoc_options = ["--title".freeze, "ConstConf - Clean DSL for config settings with validation and Rails integration".freeze, "--main".freeze, "README.md".freeze]
19
19
  s.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze)
20
- s.rubygems_version = "4.0.17".freeze
20
+ s.rubygems_version = "4.0.20".freeze
21
21
  s.summary = "Clean DSL for config settings with validation and Rails integration".freeze
22
22
  s.test_files = ["spec/const_conf/dir_plugin_spec.rb".freeze, "spec/const_conf/env_dir_extension_spec.rb".freeze, "spec/const_conf/file_plugin_spec.rb".freeze, "spec/const_conf/json_plugin_spec.rb".freeze, "spec/const_conf/setting_accessor_spec.rb".freeze, "spec/const_conf/setting_spec.rb".freeze, "spec/const_conf/tree_spec.rb".freeze, "spec/const_conf/yaml_plugin_spec.rb".freeze, "spec/const_conf_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
23
 
@@ -92,16 +92,32 @@ class ConstConf::Setting
92
92
  # validate that a setting meets certain criteria beyond basic required and
93
93
  # default value checks. The check can be a Proc that evaluates to true,
94
94
  # :unchecked_true (truthy), or false, allowing for custom validation logic. It
95
- # deffaults to true, if not set otherwise.
95
+ # defaults to true, if not set otherwise.
96
96
  #
97
97
  # @return [Proc, Object] the current check configuration value
98
98
  setting_accessor :check, -> setting { :unchecked_true }
99
99
 
100
100
  # Checks if the configuration setting passes its validation check.
101
101
  #
102
+ # The check block is evaluated via {#instance_eval} in the context of this
103
+ # Setting instance, so bare method calls (e.g. `value`, `configured?`)
104
+ # resolve to this setting. Standard Ruby `instance_eval` arity rules apply:
105
+ #
106
+ # - **Arity 0**: The block runs with `self` set to this Setting instance
107
+ # and receives no arguments. Access setting properties directly:
108
+ # `check { value.blank? || value.scheme == 'redis' }`
109
+ #
110
+ # - **Arity 1**: The block receives this Setting instance as its sole
111
+ # argument as well. Use it when you prefer explicit receiver access:
112
+ # `check ->(s) { s.value.blank? || s.value.scheme == 'redis' }`
113
+ #
114
+ # Note: {#checked?} is evaluated unconditionally in {#confirm!}, even when
115
+ # no value is supplied. If your check should pass for absent values, guard
116
+ # with `value.blank? ||`.
117
+ #
102
118
  # @return [Boolean, Symbol] true if the setting's check logic evaluates to
103
- # true, # false or false if not. I no check was defined, returns
104
- # :unchecked_true. @see check
119
+ # true, false if not. If no check was defined, returns
120
+ # :unchecked_true. @see check
105
121
  def checked?
106
122
  instance_eval(&check)
107
123
  end
@@ -124,8 +140,8 @@ class ConstConf::Setting
124
140
  # * With arity 0: Called without arguments (e.g., `-> {
125
141
  # some_value.present? }`)
126
142
  # @return [Boolean, Proc] returns the value that was set
127
- # @method required(value = nil, &block)
128
- # @see #required?
143
+ # @method required(value = nil, &block)
144
+ # @see #required?
129
145
  setting_accessor :required, false
130
146
 
131
147
  # Checks if the setting has a required value configured or as a default
@@ -151,10 +167,13 @@ class ConstConf::Setting
151
167
  end
152
168
  end
153
169
 
154
- # Checks if the setting has a required value configured.
170
+ # Sets or retrieves the sensitive flag for the configuration setting.
155
171
  #
156
- # @return [Boolean] true if the setting is marked as required and has a valid
157
- # value, false otherwise
172
+ # When true, the setting's value is masked as 🤫 in the {#view} output
173
+ # to protect confidential data such as passwords, API keys, and tokens.
174
+ #
175
+ # @param value [Boolean] true to mark the setting as sensitive
176
+ # @return [Boolean] the current sensitivity flag
158
177
  setting_accessor :sensitive, false
159
178
 
160
179
  alias sensitive? sensitive
@@ -300,8 +319,10 @@ class ConstConf::Setting
300
319
  #
301
320
  # @raise [ ConstConf::RequiredDescriptionNotConfigured ] if the setting's description
302
321
  # 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
322
+ # @raise [ ConstConf::RequiredValueNotConfigured ] if the setting is required
323
+ # but no non-nil value is provided. Note that blank values (e.g. `""` or
324
+ # `" "`) satisfy this check; use {#check} or a Proc-based {#required}
325
+ # to also reject them.
305
326
  # @raise [ ConstConf::SettingCheckFailed ] if the setting's check fails
306
327
  def confirm!
307
328
  if parent_namespace.is_a?(Module) && parent_namespace < ConstConf
@@ -332,8 +353,11 @@ class ConstConf::Setting
332
353
  # standard output.
333
354
  #
334
355
  # @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:)
356
+ # @param pager [String, nil] the pager command to use when output exceeds
357
+ # terminal height; if nil, falls back to the PAGER environment variable or
358
+ # `less -r`
359
+ def view(io: nil, pager: nil)
360
+ parent_namespace.view(object: self, io:, pager:)
337
361
  end
338
362
 
339
363
  # Returns the string representation of the tree structure.
@@ -1,6 +1,6 @@
1
1
  module ConstConf
2
2
  # ConstConf version
3
- VERSION = '0.8.1'
3
+ VERSION = '0.9.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/const_conf.rb CHANGED
@@ -410,14 +410,17 @@ module ConstConf
410
410
  # @param object [Object] the ConstConf module or setting to display
411
411
  # @param io [IO, nil] the IO object to write the output to; if nil, uses
412
412
  # STDOUT
413
- def view(object: self, io: nil)
413
+ # @param pager [String, nil] the pager command for long output; if nil,
414
+ # falls back to the `PAGER` environment variable or `less -r`
415
+ def view(object: self, io: nil, pager: nil)
416
+ pager ||= ENV.fetch('PAGER', 'less -r')
414
417
  output = ConstConf::Tree.from_const_conf(object).to_a
415
418
  if io
416
419
  io.puts output
417
420
  elsif output.size < Tins::Terminal.lines
418
421
  STDOUT.puts output
419
422
  else
420
- IO.popen(ENV.fetch('PAGER', 'less -r'), ?w) do |f|
423
+ IO.popen(pager, ?w) do |f|
421
424
  f.puts output
422
425
  f.close_write
423
426
  end
@@ -265,6 +265,12 @@ describe ConstConf do
265
265
  )
266
266
  TestConstConf.view
267
267
  end
268
+
269
+ it 'passes custom pager to IO.popen' do
270
+ allow(Tins::Terminal).to receive(:lines).and_return(0)
271
+ expect(IO).to receive(:popen).with('my-custom-pager', ?w)
272
+ TestConstConf.view(pager: 'my-custom-pager')
273
+ end
268
274
  end
269
275
 
270
276
  describe '.documentation' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: const_conf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -229,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
229
  - !ruby/object:Gem::Version
230
230
  version: '0'
231
231
  requirements: []
232
- rubygems_version: 4.0.17
232
+ rubygems_version: 4.0.20
233
233
  specification_version: 4
234
234
  summary: Clean DSL for config settings with validation and Rails integration
235
235
  test_files: