Module: Canon::Config::ConfigDSL

Included in:
DiffConfig
Defined in:
lib/canon/config/config_dsl.rb

Overview

DSL for declaring config-backed attributes on a config class.

A config class (DiffConfig, MatchConfig, FormatConfig) extends this module and declares each user-tunable attribute with #config_key. The DSL generates the matching getter/setter pair and registers the attribute’s metadata (type, enum, default, coercion) in a per-class registry so other components (EnvSchema, TypeConverter) can discover it without duplicating the schema.

Goals (lutaml/canon TODO.improve/07):

  • Eliminate the 5-line getter/setter boilerplate per attribute

  • Provide a single source of truth for attribute types/enums

  • Keep the public API stable (methods behave exactly as before)

Examples:

Declare a simple boolean key

class DiffConfig
  extend ConfigDSL
  config_key :verbose_diff, type: :boolean, default: false
end

Declare an enum-constrained symbol key

config_key :mode, type: :symbol,
                    enum: %i[by_line by_object pretty_diff],
                    default: :by_line

Declare a key with setter coercion

config_key :preserve_whitespace_elements,
          type: :string_array,
          default: [],
          coerce: ->(v) { Array(v).map(&:to_s) }

Instance Method Summary collapse

Instance Method Details

#config_key(name, type: :pass_through, enum: nil, default: nil, coerce: nil, getter_coerce: nil) ⇒ void

This method returns an undefined value.

Declare a config-backed attribute.

Generates a getter and setter on the extending class. The getter reads through the resolver; the setter validates against enum (if provided), applies coerce (if provided), and writes through the resolver.

Parameters:

  • name (Symbol)

    Attribute name

  • type (Symbol) (defaults to: :pass_through)

    Type tag for ENV conversion (:boolean, :integer, :symbol, :string, :string_array,

    :pass_through)
    
  • enum (Array, nil) (defaults to: nil)

    Allowed values; setter validates

  • default (Object, Proc) (defaults to: nil)

    Default value, or a callable that returns the default. Callables are evaluated each time the resolver is (re)built, so they pick up runtime state such as ColorDetector.supports_color? when stubs are installed by the test suite.

  • coerce (Proc, nil) (defaults to: nil)

    Setter coercion proc (value → value)

  • getter_coerce (Proc, nil) (defaults to: nil)

    Getter coercion proc



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/canon/config/config_dsl.rb', line 63

def config_key(name, type: :pass_through, enum: nil, default: nil,
               coerce: nil, getter_coerce: nil)
  sym = name.to_sym
  config_keys[sym] = {
    type: type,
    enum: enum,
    default: default,
    default_proc: default.is_a?(Proc) ? default : nil,
    coerce: coerce,
    getter_coerce: getter_coerce,
  }.freeze

  define_getter(sym, getter_coerce)
  define_setter(sym, coerce)
end

#config_keysObject

Per-class attribute registry, lazily initialized on first config_key declaration. Stored on the extending class so each config class owns its own map without registry leakage.



39
40
41
# File 'lib/canon/config/config_dsl.rb', line 39

def config_keys
  @config_keys ||= {}
end

#enum_valuesHash{Symbol => Array}

Enum constraint map, keyed by attribute name.

Derived from declared config_keys for backward compatibility with the original VALID_ENUM_VALUES constant.

Returns:

  • (Hash{Symbol => Array})

    Enum values per attribute



123
124
125
# File 'lib/canon/config/config_dsl.rb', line 123

def enum_values
  config_keys.filter_map { |k, m| [k, m[:enum]] if m[:enum] }.to_h
end

#resolve_default(key) ⇒ Object

Resolve a declared default to a concrete value.

If the declared default is a callable (e.g. a proc or method object), it is invoked each time this method is called so the result reflects the current runtime state. Otherwise the stored default value is returned as-is.

Parameters:

  • key (Symbol)

    Attribute name

Returns:

  • (Object)

    Resolved default value (may be nil)



88
89
90
91
92
93
94
95
96
# File 'lib/canon/config/config_dsl.rb', line 88

def resolve_default(key)
  meta = config_keys[key]
  return nil unless meta

  proc_form = meta[:default_proc]
  return proc_form.call if proc_form

  meta[:default]
end

#validate_config_value!(key, value) ⇒ void

This method returns an undefined value.

Validate a value against an attribute’s enum, if any.

Mirrors the original DiffConfig.validate_config_value! API so existing call sites (and specs) keep working.

Parameters:

  • key (Symbol)

    Attribute name

  • value (Object)

    Value to validate

Raises:

  • (ArgumentError)

    if value is not in the enum



107
108
109
110
111
112
113
114
115
# File 'lib/canon/config/config_dsl.rb', line 107

def validate_config_value!(key, value)
  meta = config_keys[key]
  return unless meta&.dig(:enum)
  return if meta[:enum].include?(value)

  raise ArgumentError,
        "Invalid value #{value.inspect} for #{key}. " \
        "Valid values: #{meta[:enum].map(&:inspect).join(', ')}"
end