anyway_config 2.1.0 → 2.2.3
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 +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +189 -4
- data/lib/.rbnext/2.7/anyway/auto_cast.rb +41 -21
- data/lib/.rbnext/2.7/anyway/config.rb +48 -1
- data/lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb +8 -2
- data/lib/.rbnext/2.7/anyway/rbs.rb +92 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +5 -7
- data/lib/.rbnext/2.7/anyway/type_casting.rb +143 -0
- data/lib/.rbnext/3.0/anyway/auto_cast.rb +53 -0
- data/lib/.rbnext/3.0/anyway/config.rb +48 -1
- data/lib/.rbnext/3.0/anyway/tracing.rb +5 -7
- data/lib/.rbnext/{1995.next → 3.1}/anyway/config.rb +48 -1
- data/lib/.rbnext/{1995.next → 3.1}/anyway/dynamic_config.rb +6 -2
- data/lib/.rbnext/{1995.next → 3.1}/anyway/env.rb +0 -0
- data/lib/.rbnext/{1995.next → 3.1}/anyway/loaders/base.rb +0 -0
- data/lib/.rbnext/{1995.next → 3.1}/anyway/tracing.rb +2 -2
- data/lib/anyway/auto_cast.rb +41 -21
- data/lib/anyway/config.rb +48 -1
- data/lib/anyway/dynamic_config.rb +6 -2
- data/lib/anyway/ext/deep_dup.rb +6 -0
- data/lib/anyway/loaders/env.rb +3 -1
- data/lib/anyway/loaders/yaml.rb +16 -4
- data/lib/anyway/option_parser_builder.rb +1 -3
- data/lib/anyway/optparse_config.rb +5 -7
- data/lib/anyway/rails/loaders/credentials.rb +2 -2
- data/lib/anyway/rails/loaders/secrets.rb +5 -7
- data/lib/anyway/rails/loaders/yaml.rb +8 -2
- data/lib/anyway/rails/settings.rb +8 -2
- data/lib/anyway/rbs.rb +92 -0
- data/lib/anyway/tracing.rb +3 -3
- data/lib/anyway/type_casting.rb +134 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +2 -0
- data/lib/generators/anyway/install/templates/application_config.rb.tt +1 -1
- data/sig/anyway_config.rbs +129 -0
- metadata +21 -16
- data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e4b1e98155e34f0220746d9230e433072143638e838f8975023c79cf75a9fe1
|
4
|
+
data.tar.gz: e5d8b4f78fe7f32fc2dd3068f90897a28f57a292596346f5bb368a36094cea36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 849c9c7fb4bc1101444dfec952a84ac3b1f605f3415d65d31ea727f176d9281cddc90e8b56ff73a3b25431126d61640931cfbc2621dc607493014e9a97d4ee3a
|
7
|
+
data.tar.gz: 840073b4420e4c1a792db2571b29eb7605cbc7066833bc4afe91defd94ba08153a4f32036a6cd09c6cf8f1da718f7f11d41bb46c8b9ea1ab0a0bfd2a81ab0a20
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,57 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 2.2.3 (2022-01-21)
|
6
|
+
|
7
|
+
- Fix Ruby 3.1 compatibility. ([@palkan][])
|
8
|
+
|
9
|
+
- Add ability to set default key for environmental YAML files. ([@skryukov])
|
10
|
+
|
11
|
+
Define a key for environmental yaml files to read default values from with `config.anyway_config.default_environmental_key = "default"`.
|
12
|
+
This way Anyway Config will try to read settings under the `"default"` key and then merge environmental settings into them.
|
13
|
+
|
14
|
+
## 2.2.2 (2020-10-26)
|
15
|
+
|
16
|
+
- Fixed regression introduced by the `#deep_merge!` refinement.
|
17
|
+
|
18
|
+
## 2.2.1 (2020-09-28)
|
19
|
+
|
20
|
+
- Minor fixes to the prev release.
|
21
|
+
|
22
|
+
## 2.2.0 ⛓ (2020-09-28)
|
23
|
+
|
24
|
+
- Add RBS signatures and generator. ([@palkan][])
|
25
|
+
|
26
|
+
Anyway Config now ships with the basic RBS support. To use config types with Steep, add `library "anyway_config"` to your Steepfile.
|
27
|
+
|
28
|
+
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.
|
29
|
+
|
30
|
+
- Add type coercion support. ([@palkan][])
|
31
|
+
|
32
|
+
Example:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class CoolConfig < Anyway::Config
|
36
|
+
attr_config :port, :user
|
37
|
+
|
38
|
+
coerce_types port: :string, user: {dob: :date}
|
39
|
+
end
|
40
|
+
|
41
|
+
ENV["COOL_USER__DOB"] = "1989-07-01"
|
42
|
+
|
43
|
+
config = CoolConfig.new({port: 8080})
|
44
|
+
config.port == "8080" #=> true
|
45
|
+
config.user["dob"] == Date.new(1989, 7, 1) #=> true
|
46
|
+
```
|
47
|
+
|
48
|
+
You can also add `.disable_auto_cast!` to your config class to disable automatic conversion.
|
49
|
+
|
50
|
+
**Warning** Now values from all sources are coerced (e.g., YAML files). That could lead to a different behaviour.
|
51
|
+
|
52
|
+
- Do not dup modules/classes passed as configuration values. ([@palkan][])
|
53
|
+
|
54
|
+
- Handle loading empty YAML config files. ([@micahlee][])
|
55
|
+
|
5
56
|
## 2.1.0 (2020-12-29)
|
6
57
|
|
7
58
|
- Drop deprecated `attr_config` instance variables support.
|
@@ -397,3 +448,4 @@ No we're dependency-free!
|
|
397
448
|
[@jastkand]: https://github.com/jastkand
|
398
449
|
[@envek]: https://github.com/Envek
|
399
450
|
[@progapandist]: https://github.com/progapandist
|
451
|
+
[@skryukov]: https://github.com/skryukov
|
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
|
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
|
-
|
337
|
+
Rails environment is used as the namespace (required); supports `ERB`:
|
335
338
|
|
336
339
|
```yml
|
337
340
|
test:
|
@@ -370,6 +373,26 @@ staging:
|
|
370
373
|
port: 3002 # This value will not be loaded at all
|
371
374
|
```
|
372
375
|
|
376
|
+
To provide default values you can use YAML anchors, but they do not deep-merge settings, so Anyway Config provides a way to define a special top-level key for default values like this:
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
config.anyway_config.default_environmental_key = "default"
|
380
|
+
```
|
381
|
+
|
382
|
+
After that, Anyway Config will start reading settings under the `"default"` key and then merge environmental settings into them.
|
383
|
+
|
384
|
+
```yml
|
385
|
+
default:
|
386
|
+
server: # This values will be loaded in all environments by default
|
387
|
+
host: localhost
|
388
|
+
port: 3002
|
389
|
+
|
390
|
+
staging:
|
391
|
+
server:
|
392
|
+
host: staging.example.com # This value will override the defaults when Rails.env.staging? is true
|
393
|
+
# port will be set to the value from the defaults — 3002
|
394
|
+
```
|
395
|
+
|
373
396
|
You can specify the lookup path for YAML files in one of the following ways:
|
374
397
|
|
375
398
|
- By setting `config.anyway_config.default_config_path` to a target directory path:
|
@@ -451,6 +474,22 @@ config.anyway_config.autoload_static_config_path = "path/to/configs"
|
|
451
474
|
**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
475
|
Or you can store everything in `app/configs` by setting `config.anyway_config.autoload_static_config_path = "app/configs"`.
|
453
476
|
|
477
|
+
**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:
|
478
|
+
|
479
|
+
```ruby
|
480
|
+
# config/application.rb
|
481
|
+
|
482
|
+
# ...
|
483
|
+
|
484
|
+
require_relative "initializers/inflections"
|
485
|
+
|
486
|
+
module SomeApp
|
487
|
+
class Application < Rails::Application
|
488
|
+
# ...
|
489
|
+
end
|
490
|
+
end
|
491
|
+
```
|
492
|
+
|
454
493
|
### Generators
|
455
494
|
|
456
495
|
Anyway Config provides Rails generators to create new config classes:
|
@@ -525,13 +564,15 @@ Environmental variables for your config should start with your config name, uppe
|
|
525
564
|
|
526
565
|
For example, if your config name is "mycoolgem", then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
|
527
566
|
|
528
|
-
|
567
|
+
By default, environment variables are automatically type cast\*:
|
529
568
|
|
530
569
|
- `"True"`, `"t"` and `"yes"` to `true`;
|
531
570
|
- `"False"`, `"f"` and `"no"` to `false`;
|
532
571
|
- `"nil"` and `"null"` to `nil` (do you really need it?);
|
533
572
|
- `"123"` to 123 and `"3.14"` to 3.14.
|
534
573
|
|
574
|
+
\* See below for coercion customization.
|
575
|
+
|
535
576
|
*Anyway Config* supports nested (_hashed_) env variables—just separate keys with double-underscore.
|
536
577
|
|
537
578
|
For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
|
@@ -549,6 +590,94 @@ If you want to provide a text-like env variable which contains commas then wrap
|
|
549
590
|
MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
|
550
591
|
```
|
551
592
|
|
593
|
+
## Type coercion
|
594
|
+
|
595
|
+
> 🆕 v2.2.0
|
596
|
+
|
597
|
+
You can define custom type coercion rules to convert string data to config values. To do that, use `.coerce_types` method:
|
598
|
+
|
599
|
+
```ruby
|
600
|
+
class CoolConfig < Anyway::Config
|
601
|
+
config_name :cool
|
602
|
+
attr_config port: 8080,
|
603
|
+
host: "localhost",
|
604
|
+
user: {name: "admin", password: "admin"}
|
605
|
+
|
606
|
+
coerce_types port: :string, user: {dob: :date}
|
607
|
+
end
|
608
|
+
|
609
|
+
ENV["COOL_USER__DOB"] = "1989-07-01"
|
610
|
+
|
611
|
+
config = CoolConfig.new
|
612
|
+
config.port == "8080" # Even though we defined the default value as int, it's converted into a string
|
613
|
+
config.user["dob"] == Date.new(1989, 7, 1) #=> true
|
614
|
+
```
|
615
|
+
|
616
|
+
Type coercion is especially useful to deal with array values:
|
617
|
+
|
618
|
+
```ruby
|
619
|
+
# To define an array type, provide a hash with two keys:
|
620
|
+
# - type — elements type
|
621
|
+
# - array: true — mark the parameter as array
|
622
|
+
coerce_types list: {type: :string, array: true}
|
623
|
+
```
|
624
|
+
|
625
|
+
You can use `type: nil` in case you don't want to coerce values, just convert a value into an array:
|
626
|
+
|
627
|
+
```ruby
|
628
|
+
# From AnyCable config (sentinels could be represented via strings or hashes)
|
629
|
+
coerce_types redis_sentinels: {type: nil, array: true}
|
630
|
+
```
|
631
|
+
|
632
|
+
It's also could be useful to explicitly define non-array types (to avoid confusion):
|
633
|
+
|
634
|
+
```ruby
|
635
|
+
coerce_types non_list: :string
|
636
|
+
```
|
637
|
+
|
638
|
+
Finally, it's possible to disable auto-casting for a particular config completely:
|
639
|
+
|
640
|
+
```ruby
|
641
|
+
class CoolConfig < Anyway::Config
|
642
|
+
attr_config port: 8080,
|
643
|
+
host: "localhost",
|
644
|
+
user: {name: "admin", password: "admin"}
|
645
|
+
|
646
|
+
disable_auto_cast!
|
647
|
+
end
|
648
|
+
|
649
|
+
ENV["COOL_PORT"] = "443"
|
650
|
+
|
651
|
+
CoolConfig.new.port == "443" #=> true
|
652
|
+
```
|
653
|
+
|
654
|
+
**IMPORTANT**: Values provided explicitly (via attribute writers) are not coerced. Coercion is only happening during the load phase.
|
655
|
+
|
656
|
+
The following types are supported out-of-the-box: `:string`, `:integer`, `:float`, `:date`, `:datetime`, `:uri`, `:boolean`.
|
657
|
+
|
658
|
+
You can use custom deserializers by passing a callable object instead of a type name:
|
659
|
+
|
660
|
+
```ruby
|
661
|
+
COLOR_TO_HEX = lambda do |raw|
|
662
|
+
case raw
|
663
|
+
when "red"
|
664
|
+
"#ff0000"
|
665
|
+
when "green"
|
666
|
+
"#00ff00"
|
667
|
+
when "blue"
|
668
|
+
"#0000ff"
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
class CoolConfig < Anyway::Config
|
673
|
+
attr_config :color
|
674
|
+
|
675
|
+
coerce_types color: COLOR_TO_HEX
|
676
|
+
end
|
677
|
+
|
678
|
+
CoolConfig.new({color: "red"}).color #=> "#ff0000"
|
679
|
+
```
|
680
|
+
|
552
681
|
## Local files
|
553
682
|
|
554
683
|
It's useful to have a personal, user-specific configuration in development, which extends the project-wide one.
|
@@ -610,7 +739,7 @@ In order to support [source tracing](#tracing), you need to wrap the resulting H
|
|
610
739
|
|
611
740
|
```ruby
|
612
741
|
def call(name:, **_opts)
|
613
|
-
trace!(
|
742
|
+
trace!(:chamber) do
|
614
743
|
Chamber.env.to_h[name] || {}
|
615
744
|
end
|
616
745
|
end
|
@@ -790,6 +919,62 @@ describe_options(
|
|
790
919
|
)
|
791
920
|
```
|
792
921
|
|
922
|
+
## RBS support
|
923
|
+
|
924
|
+
Anyway Config comes with Ruby type signatures (RBS).
|
925
|
+
|
926
|
+
To use them with Steep, add `library "anyway_config"` to your Steepfile.
|
927
|
+
|
928
|
+
We also provide an API to generate a type signature for your config class:
|
929
|
+
|
930
|
+
```ruby
|
931
|
+
class MyGem::Config < Anyway::Config
|
932
|
+
attr_config :host, port: 8080, tags: [], debug: false
|
933
|
+
|
934
|
+
coerce_types host: :string, port: :integer,
|
935
|
+
tags: {type: :string, array: true}
|
936
|
+
|
937
|
+
required :host
|
938
|
+
end
|
939
|
+
```
|
940
|
+
|
941
|
+
Then calling `MyGem::Config.to_rbs` will return the following signature:
|
942
|
+
|
943
|
+
```rbs
|
944
|
+
module MyGem
|
945
|
+
interface _Config
|
946
|
+
def host: () -> String
|
947
|
+
def host=: (String) -> void
|
948
|
+
def port: () -> String?
|
949
|
+
def port=: (String) -> void
|
950
|
+
def tags: () -> Array[String]?
|
951
|
+
def tags=: (Array[String]) -> void
|
952
|
+
def debug: () -> bool
|
953
|
+
def debug?: () -> bool
|
954
|
+
def debug=: (bool) -> void
|
955
|
+
end
|
956
|
+
|
957
|
+
class Config < Anyway::Config
|
958
|
+
include _Config
|
959
|
+
end
|
960
|
+
end
|
961
|
+
```
|
962
|
+
|
963
|
+
### Handling `on_load`
|
964
|
+
|
965
|
+
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:
|
966
|
+
|
967
|
+
```ruby
|
968
|
+
class MyConfig < Anyway::Config
|
969
|
+
on_load do
|
970
|
+
# @type self : MyConfig
|
971
|
+
raise_validation_error("host is invalid") if host.start_with?("localhost")
|
972
|
+
end
|
973
|
+
end
|
974
|
+
```
|
975
|
+
|
976
|
+
Yeah, a lot of annotations 😞 Welcome to the type-safe world!
|
977
|
+
|
793
978
|
## Contributing
|
794
979
|
|
795
980
|
Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/anyway_config](https://github.com/palkan/anyway_config).
|
@@ -4,30 +4,50 @@ module Anyway
|
|
4
4
|
module AutoCast
|
5
5
|
# Regexp to detect array values
|
6
6
|
# Array value is a values that contains at least one comma
|
7
|
-
# and doesn't start/end with quote
|
8
|
-
ARRAY_RXP = /\A[^'"].*\s*,\s*.*[^'"]\z/
|
7
|
+
# and doesn't start/end with quote or curly braces
|
8
|
+
ARRAY_RXP = /\A[^'"{].*\s*,\s*.*[^'"}]\z/
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
class << self
|
11
|
+
def call(val)
|
12
|
+
return val unless val.is_a?(::Hash) || val.is_a?(::String)
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
+
Utils.deep_merge!(coercion_mapping, 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
|
@@ -8,7 +8,11 @@ module Anyway
|
|
8
8
|
parsed_yml = super
|
9
9
|
return parsed_yml unless environmental?(parsed_yml)
|
10
10
|
|
11
|
-
|
11
|
+
env_config = parsed_yml[::Rails.env] || {}
|
12
|
+
return env_config if Anyway::Settings.default_environmental_key.blank?
|
13
|
+
|
14
|
+
default_config = parsed_yml[Anyway::Settings.default_environmental_key] || {}
|
15
|
+
Utils.deep_merge!(default_config, env_config)
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
@@ -18,7 +22,9 @@ module Anyway
|
|
18
22
|
# likely
|
19
23
|
return true if parsed_yml.key?(::Rails.env)
|
20
24
|
# less likely
|
21
|
-
::Rails.application.config.anyway_config.known_environments.any? { |_1| parsed_yml.key?(_1) }
|
25
|
+
return true if ::Rails.application.config.anyway_config.known_environments.any? { |_1| parsed_yml.key?(_1) }
|
26
|
+
# strange, but still possible
|
27
|
+
Anyway::Settings.default_environmental_key.present? && parsed_yml.key?(Anyway::Settings.default_environmental_key)
|
22
28
|
end
|
23
29
|
|
24
30
|
def relative_config_path(path)
|
@@ -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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|