Class: I18nContextGenerator::Config

Inherits:
Object
  • Object
show all
Extended by:
CliValues, Defaults
Includes:
Serialization, Validation
Defined in:
lib/i18n_context_generator/config.rb,
lib/i18n_context_generator/config/schema.rb,
lib/i18n_context_generator/config/defaults.rb,
lib/i18n_context_generator/config/cli_values.rb,
lib/i18n_context_generator/config/validation.rb,
lib/i18n_context_generator/config/serialization.rb

Overview

Holds all configuration for an extraction run, loaded from YAML config files and/or CLI options.

Defined Under Namespace

Modules: CliValues, Defaults, Schema, Serialization, Validation

Constant Summary collapse

DEFAULT_CONTEXT_PREFIX =
Schema.default(:context_prefix).freeze
DEFAULT_CONTEXT_MODE =
Schema.default(:context_mode).freeze
DEFAULT_MAX_PROMPT_CHARS =
Schema.default(:max_prompt_chars)
DEFAULT_CACHE_DIR =
Schema.default(:cache_dir).freeze
VALID_PROVIDERS =
Schema.values(:provider)
VALID_OUTPUT_FORMATS =
Schema.values(:output_format)
VALID_CONTEXT_MODES =
Schema.values(:context_mode)
VALID_DISCOVERY_MODES =
Schema.values(:discovery_mode)
VALID_PLATFORMS =
Schema.values(:platform)
VALID_OUTPUT_EXTENSIONS =
{ '.csv' => 'csv', '.json' => 'json' }.freeze

Constants included from Validation

Validation::BOOLEAN_OPTIONS, Validation::OPTIONAL_STRING_OPTIONS, Validation::TRANSLATION_DIFF_EXTENSIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Defaults

default_ignore_patterns, merge_ignore_patterns

Methods included from CliValues

cli_key_filters, cli_path_list

Methods included from Serialization

#to_h

Methods included from Validation

#validate!

Constructor Details

#initialize(**attrs) ⇒ Config

Returns a new instance of Config.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/i18n_context_generator/config.rb', line 40

def initialize(**attrs)
  @schema_version = fetch_defaulting_value(attrs, :schema_version, Schema.default(:schema_version))
  @translations = deduplicate_paths(fetch_defaulting_value(attrs, :translations, Schema.default(:translations)))
  @source_paths = deduplicate_source_paths(fetch_defaulting_value(attrs, :source_paths, Schema.default(:source_paths)))
  @context_files = deduplicate_paths(fetch_defaulting_value(attrs, :context_files, Schema.default(:context_files)))
  @supplemental_context = fetch_defaulting_value(attrs, :supplemental_context, {})
  @source_line_filter = fetch_config_value(attrs, :source_line_filter, nil)
  @translation_locales = fetch_defaulting_value(attrs, :translation_locales, {})
  @ignore_patterns = self.class.merge_ignore_patterns(fetch_defaulting_value(attrs, :ignore_patterns, Schema.default(:ignore_patterns)))
  @provider = normalize_enum_value(fetch_defaulting_value(attrs, :provider, Schema.default(:provider)))
  @model = fetch_config_value(attrs, :model, nil)
  @endpoint = fetch_config_value(attrs, :endpoint, nil)
  @concurrency = fetch_defaulting_value(attrs, :concurrency, Schema.default(:concurrency))
  @context_lines = fetch_defaulting_value(attrs, :context_lines, Schema.default(:context_lines))
  @max_matches_per_key = fetch_defaulting_value(attrs, :max_matches_per_key, Schema.default(:max_matches_per_key))
  @max_prompt_chars = fetch_defaulting_value(attrs, :max_prompt_chars, Schema.default(:max_prompt_chars))
  configured_output_path = fetch_config_value(attrs, :output_path, nil)
  explicit_stdout = fetch_boolean_value(attrs, :output_stdout, Schema.default(:output_stdout))
  @output_stdout = explicit_stdout || configured_output_path == '-'
  @output_destination_conflict = explicit_stdout && !configured_output_path.nil? && configured_output_path != '-'
  @output_path = @output_stdout ? '-' : configured_output_path
  @output_format_explicit = attrs.key?(:output_format) && !attrs[:output_format].nil?
  @output_format = normalize_enum_value(resolve_output_format(attrs[:output_format], @output_path))
  @no_cache = fetch_boolean_value(attrs, :no_cache, !Schema.default(:cache_enabled))
  @cache_dir = fetch_defaulting_value(attrs, :cache_dir, Schema.default(:cache_dir))
  @dry_run = fetch_boolean_value(attrs, :dry_run, Schema.default(:dry_run))
  @print_config = fetch_boolean_value(attrs, :print_config, Schema.default(:print_config))
  @workflow_stage = normalize_enum_value(
    fetch_defaulting_value(attrs, :workflow_stage, Schema.default(:workflow_stage))
  )
  @key_filter = fetch_config_value(attrs, :key_filter, nil)
  @write_back = fetch_boolean_value(attrs, :write_back, Schema.default(:write_back))
  @write_back_to_code = fetch_boolean_value(attrs, :write_back_to_code, Schema.default(:write_back_to_code))
  @swift_functions = merge_swift_functions(
    fetch_defaulting_value(attrs, :swift_functions, default_swift_functions)
  )
  @diff_base = fetch_config_value(attrs, :diff_base, nil)
  @diff_head = fetch_defaulting_value(attrs, :diff_head, Schema.default(:diff_head))
  @context_prefix = fetch_defaulting_value(attrs, :context_prefix, Schema.default(:context_prefix))
  @context_mode = normalize_enum_value(fetch_defaulting_value(attrs, :context_mode, Schema.default(:context_mode)))
  @start_key = fetch_config_value(attrs, :start_key, nil)
  @end_key = fetch_config_value(attrs, :end_key, nil)
  @include_file_paths = fetch_boolean_value(attrs, :include_file_paths, Schema.default(:include_file_paths))
  @include_translation_comments = fetch_boolean_value(attrs, :include_translation_comments, Schema.default(:include_translation_comments))
  @redact_prompts = fetch_boolean_value(attrs, :redact_prompts, Schema.default(:redact_prompts))
  @discovery_mode = normalize_enum_value(fetch_defaulting_value(attrs, :discovery_mode, Schema.default(:discovery_mode)))
  @platform = normalize_enum_value(fetch_config_value(attrs, :platform, nil))
end

Instance Attribute Details

#cache_dirObject (readonly)

Returns the value of attribute cache_dir.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def cache_dir
  @cache_dir
end

#concurrencyObject (readonly)

Returns the value of attribute concurrency.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def concurrency
  @concurrency
end

#context_filesObject (readonly)

Returns the value of attribute context_files.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def context_files
  @context_files
end

#context_linesObject (readonly)

Returns the value of attribute context_lines.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def context_lines
  @context_lines
end

#context_modeObject (readonly)

Returns the value of attribute context_mode.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def context_mode
  @context_mode
end

#context_prefixObject (readonly)

Returns the value of attribute context_prefix.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def context_prefix
  @context_prefix
end

#diff_baseObject (readonly)

Returns the value of attribute diff_base.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def diff_base
  @diff_base
end

#diff_headObject (readonly)

Returns the value of attribute diff_head.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def diff_head
  @diff_head
end

#discovery_modeObject (readonly)

Returns the value of attribute discovery_mode.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def discovery_mode
  @discovery_mode
end

#dry_runObject (readonly)

Returns the value of attribute dry_run.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def dry_run
  @dry_run
end

#end_keyObject (readonly)

Returns the value of attribute end_key.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def end_key
  @end_key
end

#endpointObject (readonly)

Returns the value of attribute endpoint.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def endpoint
  @endpoint
end

#ignore_patternsObject (readonly)

Returns the value of attribute ignore_patterns.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def ignore_patterns
  @ignore_patterns
end

#include_file_pathsObject (readonly)

Returns the value of attribute include_file_paths.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def include_file_paths
  @include_file_paths
end

#include_translation_commentsObject (readonly)

Returns the value of attribute include_translation_comments.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def include_translation_comments
  @include_translation_comments
end

#key_filterObject (readonly)

Returns the value of attribute key_filter.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def key_filter
  @key_filter
end

#max_matches_per_keyObject (readonly)

Returns the value of attribute max_matches_per_key.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def max_matches_per_key
  @max_matches_per_key
end

#max_prompt_charsObject (readonly)

Returns the value of attribute max_prompt_chars.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def max_prompt_chars
  @max_prompt_chars
end

#modelObject (readonly)

Returns the value of attribute model.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def model
  @model
end

#no_cacheObject (readonly)

Returns the value of attribute no_cache.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def no_cache
  @no_cache
end

#output_formatObject (readonly)

Returns the value of attribute output_format.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def output_format
  @output_format
end

#output_pathObject (readonly)

Returns the value of attribute output_path.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def output_path
  @output_path
end

#output_stdoutObject (readonly)

Returns the value of attribute output_stdout.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def output_stdout
  @output_stdout
end

#platformObject (readonly)

Returns the value of attribute platform.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def platform
  @platform
end

Returns the value of attribute print_config.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def print_config
  @print_config
end

#providerObject (readonly)

Returns the value of attribute provider.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def provider
  @provider
end

#redact_promptsObject (readonly)

Returns the value of attribute redact_prompts.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def redact_prompts
  @redact_prompts
end

#schema_versionObject (readonly)

Returns the value of attribute schema_version.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def schema_version
  @schema_version
end

#source_line_filterObject (readonly)

Returns the value of attribute source_line_filter.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def source_line_filter
  @source_line_filter
end

#source_pathsObject (readonly)

Returns the value of attribute source_paths.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def source_paths
  @source_paths
end

#start_keyObject (readonly)

Returns the value of attribute start_key.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def start_key
  @start_key
end

#supplemental_contextObject (readonly)

Returns the value of attribute supplemental_context.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def supplemental_context
  @supplemental_context
end

#swift_functionsObject (readonly)

Returns the value of attribute swift_functions.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def swift_functions
  @swift_functions
end

#translation_localesObject (readonly)

Returns the value of attribute translation_locales.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def translation_locales
  @translation_locales
end

#translationsObject (readonly)

Returns the value of attribute translations.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def translations
  @translations
end

#workflow_stageObject (readonly)

Returns the value of attribute workflow_stage.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def workflow_stage
  @workflow_stage
end

#write_backObject (readonly)

Returns the value of attribute write_back.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def write_back
  @write_back
end

#write_back_to_codeObject (readonly)

Returns the value of attribute write_back_to_code.



18
19
20
# File 'lib/i18n_context_generator/config.rb', line 18

def write_back_to_code
  @write_back_to_code
end

Class Method Details

.from_cli(options) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/i18n_context_generator/config.rb', line 156

def self.from_cli(options)
  translations = cli_path_list(options[:translation], legacy: options[:translations])
  source_paths = cli_path_list(options[:source])
  source_paths = Schema.default(:source_paths) if source_paths.empty?
  context_files = cli_path_list(options[:context_file])
  key_filters = cli_key_filters(options[:key], legacy: options[:keys])

  attrs = {
    schema_version: Schema::VERSION,
    translations: translations,
    source_paths: source_paths,
    context_files: context_files,
    ignore_patterns: [],
    provider: options[:provider] || Schema.default(:provider),
    model: options[:model],
    endpoint: options[:endpoint],
    concurrency: options[:concurrency] || Schema.default(:concurrency),
    context_lines: Schema.default(:context_lines),
    max_matches_per_key: Schema.default(:max_matches_per_key),
    discovery_mode: options[:discovery_mode] || Schema.default(:discovery_mode),
    platform: options[:platform],
    output_path: options[:output],
    output_stdout: options[:stdout],
    dry_run: options[:dry_run] || Schema.default(:dry_run),
    print_config: options[:print_config],
    workflow_stage: options[:workflow_stage] || Schema.default(:workflow_stage),
    key_filter: key_filters.empty? ? nil : key_filters,
    write_back: options[:write_back] || Schema.default(:write_back),
    write_back_to_code: options[:write_back_to_code] || Schema.default(:write_back_to_code),
    diff_base: options[:diff_base],
    diff_head: options[:diff_head],
    start_key: options[:start_key],
    end_key: options[:end_key]
  }
  attrs.merge!(cache_cli_attributes(options))
  attrs[:output_format] = options[:format] if options[:format]

  # Only include if explicitly provided, so Config.new can apply its defaults
  attrs[:context_prefix] = options[:context_prefix] unless options[:context_prefix].nil?
  attrs[:context_mode] = options[:context_mode] if options[:context_mode]
  attrs[:include_file_paths] = options[:include_file_paths] unless options[:include_file_paths].nil?
  attrs[:include_translation_comments] = options[:include_translation_comments] unless options[:include_translation_comments].nil?
  attrs[:redact_prompts] = options[:redact_prompts] unless options[:redact_prompts].nil?

  new(**attrs)
end

.from_file(path) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/i18n_context_generator/config.rb', line 101

def self.from_file(path)
  yaml = YAML.safe_load_file(path, permitted_classes: []) || {}
  raise Error, "Invalid config #{path}: root must be a mapping" unless yaml.is_a?(Hash)

  schema_version = Schema.validate_document!(yaml, path: path)
  %w[source context llm processing output swift privacy workflow].each { |section| config_section(yaml, section, path) }
  cache = config_section(yaml, 'cache', path)
  invalid_cache_enabled = cache.key?('enabled') && ![true, false].include?(cache['enabled'])
  raise Error, "Invalid config #{path}: cache.enabled must be true or false" if invalid_cache_enabled

  translation_settings = parse_translation_settings(yaml['translations'], path: path)

  attrs = {
    schema_version: schema_version,
    translations: translation_settings[:paths],
    translation_locales: translation_settings[:locales],
    source_paths: Schema.value(yaml, :source_paths),
    ignore_patterns: Schema.value(yaml, :ignore_patterns),
    context_files: Schema.value(yaml, :context_files),
    provider: Schema.value(yaml, :provider),
    model: Schema.value(yaml, :model),
    endpoint: Schema.value(yaml, :endpoint),
    concurrency: Schema.value(yaml, :concurrency),
    context_lines: Schema.value(yaml, :context_lines),
    max_matches_per_key: Schema.value(yaml, :max_matches_per_key),
    max_prompt_chars: Schema.value(yaml, :max_prompt_chars),
    discovery_mode: Schema.value(yaml, :discovery_mode),
    platform: Schema.value(yaml, :platform),
    output_path: Schema.value(yaml, :output_path),
    output_stdout: Schema.value(yaml, :output_stdout),
    write_back: Schema.value(yaml, :write_back),
    write_back_to_code: Schema.value(yaml, :write_back_to_code),
    swift_functions: Schema.value(yaml, :swift_functions),
    no_cache: !Schema.value(yaml, :cache_enabled),
    cache_dir: Schema.value(yaml, :cache_dir),
    workflow_stage: Schema.value(yaml, :workflow_stage)
  }
  attrs[:output_format] = Schema.value(yaml, :output_format) if Schema.configured?(yaml, :output_format)
  attrs[:context_mode] = Schema.value(yaml, :context_mode) if Schema.configured?(yaml, :context_mode)

  # Only pass context_prefix when explicitly set in YAML, so initialize default applies
  attrs[:context_prefix] = Schema.value(yaml, :context_prefix) if Schema.configured?(yaml, :context_prefix)
  attrs[:include_file_paths] = Schema.value(yaml, :include_file_paths) if Schema.configured?(yaml, :include_file_paths)
  attrs[:include_translation_comments] = Schema.value(yaml, :include_translation_comments) if Schema.configured?(yaml, :include_translation_comments)
  attrs[:redact_prompts] = Schema.value(yaml, :redact_prompts) if Schema.configured?(yaml, :redact_prompts)

  new(**attrs)
rescue Psych::SyntaxError => e
  raise Error, "Invalid config YAML #{path}: #{e.problem} at line #{e.line}, column #{e.column}"
rescue Psych::Exception => e
  raise Error, "Invalid config YAML #{path}: #{e.message}"
rescue SystemCallError => e
  raise Error, "Unable to read config #{path}: #{e.message}"
end

.load(options) ⇒ Object

Raises:



93
94
95
96
97
98
99
# File 'lib/i18n_context_generator/config.rb', line 93

def self.load(options)
  return from_cli(options) unless options[:config]

  raise Error, "Config file not found: #{options[:config]}" unless File.file?(options[:config])

  from_file(options[:config]).merge_cli(options)
end

.parse_translation_settings(translations, path: nil) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/i18n_context_generator/config.rb', line 230

def self.parse_translation_settings(translations, path: nil)
  return { paths: [], locales: {} } if translations.nil?

  unless translations.is_a?(Array)
    location = path ? " in #{path}" : ''
    raise Error, "Invalid translations#{location}: expected an array"
  end

  paths = []
  locales = {}

  translations.each do |translation|
    case translation
    when String
      paths << translation
    when Hash
      unknown_keys = translation.keys - %w[path locale]
      raise Error, "Invalid translation entry: unknown keys: #{unknown_keys.join(', ')}" if unknown_keys.any?

      translation_path = translation['path']
      raise Error, 'Invalid translation entry: path must be a non-empty string' unless valid_nonempty_string?(translation_path)

      locale = translation['locale']
      raise Error, "Invalid translation locale for #{translation_path}: expected a non-empty string" unless locale.nil? || valid_nonempty_string?(locale)
      raise Error, "Conflicting translation locales for #{translation_path}" if conflicting_locale?(locales, translation_path, locale)

      paths << translation_path
      locales[translation_path] = locale if locale
    else
      raise Error, 'Invalid translation entry: expected a path string or mapping'
    end
  end

  { paths: paths.uniq, locales: locales }
end

.parse_translations(translations) ⇒ Object



226
227
228
# File 'lib/i18n_context_generator/config.rb', line 226

def self.parse_translations(translations)
  parse_translation_settings(translations)[:paths]
end

Instance Method Details

#default_swift_functionsObject



89
90
91
# File 'lib/i18n_context_generator/config.rb', line 89

def default_swift_functions
  Schema.default(:swift_functions)
end

#merge_cli(options) ⇒ Object

Merge CLI options over config-file values. Only options explicitly passed by the user (non-nil) are merged. Thor options without defaults are nil when not passed, so this correctly preserves config-file values for unspecified flags.



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/i18n_context_generator/config.rb', line 207

def merge_cli(options)
  translations = self.class.cli_path_list(options[:translation], legacy: options[:translations])
  if translations.any?
    @translations = deduplicate_paths(translations)
    @translation_locales = {}
  end
  source_paths = self.class.cli_path_list(options[:source])
  @source_paths = deduplicate_source_paths(source_paths) if source_paths.any?
  context_files = self.class.cli_path_list(options[:context_file])
  @context_files = deduplicate_paths(context_files) if context_files.any?
  key_filters = self.class.cli_key_filters(options[:key], legacy: options[:keys])
  @key_filter = key_filters if key_filters.any?
  merge_cli_provider_and_model(options)
  merge_cli_output(options)
  merge_cli_scalar_options(options)
  merge_cli_boolean_options(options)
  self
end