anyway_config 2.1.0 → 2.2.0

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.
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