Module: Lich::Common::FrontendSettings

Defined in:
documented/common/frontend_settings.rb

Overview

Owns user-configurable frontend definitions and built-in launch overrides.

The persisted schema deliberately lives outside entry.yaml so frontend configuration never enters the credential lifecycle. Callers replace the complete configuration; validation, atomic persistence, catalog cleanup, and immutable process state stay behind this module's small interface.

Defined Under Namespace

Classes: UnsupportedVersionError

Constant Summary collapse

FILE_NAME =
'frontends.yml'
SCHEMA_VERSION =
1
CUSTOM_ID_PATTERN =

Pattern for valid custom frontend identifiers.

Custom IDs must start with a lowercase letter or digit, followed by 0-63 lowercase letters, digits, hyphens, or underscores.

Examples:

Valid and invalid identifiers

"my_frontend" #=> matches
"gstone-iv" #=> matches
"MyFrontend" #=> does not match (uppercase)
"_custom" #=> does not match (starts with underscore)

See Also:

  • #normalize_custom
/\A[a-z0-9][a-z0-9_-]{0,63}\z/
MAX_SCALAR_BYTES =
4096
MAX_ARGUMENTS =
256
MUTEX =

Synchronization lock protecting access to the current frontend configuration.

All public methods that read or write @current must hold this lock to ensure thread-safe changes to the in-memory catalog and process state.

Mutex.new
EMPTY_CONFIGURATION =

The pristine baseline configuration with no built-in overrides or custom definitions.

Used to restore a known-good state when load! encounters errors or unsupported schemas. The returned structure is frozen and cannot be mutated.

Lich::Util.deep_freeze(
  {
    'version'  => SCHEMA_VERSION,
    'builtins' => {},
    'custom'   => {}
  }
)

Class Method Summary collapse

Class Method Details

.current ⇒ Hash

Returns a detached snapshot so callers cannot mutate applied settings.

Returns:



111
112
113
# File 'documented/common/frontend_settings.rb', line 111

def current
  MUTEX.synchronize { deep_copy(@current) }
end

.load!(data_dir: nil) ⇒ Hash

Loads, validates, and applies frontends.yml from a Lich data directory. Missing, unreadable, or malformed files become an empty configuration and restore the pristine built-in catalog. A document from a newer schema is left untouched and the last known-good catalog remains active. Invalid argument lists likewise retain the previous catalog and are logged.

Parameters:

  • data_dir (String, nil) (defaults to: nil) —

    Lich data directory; defaults to DATA_DIR

Returns:

  • (Hash) —

    normalized configuration snapshot



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'documented/common/frontend_settings.rb', line 69

def load!(data_dir: nil)
  directory = resolve_data_dir(data_dir)
  MUTEX.synchronize do
    path = File.join(directory, FILE_NAME)
    raw = read_file(path)
    if unsupported_version?(raw)
      warn_unsupported_file(raw)
      return deep_copy(@current)
    end

    apply_configuration(normalize_configuration(raw))
  rescue ArgumentError => error
    log_warning("could not apply #{FILE_NAME}: #{error.message}")
    deep_copy(@current)
  end
end

.replace!(data_dir: nil, builtins: {}, custom: {}) ⇒ Hash

Validates, atomically persists, and applies a complete configuration. Replacing the whole document makes deletion explicit and prevents stale custom definitions from accumulating across settings reloads.

Parameters:

  • data_dir (String, nil) (defaults to: nil) —

    Lich data directory; defaults to DATA_DIR

  • builtins (Hash) (defaults to: {}) —

    built-in executable/argument overrides

  • custom (Hash) (defaults to: {}) —

    custom frontend definitions keyed by stable id

Returns:

  • (Hash) —

    normalized configuration snapshot



94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'documented/common/frontend_settings.rb', line 94

def replace!(data_dir: nil, builtins: {}, custom: {})
  directory = resolve_data_dir(data_dir)
  MUTEX.synchronize do
    path = File.join(directory, FILE_NAME)
    refuse_unsupported_overwrite!(path)
    configuration = normalize_configuration(
      'version'  => SCHEMA_VERSION,
      'builtins' => builtins,
      'custom'   => custom
    )
    write_file(path, configuration)
    apply_configuration(configuration)
  end
end

.settings_for(frontend_id) ⇒ Hash?

Returns persisted settings for a built-in or custom frontend identifier. Aliases such as wrayth resolve to their stable built-in id.

Parameters:

  • frontend_id (String, Symbol)

Returns:



120
121
122
123
124
125
126
127
# File 'documented/common/frontend_settings.rb', line 120

def settings_for(frontend_id)
  frontend_id = Frontend.canonical_name(frontend_id)
  MUTEX.synchronize do
    settings = @current.fetch('builtins').fetch(frontend_id, nil) ||
               @current.fetch('custom').fetch(frontend_id, nil)
    settings && deep_copy(settings)
  end
end

.validate_arguments(value) ⇒ Array<String>

Validates literal argv values without trimming or dropping positions.

Parameters:

  • value (Array<String>) —

    complete additional argument list

Returns:

  • (Array<String>) —

    detached, unchanged argument values

Raises:

  • (ArgumentError) —

    when the list exceeds bounds or contains invalid values



134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'documented/common/frontend_settings.rb', line 134

def validate_arguments(value)
  unless value.is_a?(Array) && value.length <= MAX_ARGUMENTS
    raise ArgumentError, "Additional arguments must be an array of at most #{MAX_ARGUMENTS} strings."
  end
  unless value.all? { |argument|
    argument.is_a?(String) && argument.valid_encoding? &&
    argument.bytesize <= MAX_SCALAR_BYTES && !argument.match?(/[\x00-\x1f\x7f]/)
  }
    raise ArgumentError, 'Arguments must be bounded strings without control characters.'
  end

  value.map(&:dup)
end