anyway_config 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae67246988f82692f84ae07b01642fbca38c9f1593b0e4292b335e08654bbf7e
4
- data.tar.gz: 045f36927093f3e7929889918dff12d63c61ef58ffcb5fc5793f017dd2da1c5e
3
+ metadata.gz: 87d32ae676d03c8a9fd3a0b22efe3321d76661f226438f52fd313f4d73b1876d
4
+ data.tar.gz: 82f2ab5d663b03622f650fbcf8e37216a71d81fdfd9270d54f539fd6253ada9d
5
5
  SHA512:
6
- metadata.gz: de0f8281daad0d2da4ad557f7099602a46d7fa4476f0a3adfb5b7f14c3abcd44fd020a89fd661a4c9ebeb9b5e70550d37f1b16d01bf68d535ab63877b9ae1661
7
- data.tar.gz: 04de1b39c49b4dc39faa12ee199143ca64b3722cbc547e8c41b41bc3e7e365848bae2f06dfc9f99db5693782df4239cd5bbdca99cf2e58e50f38147b2b5dc95e
6
+ metadata.gz: 9f7952609c7964cc9866fe530a9a519cf981f0551f9979c72f38b2acd31e59edd98d6234f095da99419acd462bcfc7c6b7d5016c41ded3bb0206d7f1e03550b3
7
+ data.tar.gz: 1559b8e140b7df15164134233fd6c65e11d9283f94c9495a0d92f6afdcc69271cec68a7bc6f5052b8ec06dab35e33f1cadd32f4f044701156bfcc0ca4676ba41
data/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 2.2.0 ⛓
6
+
7
+ - Add RBS signatures and generator. ([@palkan][])
8
+
9
+ Anyway Config now ships with the basic RBS support. To use config types with Steep, add `library "anyway_config"` to your Steepfile.
10
+
11
+ We also provide an API to generate a signature for you config class: `MyConfig.to_rbs`. You can use this method to generate a scaffold for your config class.
12
+
13
+ - Add type coercion support. ([@palkan][])
14
+
15
+ Example:
16
+
17
+ ```ruby
18
+ class CoolConfig < Anyway::Config
19
+ attr_config :port, :user
20
+
21
+ coerce_types port: :string, user: {dob: :date}
22
+ end
23
+
24
+ ENV["COOL_USER__DOB"] = "1989-07-01"
25
+
26
+ config = CoolConfig.new({port: 8080})
27
+ config.port == "8080" #=> true
28
+ config.user["dob"] == Date.new(1989, 7, 1) #=> true
29
+ ```
30
+
31
+ You can also add `.disable_auto_cast!` to your config class to disable automatic conversion.
32
+
33
+ **Warning** Now values from all sources are coerced (e.g., YAML files). That could lead to a different behaviour.
34
+
35
+ - Do not dup modules/classes passed as configuration values. ([@palkan][])
36
+
37
+ - Handle loading empty YAML config files. ([@micahlee][])
38
+
5
39
  ## 2.1.0 (2020-12-29)
6
40
 
7
41
  - Drop deprecated `attr_config` instance variables support.
data/README.md CHANGED
@@ -42,12 +42,14 @@ For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_con
42
42
  - [Generators](#generators)
43
43
  - [Using with Ruby applications](#using-with-ruby)
44
44
  - [Environment variables](#environment-variables)
45
+ - [Type coercion](#type-coercion)
45
46
  - [Local configuration](#local-files)
46
47
  - [Data loaders](#data-loaders)
47
48
  - [Source tracing](#tracing)
48
49
  - [Pattern matching](#pattern-matching)
49
50
  - [Test helpers](#test-helpers)
50
51
  - [OptionParser integration](#optionparser-integration)
52
+ - [RBS support](#rbs-support)
51
53
 
52
54
  ## Main concepts
53
55
 
@@ -94,7 +96,7 @@ Or adding to your project:
94
96
 
95
97
  ```ruby
96
98
  # Gemfile
97
- gem "anyway_config", "~> 2.0.0"
99
+ gem "anyway_config", "~> 2.0"
98
100
  ```
99
101
 
100
102
  ### Supported Ruby versions
@@ -269,6 +271,7 @@ This feature is similar to `Rails.application.config_for` but more powerful:
269
271
  | Load data from environment | ❌ | ✅ |
270
272
  | Load data from [custom sources](#data-loaders) | ❌ | ✅ |
271
273
  | Local config files | ❌ | ✅ |
274
+ | Type coercion | ❌ | ✅ |
272
275
  | [Source tracing](#tracing) | ❌ | ✅ |
273
276
  | Return Hash with indifferent access | ❌ | ✅ |
274
277
  | Support ERB\* within `config/app.yml` | ✅ | ✅ |
@@ -331,7 +334,7 @@ Your config is filled up with values from the following sources (ordered by prio
331
334
 
332
335
  1) **YAML configuration files**: `RAILS_ROOT/config/my_cool_gem.yml`.
333
336
 
334
- Recognizes Rails environment, supports `ERB`:
337
+ Rails environment is used as the namespace (required); supports `ERB`:
335
338
 
336
339
  ```yml
337
340
  test:
@@ -451,6 +454,22 @@ config.anyway_config.autoload_static_config_path = "path/to/configs"
451
454
  **NOTE:** Configs loaded from the `autoload_static_config_path` are **not reloaded in development**. We call them _static_. So, it makes sense to keep only configs necessary for initialization in this folder. Other configs, _dynamic_, could be stored in `app/configs`.
452
455
  Or you can store everything in `app/configs` by setting `config.anyway_config.autoload_static_config_path = "app/configs"`.
453
456
 
457
+ **NOTE 2**: Since _static_ configs are loaded before initializers, it's not possible to use custom inflection Rules (usually defined in `config/initializers/inflections.rb`) to resolve constant names from files. If you rely on custom inflection rules (see, for example, [#81](https://github.com/palkan/anyway_config/issues/81)), we recommend configuration Rails inflector before initialization as well:
458
+
459
+ ```ruby
460
+ # config/application.rb
461
+
462
+ # ...
463
+
464
+ require_relative "initializers/inflections"
465
+
466
+ module SomeApp
467
+ class Application < Rails::Application
468
+ # ...
469
+ end
470
+ end
471
+ ```
472
+
454
473
  ### Generators
455
474
 
456
475
  Anyway Config provides Rails generators to create new config classes:
@@ -525,13 +544,15 @@ Environmental variables for your config should start with your config name, uppe
525
544
 
526
545
  For example, if your config name is "mycoolgem", then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
527
546
 
528
- Environment variables are automatically type cast:
547
+ By default, environment variables are automatically type cast\*:
529
548
 
530
549
  - `"True"`, `"t"` and `"yes"` to `true`;
531
550
  - `"False"`, `"f"` and `"no"` to `false`;
532
551
  - `"nil"` and `"null"` to `nil` (do you really need it?);
533
552
  - `"123"` to 123 and `"3.14"` to 3.14.
534
553
 
554
+ \* See below for coercion customization.
555
+
535
556
  *Anyway Config* supports nested (_hashed_) env variables—just separate keys with double-underscore.
536
557
 
537
558
  For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
@@ -549,6 +570,87 @@ If you want to provide a text-like env variable which contains commas then wrap
549
570
  MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
550
571
  ```
551
572
 
573
+ ## Type coercion
574
+
575
+ > 🆕 v2.2.0
576
+
577
+ You can define custom type coercion rules to convert string data to config values. To do that, use `.coerce_types` method:
578
+
579
+ ```ruby
580
+ class CoolConfig < Anyway::Config
581
+ config_name :cool
582
+ attr_config port: 8080,
583
+ host: "localhost",
584
+ user: {name: "admin", password: "admin"}
585
+
586
+ coerce_types port: :string, user: {dob: :date}
587
+ end
588
+
589
+ ENV["COOL_USER__DOB"] = "1989-07-01"
590
+
591
+ config = CoolConfig.new
592
+ config.port == "8080" # Even though we defined the default value as int, it's converted into a string
593
+ config.user["dob"] == Date.new(1989, 7, 1) #=> true
594
+ ```
595
+
596
+ Type coercion is especially useful to deal with array values:
597
+
598
+ ```ruby
599
+ # To define an array type, provide a hash with two keys:
600
+ # - type — elements type
601
+ # - array: true — mark the parameter as array
602
+ coerce_types list: {type: :string, array: true}
603
+ ```
604
+
605
+ It's also could be useful to explicitly define non-array types (to avoid confusion):
606
+
607
+ ```ruby
608
+ coerce_types non_list: :string
609
+ ```
610
+
611
+ Finally, it's possible to disable auto-casting for a particular config completely:
612
+
613
+ ```ruby
614
+ class CoolConfig < Anyway::Config
615
+ attr_config port: 8080,
616
+ host: "localhost",
617
+ user: {name: "admin", password: "admin"}
618
+
619
+ disable_auto_cast!
620
+ end
621
+
622
+ ENV["COOL_PORT"] = "443"
623
+
624
+ CoolConfig.new.port == "443" #=> true
625
+ ```
626
+
627
+ **IMPORTANT**: Values provided explicitly (via attribute writers) are not coerced. Coercion is only happening during the load phase.
628
+
629
+ The following types are supported out-of-the-box: `:string`, `:integer`, `:float`, `:date`, `:datetime`, `:uri`, `:boolean`.
630
+
631
+ You can use custom deserializers by passing a callable object instead of a type name:
632
+
633
+ ```ruby
634
+ COLOR_TO_HEX = lambda do |raw|
635
+ case raw
636
+ when "red"
637
+ "#ff0000"
638
+ when "green"
639
+ "#00ff00"
640
+ when "blue"
641
+ "#0000ff"
642
+ end
643
+ end
644
+
645
+ class CoolConfig < Anyway::Config
646
+ attr_config :color
647
+
648
+ coerce_types color: COLOR_TO_HEX
649
+ end
650
+
651
+ CoolConfig.new({color: "red"}).color #=> "#ff0000"
652
+ ```
653
+
552
654
  ## Local files
553
655
 
554
656
  It's useful to have a personal, user-specific configuration in development, which extends the project-wide one.
@@ -610,7 +712,7 @@ In order to support [source tracing](#tracing), you need to wrap the resulting H
610
712
 
611
713
  ```ruby
612
714
  def call(name:, **_opts)
613
- trace!(source: :chamber) do
715
+ trace!(:chamber) do
614
716
  Chamber.env.to_h[name] || {}
615
717
  end
616
718
  end
@@ -790,6 +892,62 @@ describe_options(
790
892
  )
791
893
  ```
792
894
 
895
+ ## RBS support
896
+
897
+ Anyway Config comes with Ruby type signatures (RBS).
898
+
899
+ To use them with Steep, add `library "anyway_config"` to your Steepfile.
900
+
901
+ We also provide an API to generate a type signature for your config class:
902
+
903
+ ```ruby
904
+ class MyGem::Config < Anyway::Config
905
+ attr_config :host, port: 8080, tags: [], debug: false
906
+
907
+ coerce_types host: :string, port: :integer,
908
+ tags: {type: :string, array: true}
909
+
910
+ required :host
911
+ end
912
+ ```
913
+
914
+ Then calling `MyGem::Config.to_rbs` will return the following signature:
915
+
916
+ ```rbs
917
+ module MyGem
918
+ interface _Config
919
+ def host: () -> String
920
+ def host=: (String) -> void
921
+ def port: () -> String?
922
+ def port=: (String) -> void
923
+ def tags: () -> Array[String]?
924
+ def tags=: (Array[String]) -> void
925
+ def debug: () -> bool
926
+ def debug?: () -> bool
927
+ def debug=: (bool) -> void
928
+ end
929
+
930
+ class Config < Anyway::Config
931
+ include _Config
932
+ end
933
+ end
934
+ ```
935
+
936
+ ### Handling `on_load`
937
+
938
+ When we use `on_load` callback with a block, we switch the context (via `instance_eval`), and we need to provide type hints for the type checker. Here is an example:
939
+
940
+ ```ruby
941
+ class MyConfig < Anyway::Config
942
+ on_load do
943
+ # @type self : MyConfig
944
+ raise_validation_error("host is invalid") if host.start_with?("localhost")
945
+ end
946
+ end
947
+ ```
948
+
949
+ Yeah, a lot of annotations 😞 Welcome to the type-safe world!
950
+
793
951
  ## Contributing
794
952
 
795
953
  Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/anyway_config](https://github.com/palkan/anyway_config).
@@ -44,6 +44,7 @@ module Anyway # :nodoc:
44
44
  to_h
45
45
  to_source_trace
46
46
  write_config_attr
47
+ __type_caster__
47
48
  ].freeze
48
49
 
49
50
  class Error < StandardError; end
@@ -196,6 +197,47 @@ module Anyway # :nodoc:
196
197
 
197
198
  def new_empty_config() = {}
198
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
+
199
241
  private
200
242
 
201
243
  def define_config_accessor(*names)
@@ -373,7 +415,7 @@ module Anyway # :nodoc:
373
415
  values[name].nil? || (values[name].is_a?(String) && values[name].empty?)
374
416
  end.then do |missing|
375
417
  next if missing.empty?
376
- 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(", ")}"
377
419
  end
378
420
  end
379
421
 
@@ -381,11 +423,16 @@ module Anyway # :nodoc:
381
423
  key = key.to_sym
382
424
  return unless self.class.config_attributes.include?(key)
383
425
 
426
+ val = __type_caster__.coerce(key, val)
384
427
  public_send(:"#{key}=", val)
385
428
  end
386
429
 
387
430
  def raise_validation_error(msg)
388
431
  raise ValidationError, msg
389
432
  end
433
+
434
+ def __type_caster__
435
+ self.class.type_caster
436
+ end
390
437
  end
391
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: name, **options)
21
+ return raw_config unless auto_cast
22
+
23
+ AutoCast.call(raw_config)
20
24
  end
21
25
  end
22
26
 
@@ -34,11 +34,11 @@ module Anyway
34
34
 
35
35
  def record_value(val, *path, **opts)
36
36
  key = path.pop
37
- if val.is_a?(Hash)
37
+ trace = if val.is_a?(Hash)
38
38
  Trace.new.tap { _1.merge_values(val, **opts) }
39
39
  else
40
40
  Trace.new(:value, val, **opts)
41
- end => trace
41
+ end
42
42
 
43
43
  target_trace = path.empty? ? self : value.dig(*path)
44
44
  target_trace.value[key.to_s] = trace
@@ -7,27 +7,47 @@ module Anyway
7
7
  # and doesn't start/end with quote
8
8
  ARRAY_RXP = /\A[^'"].*\s*,\s*.*[^'"]\z/
9
9
 
10
- def self.call(val)
11
- return val unless String === val
10
+ class << self
11
+ def call(val)
12
+ return val unless val.is_a?(::Hash) || val.is_a?(::String)
12
13
 
13
- case val
14
- when ARRAY_RXP
15
- val.split(/\s*,\s*/).map { |_1| call(_1) }
16
- when /\A(true|t|yes|y)\z/i
17
- true
18
- when /\A(false|f|no|n)\z/i
19
- false
20
- when /\A(nil|null)\z/i
21
- nil
22
- when /\A\d+\z/
23
- val.to_i
24
- when /\A\d*\.\d+\z/
25
- val.to_f
26
- when /\A['"].*['"]\z/
27
- val.gsub(/(\A['"]|['"]\z)/, "")
28
- else
29
- val
14
+ case val
15
+ when Hash
16
+ val.transform_values { |_1| call(_1) }
17
+ when ARRAY_RXP
18
+ val.split(/\s*,\s*/).map { |_1| call(_1) }
19
+ when /\A(true|t|yes|y)\z/i
20
+ true
21
+ when /\A(false|f|no|n)\z/i
22
+ false
23
+ when /\A(nil|null)\z/i
24
+ nil
25
+ when /\A\d+\z/
26
+ val.to_i
27
+ when /\A\d*\.\d+\z/
28
+ val.to_f
29
+ when /\A['"].*['"]\z/
30
+ val.gsub(/(\A['"]|['"]\z)/, "")
31
+ else
32
+ val
33
+ end
34
+ end
35
+
36
+ def cast_hash(obj)
37
+ obj.transform_values do |val|
38
+ val.is_a?(::Hash) ? cast_hash(val) : call(val)
39
+ end
40
+ end
41
+
42
+ def coerce(_key, val)
43
+ call(val)
30
44
  end
31
45
  end
32
46
  end
47
+
48
+ module NoCast
49
+ def self.call(val) ; val; end
50
+
51
+ def self.coerce(_key, val) ; val; end
52
+ end
33
53
  end
@@ -44,6 +44,7 @@ module Anyway # :nodoc:
44
44
  to_h
45
45
  to_source_trace
46
46
  write_config_attr
47
+ __type_caster__
47
48
  ].freeze
48
49
 
49
50
  class Error < StandardError; end
@@ -196,6 +197,47 @@ module Anyway # :nodoc:
196
197
 
197
198
  def new_empty_config() ; {}; end
198
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
+
199
241
  private
200
242
 
201
243
  def define_config_accessor(*names)
@@ -373,7 +415,7 @@ module Anyway # :nodoc:
373
415
  values[name].nil? || (values[name].is_a?(String) && values[name].empty?)
374
416
  end.then do |missing|
375
417
  next if missing.empty?
376
- 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(", ")}"
377
419
  end
378
420
  end
379
421
 
@@ -381,11 +423,16 @@ module Anyway # :nodoc:
381
423
  key = key.to_sym
382
424
  return unless self.class.config_attributes.include?(key)
383
425
 
426
+ val = __type_caster__.coerce(key, val)
384
427
  public_send(:"#{key}=", val)
385
428
  end
386
429
 
387
430
  def raise_validation_error(msg)
388
431
  raise ValidationError, msg
389
432
  end
433
+
434
+ def __type_caster__
435
+ self.class.type_caster
436
+ end
390
437
  end
391
438
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+ using RubyNext;
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; when ((__m__ = type)) && false
41
+ when (NilClass === __m__)
42
+ "untyped"
43
+ when (Symbol === __m__)
44
+ TYPE_TO_CLASS.fetch(type) { defaults[param] ? "Symbol" : "untyped" }
45
+ when (Array === __m__)
46
+ "Array[untyped]"
47
+ when ((__m__.respond_to?(:deconstruct_keys) && (((__m_hash__src__ = __m__.deconstruct_keys(nil)) || true) && (Hash === __m_hash__src__ || Kernel.raise(TypeError, "#deconstruct_keys must return Hash"))) && (__m_hash__ = __m_hash__src__.dup)) && ((__m_hash__.key?(:array) && __m_hash__.key?(:type)) && (((array = __m_hash__.delete(:array)) || true) && (((type = __m_hash__.delete(:type)) || true) && __m_hash__.empty?))))
48
+ "Array[#{TYPE_TO_CLASS.fetch(type, "untyped")}]"
49
+ when (Hash === __m__)
50
+ "Hash[string,untyped]"
51
+ when ((TrueClass === __m__) || (FalseClass === __m__))
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
@@ -34,13 +34,11 @@ module Anyway
34
34
 
35
35
  def record_value(val, *path, **opts)
36
36
  key = path.pop
37
- (__m__ = if val.is_a?(Hash)
38
- Trace.new.tap { |_1|
39
- _1.merge_values(val, **opts)
40
- }
41
- else
42
- Trace.new(:value, val, **opts)
43
- end) && (((trace = __m__) || true) || Kernel.raise(NoMatchingPatternError, __m__.inspect))
37
+ trace = if val.is_a?(Hash)
38
+ Trace.new.tap { |_1| _1.merge_values(val, **opts) }
39
+ else
40
+ Trace.new(:value, val, **opts)
41
+ end
44
42
 
45
43
  target_trace = path.empty? ? self : value.dig(*path)
46
44
  target_trace.value[key.to_s] = trace