anyway_config 2.0.5 → 2.2.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -181
  3. data/README.md +238 -13
  4. data/lib/.rbnext/1995.next/anyway/config.rb +438 -0
  5. data/lib/.rbnext/1995.next/anyway/dynamic_config.rb +31 -0
  6. data/lib/.rbnext/1995.next/anyway/env.rb +56 -0
  7. data/lib/.rbnext/1995.next/anyway/loaders/base.rb +21 -0
  8. data/lib/.rbnext/1995.next/anyway/tracing.rb +181 -0
  9. data/lib/.rbnext/2.7/anyway/auto_cast.rb +39 -19
  10. data/lib/.rbnext/2.7/anyway/config.rb +61 -16
  11. data/lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb +30 -0
  12. data/lib/.rbnext/2.7/anyway/rbs.rb +92 -0
  13. data/lib/.rbnext/2.7/anyway/settings.rb +79 -0
  14. data/lib/.rbnext/2.7/anyway/tracing.rb +6 -6
  15. data/lib/.rbnext/2.7/anyway/type_casting.rb +143 -0
  16. data/lib/.rbnext/3.0/anyway/auto_cast.rb +53 -0
  17. data/lib/.rbnext/{2.8 → 3.0}/anyway/config.rb +61 -16
  18. data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders/base.rb +0 -0
  19. data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders.rb +0 -0
  20. data/lib/.rbnext/{2.8 → 3.0}/anyway/tracing.rb +6 -6
  21. data/lib/anyway/auto_cast.rb +39 -19
  22. data/lib/anyway/config.rb +75 -30
  23. data/lib/anyway/dynamic_config.rb +6 -2
  24. data/lib/anyway/env.rb +1 -1
  25. data/lib/anyway/ext/deep_dup.rb +12 -0
  26. data/lib/anyway/ext/hash.rb +10 -12
  27. data/lib/anyway/loaders/base.rb +1 -1
  28. data/lib/anyway/loaders/env.rb +3 -1
  29. data/lib/anyway/loaders/yaml.rb +9 -5
  30. data/lib/anyway/option_parser_builder.rb +1 -3
  31. data/lib/anyway/optparse_config.rb +5 -7
  32. data/lib/anyway/rails/loaders/credentials.rb +4 -4
  33. data/lib/anyway/rails/loaders/secrets.rb +6 -8
  34. data/lib/anyway/rails/loaders/yaml.rb +11 -0
  35. data/lib/anyway/rails/settings.rb +9 -2
  36. data/lib/anyway/rbs.rb +92 -0
  37. data/lib/anyway/settings.rb +52 -2
  38. data/lib/anyway/tracing.rb +9 -9
  39. data/lib/anyway/type_casting.rb +134 -0
  40. data/lib/anyway/utils/deep_merge.rb +21 -0
  41. data/lib/anyway/version.rb +1 -1
  42. data/lib/anyway_config.rb +4 -0
  43. data/sig/anyway_config.rbs +129 -0
  44. metadata +42 -15
  45. data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +0 -31
data/lib/anyway/config.rb CHANGED
@@ -19,7 +19,7 @@ module Anyway # :nodoc:
19
19
  # Provides `attr_config` method to describe
20
20
  # configuration parameters and set defaults
21
21
  class Config
22
- PARAM_NAME = /^[a-z_]([\w]+)?$/
22
+ PARAM_NAME = /^[a-z_](\w+)?$/
23
23
 
24
24
  # List of names that couldn't be used as config names
25
25
  # (the class instance methods we use)
@@ -40,12 +40,15 @@ module Anyway # :nodoc:
40
40
  raise_validation_error
41
41
  reload
42
42
  resolve_config_path
43
+ tap
43
44
  to_h
44
45
  to_source_trace
45
46
  write_config_attr
47
+ __type_caster__
46
48
  ].freeze
47
49
 
48
50
  class Error < StandardError; end
51
+
49
52
  class ValidationError < Error; end
50
53
 
51
54
  include OptparseConfig
@@ -106,21 +109,21 @@ module Anyway # :nodoc:
106
109
  def defaults
107
110
  return @defaults if instance_variable_defined?(:@defaults)
108
111
 
109
- if superclass < Anyway::Config
112
+ @defaults = if superclass < Anyway::Config
110
113
  superclass.defaults.deep_dup
111
114
  else
112
115
  new_empty_config
113
- end => @defaults
116
+ end
114
117
  end
115
118
 
116
119
  def config_attributes
117
120
  return @config_attributes if instance_variable_defined?(:@config_attributes)
118
121
 
119
- if superclass < Anyway::Config
122
+ @config_attributes = if superclass < Anyway::Config
120
123
  superclass.config_attributes.dup
121
124
  else
122
125
  []
123
- end => @config_attributes
126
+ end
124
127
  end
125
128
 
126
129
  def required(*names)
@@ -134,17 +137,17 @@ module Anyway # :nodoc:
134
137
  def required_attributes
135
138
  return @required_attributes if instance_variable_defined?(:@required_attributes)
136
139
 
137
- if superclass < Anyway::Config
140
+ @required_attributes = if superclass < Anyway::Config
138
141
  superclass.required_attributes.dup
139
142
  else
140
143
  []
141
- end => @required_attributes
144
+ end
142
145
  end
143
146
 
144
147
  def on_load(*names, &block)
145
- raise ArgumentError, "Either methods or block should be specified, not both" if block_given? && !names.empty?
148
+ raise ArgumentError, "Either methods or block should be specified, not both" if block && !names.empty?
146
149
 
147
- if block_given?
150
+ if block
148
151
  load_callbacks << BlockCallback.new(block)
149
152
  else
150
153
  load_callbacks.push(*names.map { NamedCallback.new(_1) })
@@ -154,11 +157,11 @@ module Anyway # :nodoc:
154
157
  def load_callbacks
155
158
  return @load_callbacks if instance_variable_defined?(:@load_callbacks)
156
159
 
157
- if superclass <= Anyway::Config
160
+ @load_callbacks = if superclass <= Anyway::Config
158
161
  superclass.load_callbacks.dup
159
162
  else
160
163
  []
161
- end => @load_callbacks
164
+ end
162
165
  end
163
166
 
164
167
  def config_name(val = nil)
@@ -185,24 +188,64 @@ module Anyway # :nodoc:
185
188
 
186
189
  return @env_prefix if instance_variable_defined?(:@env_prefix)
187
190
 
188
- if superclass < Anyway::Config && superclass.explicit_config_name?
191
+ @env_prefix = if superclass < Anyway::Config && superclass.explicit_config_name?
189
192
  superclass.env_prefix
190
193
  else
191
194
  config_name.upcase
192
- end => @env_prefix
195
+ end
193
196
  end
194
197
 
195
198
  def new_empty_config() = {}
196
199
 
200
+ def coerce_types(mapping)
201
+ coercion_mapping.deep_merge!(mapping)
202
+ end
203
+
204
+ def coercion_mapping
205
+ return @coercion_mapping if instance_variable_defined?(:@coercion_mapping)
206
+
207
+ @coercion_mapping = if superclass < Anyway::Config
208
+ superclass.coercion_mapping.deep_dup
209
+ else
210
+ {}
211
+ end
212
+ end
213
+
214
+ def type_caster(val = nil)
215
+ return @type_caster unless val.nil?
216
+
217
+ @type_caster ||=
218
+ if coercion_mapping.empty?
219
+ fallback_type_caster
220
+ else
221
+ ::Anyway::TypeCaster.new(coercion_mapping, fallback: fallback_type_caster)
222
+ end
223
+ end
224
+
225
+ def fallback_type_caster(val = nil)
226
+ return (@fallback_type_caster = val) unless val.nil?
227
+
228
+ return @fallback_type_caster if instance_variable_defined?(:@fallback_type_caster)
229
+
230
+ @fallback_type_caster = if superclass < Anyway::Config
231
+ superclass.fallback_type_caster.deep_dup
232
+ else
233
+ ::Anyway::AutoCast
234
+ end
235
+ end
236
+
237
+ def disable_auto_cast!
238
+ @fallback_type_caster = ::Anyway::NoCast
239
+ end
240
+
197
241
  private
198
242
 
199
243
  def define_config_accessor(*names)
200
244
  names.each do |name|
201
245
  accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
202
246
  def #{name}=(val)
203
- __trace__&.record_value(val, \"#{name}\", Tracing.current_trace_source)
204
- # DEPRECATED: instance variable set will be removed in 2.1
205
- @#{name} = values[:#{name}] = val
247
+ __trace__&.record_value(val, \"#{name}\", **Tracing.current_trace_source)
248
+ values[:#{name}] = val
206
249
  end
207
250
 
208
251
  def #{name}
@@ -215,9 +258,9 @@ module Anyway # :nodoc:
215
258
  def accessors_module
216
259
  return @accessors_module if instance_variable_defined?(:@accessors_module)
217
260
 
218
- Module.new.tap do |mod|
261
+ @accessors_module = Module.new.tap do |mod|
219
262
  include mod
220
- end => @accessors_module
263
+ end
221
264
  end
222
265
 
223
266
  def build_config_name
@@ -229,7 +272,7 @@ module Anyway # :nodoc:
229
272
  # handle two cases:
230
273
  # - SomeModule::Config => "some_module"
231
274
  # - SomeConfig => "some"
232
- unless name =~ /^(\w+)(\:\:)?Config$/
275
+ unless name =~ /^(\w+)(::)?Config$/
233
276
  raise "Couldn't infer config name, please, specify it explicitly" \
234
277
  "via `config_name :my_config`"
235
278
  end
@@ -285,22 +328,19 @@ module Anyway # :nodoc:
285
328
  def load(overrides = nil)
286
329
  base_config = self.class.defaults.deep_dup
287
330
 
288
- Tracing.capture do
331
+ trace = Tracing.capture do
289
332
  Tracing.trace!(:defaults) { base_config }
290
333
 
291
- load_from_sources(
292
- base_config,
293
- name: config_name,
294
- env_prefix: env_prefix,
295
- config_path: resolve_config_path(config_name, env_prefix)
296
- )
334
+ config_path = resolve_config_path(config_name, env_prefix)
335
+
336
+ load_from_sources(base_config, name: config_name, env_prefix:, config_path:)
297
337
 
298
338
  if overrides
299
339
  Tracing.trace!(:load) { overrides }
300
340
 
301
- base_config.deep_merge!(overrides)
341
+ Utils.deep_merge!(base_config, overrides)
302
342
  end
303
- end => trace
343
+ end
304
344
 
305
345
  base_config.each do |key, val|
306
346
  write_config_attr(key.to_sym, val)
@@ -321,7 +361,7 @@ module Anyway # :nodoc:
321
361
 
322
362
  def load_from_sources(base_config, **options)
323
363
  Anyway.loaders.each do |(_id, loader)|
324
- base_config.deep_merge!(loader.call(**options))
364
+ Utils.deep_merge!(base_config, loader.call(**options))
325
365
  end
326
366
  base_config
327
367
  end
@@ -375,7 +415,7 @@ module Anyway # :nodoc:
375
415
  values[name].nil? || (values[name].is_a?(String) && values[name].empty?)
376
416
  end.then do |missing|
377
417
  next if missing.empty?
378
- raise_validation_error "The following config parameters are missing or empty: #{missing.join(", ")}"
418
+ raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
379
419
  end
380
420
  end
381
421
 
@@ -383,11 +423,16 @@ module Anyway # :nodoc:
383
423
  key = key.to_sym
384
424
  return unless self.class.config_attributes.include?(key)
385
425
 
426
+ val = __type_caster__.coerce(key, val)
386
427
  public_send(:"#{key}=", val)
387
428
  end
388
429
 
389
430
  def raise_validation_error(msg)
390
431
  raise ValidationError, msg
391
432
  end
433
+
434
+ def __type_caster__
435
+ self.class.type_caster
436
+ end
392
437
  end
393
438
  end
@@ -12,11 +12,15 @@ module Anyway
12
12
  # my_config = Anyway::Config.for(:my_app)
13
13
  # # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"]
14
14
  #
15
- def for(name, **options)
15
+ def for(name, auto_cast: true, **options)
16
16
  config = allocate
17
17
  options[:env_prefix] ||= name.to_s.upcase
18
18
  options[:config_path] ||= config.resolve_config_path(name, options[:env_prefix])
19
- config.load_from_sources(new_empty_config, name: name, **options)
19
+
20
+ raw_config = config.load_from_sources(new_empty_config, name:, **options)
21
+ return raw_config unless auto_cast
22
+
23
+ AutoCast.call(raw_config)
20
24
  end
21
25
  end
22
26
 
data/lib/anyway/env.rb CHANGED
@@ -49,7 +49,7 @@ module Anyway
49
49
  path = key.sub(/^#{prefix}_/, "").downcase
50
50
 
51
51
  paths = path.split("__")
52
- trace!(:env, *paths, key: key) { data.bury(type_cast.call(val), *paths) }
52
+ trace!(:env, *paths, key:) { data.bury(type_cast.call(val), *paths) }
53
53
  end
54
54
  end
55
55
  end
@@ -30,6 +30,18 @@ module Anyway
30
30
  end
31
31
  end
32
32
 
33
+ refine ::Object do
34
+ def deep_dup
35
+ dup
36
+ end
37
+ end
38
+
39
+ refine ::Module do
40
+ def deep_dup
41
+ self
42
+ end
43
+ end
44
+
33
45
  using self
34
46
  end
35
47
  end
@@ -5,18 +5,6 @@ module Anyway
5
5
  # Extend Hash through refinements
6
6
  module Hash
7
7
  refine ::Hash do
8
- # From ActiveSupport http://api.rubyonrails.org/classes/Hash.html#method-i-deep_merge
9
- def deep_merge!(other_hash)
10
- merge!(other_hash) do |key, this_value, other_value|
11
- if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
12
- this_value.deep_merge!(other_value)
13
- this_value
14
- else
15
- other_value
16
- end
17
- end
18
- end
19
-
20
8
  def stringify_keys!
21
9
  keys.each do |key|
22
10
  value = delete(key)
@@ -38,6 +26,16 @@ module Anyway
38
26
  end
39
27
  hash[last_key] = val
40
28
  end
29
+
30
+ def deep_merge!(other)
31
+ other.each do |k, v|
32
+ if key?(k) && self[k].is_a?(::Hash) && v.is_a?(::Hash)
33
+ self[k].deep_merge!(v)
34
+ else
35
+ self[k] = v
36
+ end
37
+ end
38
+ end
41
39
  end
42
40
 
43
41
  using self
@@ -7,7 +7,7 @@ module Anyway
7
7
 
8
8
  class << self
9
9
  def call(local: Anyway::Settings.use_local_files, **opts)
10
- new(local: local).call(**opts)
10
+ new(local:).call(**opts)
11
11
  end
12
12
  end
13
13
 
@@ -6,7 +6,9 @@ module Anyway
6
6
  module Loaders
7
7
  class Env < Base
8
8
  def call(env_prefix:, **_options)
9
- Anyway.env.fetch_with_trace(env_prefix).then do |(conf, trace)|
9
+ env = ::Anyway::Env.new(type_cast: ::Anyway::NoCast)
10
+
11
+ env.fetch_with_trace(env_prefix).then do |(conf, trace)|
10
12
  Tracing.current_trace&.merge!(trace)
11
13
  conf
12
14
  end
@@ -17,7 +17,7 @@ module Anyway
17
17
  local_path = local_config_path(config_path)
18
18
  local_config = trace!(:yml, path: relative_config_path(local_path).to_s) { load_local_yml(local_path) }
19
19
 
20
- base_config.deep_merge!(local_config)
20
+ Utils.deep_merge!(base_config, local_config)
21
21
  end
22
22
 
23
23
  private
@@ -25,15 +25,19 @@ module Anyway
25
25
  def parse_yml(path)
26
26
  return {} unless File.file?(path)
27
27
  require "yaml" unless defined?(::YAML)
28
+
29
+ # By default, YAML load will return `false` when the yaml document is
30
+ # empty. When this occurs, we return an empty hash instead, to match
31
+ # the interface when no config file is present.
28
32
  if defined?(ERB)
29
- ::YAML.load(ERB.new(File.read(path)).result) # rubocop:disable Security/YAMLLoad
33
+ ::YAML.load(ERB.new(File.read(path)).result) || {} # rubocop:disable Security/YAMLLoad
30
34
  else
31
- ::YAML.load_file(path)
35
+ ::YAML.load_file(path) || {}
32
36
  end
33
37
  end
34
38
 
35
- alias load_base_yml parse_yml
36
- alias load_local_yml parse_yml
39
+ alias_method :load_base_yml, :parse_yml
40
+ alias_method :load_local_yml, :parse_yml
37
41
 
38
42
  def local_config_path(path)
39
43
  path.sub(/\.yml/, ".local.yml")
@@ -8,8 +8,6 @@ module Anyway # :nodoc:
8
8
  class << self
9
9
  def call(options)
10
10
  OptionParser.new do |opts|
11
- opts.accept(AutoCast) { AutoCast.call(_1) }
12
-
13
11
  options.each do |key, descriptor|
14
12
  opts.on(*option_parser_on_args(key, **descriptor)) do |val|
15
13
  yield [key, val]
@@ -20,7 +18,7 @@ module Anyway # :nodoc:
20
18
 
21
19
  private
22
20
 
23
- def option_parser_on_args(key, flag: false, desc: nil, type: AutoCast)
21
+ def option_parser_on_args(key, flag: false, desc: nil, type: ::String)
24
22
  on_args = ["--#{key.to_s.tr("_", "-")}#{flag ? "" : " VALUE"}"]
25
23
  on_args << type unless flag
26
24
  on_args << desc unless desc.nil?
@@ -70,13 +70,11 @@ module Anyway
70
70
  end
71
71
 
72
72
  def option_parser
73
- @option_parser ||= begin
74
- OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
75
- write_config_attr(key, val)
76
- end.tap do |parser|
77
- self.class.option_parser_extensions.map do |extension|
78
- extension.call(parser, self)
79
- end
73
+ @option_parser ||= OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
74
+ write_config_attr(key, val)
75
+ end.tap do |parser|
76
+ self.class.option_parser_extensions.map do |extension|
77
+ extension.call(parser, self)
80
78
  end
81
79
  end
82
80
  end
@@ -22,15 +22,15 @@ module Anyway
22
22
  :credentials,
23
23
  store: credentials_path
24
24
  ) do
25
- ::Rails.application.credentials.public_send(name)
25
+ ::Rails.application.credentials.config[name.to_sym]
26
26
  end.then do |creds|
27
- config.deep_merge!(creds) if creds
27
+ Utils.deep_merge!(config, creds) if creds
28
28
  end
29
29
 
30
30
  if use_local?
31
31
  trace!(:credentials, store: LOCAL_CONTENT_PATH) do
32
32
  local_credentials(name)
33
- end.then { |creds| config.deep_merge!(creds) if creds }
33
+ end.then { |creds| Utils.deep_merge!(config, creds) if creds }
34
34
  end
35
35
 
36
36
  config
@@ -48,7 +48,7 @@ module Anyway
48
48
  key_path: ::Rails.root.join("config/credentials/local.key")
49
49
  )
50
50
 
51
- creds.public_send(name)
51
+ creds.config[name.to_sym]
52
52
  end
53
53
 
54
54
  def credentials_path
@@ -15,7 +15,7 @@ module Anyway
15
15
  trace!(:secrets) do
16
16
  secrets.public_send(name)
17
17
  end.then do |secrets|
18
- config.deep_merge!(secrets) if secrets
18
+ Utils.deep_merge!(config, secrets) if secrets
19
19
  end
20
20
 
21
21
  config
@@ -24,13 +24,11 @@ module Anyway
24
24
  private
25
25
 
26
26
  def secrets
27
- @secrets ||= begin
28
- ::Rails.application.secrets.tap do |_|
29
- # Reset secrets state if the app hasn't been initialized
30
- # See https://github.com/palkan/anyway_config/issues/14
31
- next if ::Rails.application.initialized?
32
- ::Rails.application.remove_instance_variable(:@secrets)
33
- end
27
+ @secrets ||= ::Rails.application.secrets.tap do |_|
28
+ # Reset secrets state if the app hasn't been initialized
29
+ # See https://github.com/palkan/anyway_config/issues/14
30
+ next if ::Rails.application.initialized?
31
+ ::Rails.application.remove_instance_variable(:@secrets)
34
32
  end
35
33
  end
36
34
  end
@@ -5,11 +5,22 @@ module Anyway
5
5
  module Loaders
6
6
  class YAML < Anyway::Loaders::YAML
7
7
  def load_base_yml(*)
8
+ parsed_yml = super
9
+ return parsed_yml unless environmental?(parsed_yml)
10
+
8
11
  super[::Rails.env] || {}
9
12
  end
10
13
 
11
14
  private
12
15
 
16
+ def environmental?(parsed_yml)
17
+ return true unless Settings.future.unwrap_known_environments
18
+ # likely
19
+ return true if parsed_yml.key?(::Rails.env)
20
+ # less likely
21
+ ::Rails.application.config.anyway_config.known_environments.any? { parsed_yml.key?(_1) }
22
+ end
23
+
13
24
  def relative_config_path(path)
14
25
  Pathname.new(path).relative_path_from(::Rails.root)
15
26
  end
@@ -9,8 +9,13 @@ end
9
9
 
10
10
  module Anyway
11
11
  class Settings
12
+ class Future
13
+ setting :unwrap_known_environments, true
14
+ end
15
+
12
16
  class << self
13
17
  attr_reader :autoload_static_config_path, :autoloader
18
+ attr_accessor :known_environments
14
19
 
15
20
  if defined?(::Zeitwerk)
16
21
  def autoload_static_config_path=(val)
@@ -22,10 +27,11 @@ module Anyway
22
27
 
23
28
  @autoload_static_config_path = val
24
29
 
25
- # See https://github.com/rails/rails/blob/8ab4fd12f18203b83d0f252db96d10731485ff6a/railties/lib/rails/autoloaders.rb#L10
30
+ # See Rails 6 https://github.com/rails/rails/blob/8ab4fd12f18203b83d0f252db96d10731485ff6a/railties/lib/rails/autoloaders.rb#L10
31
+ # and Rails 7 https://github.com/rails/rails/blob/5462fbd5de1900c1b1ce1c9dc11c1a2d8cdcd809/railties/lib/rails/autoloaders.rb#L15
26
32
  @autoloader = Zeitwerk::Loader.new.tap do |loader|
27
33
  loader.tag = "anyway.config"
28
- loader.inflector = ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector
34
+ loader.inflector = defined?(ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector) ? ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector : ::Rails::Autoloaders::Inflector
29
35
  loader.push_dir(::Rails.root.join(val))
30
36
  loader.setup
31
37
  end
@@ -56,5 +62,6 @@ module Anyway
56
62
  end
57
63
 
58
64
  self.default_config_path = ->(name) { ::Rails.root.join("config", "#{name}.yml") }
65
+ self.known_environments = %w[test development production]
59
66
  end
60
67
  end
data/lib/anyway/rbs.rb ADDED
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module RBSGenerator
5
+ TYPE_TO_CLASS = {
6
+ string: "String",
7
+ integer: "Integer",
8
+ float: "Float",
9
+ date: "Date",
10
+ datetime: "DateTime",
11
+ uri: "URI",
12
+ boolean: "bool"
13
+ }.freeze
14
+
15
+ # Generate RBS signature from a config class
16
+ def to_rbs
17
+ *namespace, class_name = name.split("::")
18
+
19
+ buf = []
20
+ indent = 0
21
+ interface_name = "_Config"
22
+
23
+ if namespace.empty?
24
+ interface_name = "_#{class_name}"
25
+ else
26
+ buf << "module #{namespace.join("::")}"
27
+ indent += 1
28
+ end
29
+
30
+ # Using interface emulates a module we include to provide getters and setters
31
+ # (thus making `super` possible)
32
+ buf << "#{" " * indent}interface #{interface_name}"
33
+ indent += 1
34
+
35
+ # Generating setters and getters for config attributes
36
+ config_attributes.each do |param|
37
+ type = coercion_mapping[param] || defaults[param.to_s]
38
+
39
+ type =
40
+ case type
41
+ in NilClass
42
+ "untyped"
43
+ in Symbol
44
+ TYPE_TO_CLASS.fetch(type) { defaults[param] ? "Symbol" : "untyped" }
45
+ in Array
46
+ "Array[untyped]"
47
+ in array:, type:, **nil
48
+ "Array[#{TYPE_TO_CLASS.fetch(type, "untyped")}]"
49
+ in Hash
50
+ "Hash[string,untyped]"
51
+ in TrueClass | FalseClass
52
+ "bool"
53
+ else
54
+ type.class.to_s
55
+ end
56
+
57
+ getter_type = type
58
+ getter_type = "#{type}?" unless required_attributes.include?(param)
59
+
60
+ buf << "#{" " * indent}def #{param}: () -> #{getter_type}"
61
+ buf << "#{" " * indent}def #{param}=: (#{type}) -> void"
62
+
63
+ if type == "bool" || type == "bool?"
64
+ buf << "#{" " * indent}def #{param}?: () -> #{getter_type}"
65
+ end
66
+ end
67
+
68
+ indent -= 1
69
+ buf << "#{" " * indent}end"
70
+
71
+ buf << ""
72
+
73
+ buf << "#{" " * indent}class #{class_name} < #{superclass.name}"
74
+ indent += 1
75
+
76
+ buf << "#{" " * indent}include #{interface_name}"
77
+
78
+ indent -= 1
79
+ buf << "#{" " * indent}end"
80
+
81
+ unless namespace.empty?
82
+ buf << "end"
83
+ end
84
+
85
+ buf << ""
86
+
87
+ buf.join("\n")
88
+ end
89
+ end
90
+
91
+ Config.extend RBSGenerator
92
+ end