etcher 1.6.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +210 -54
- data/etcher.gemspec +3 -3
- data/lib/etcher/builder.rb +19 -12
- data/lib/etcher/loaders/environment.rb +5 -5
- data/lib/etcher/loaders/hash.rb +22 -0
- data/lib/etcher/loaders/json.rb +19 -8
- data/lib/etcher/loaders/yaml.rb +44 -10
- data/lib/etcher/registry.rb +18 -13
- data/lib/etcher/resolver.rb +13 -15
- data/lib/etcher/transformers/basename.rb +29 -0
- data/lib/etcher/transformers/format.rb +42 -0
- data/lib/etcher/transformers/root.rb +29 -0
- data/lib/etcher/transformers/time.rb +3 -3
- data.tar.gz.sig +0 -0
- metadata +11 -8
- metadata.gz.sig +0 -0
- data/lib/etcher/transformers/string.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34e559c63ed6f3baa580995e227f09113e26cacc8920a919f5d10912b4d1cad0
|
4
|
+
data.tar.gz: e23d9de0fffff7d2ae6835a0c6e38dbab5cfdc555839051544a9f24432b73884
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5855391dea3b6fa69e206867a8f4b5b3c304e06902044cd70d01eea97dfcc4126bc505d2ea0e9c5424e1089caca8bf7d4ad3d340350a6a53a0d34e55cd4280eb
|
7
|
+
data.tar.gz: e4c23192f2f12a3b086a4dcfc796fa9be9b52c743583e39bcd241071e8e7d4dc487b2b1a47f37a2d31076efd62247d56a043d45c8e135100f2636b40052d2013
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
:pipeable_link: link:https://alchemists.io/projects/pipeable[Pipeable]
|
16
16
|
:runcom_link: link:https://alchemists.io/projects/runcom[Runcom]
|
17
17
|
:sod_link: link:https://alchemists.io/projects/sod[Sod]
|
18
|
+
:string_formats_link: link:https://docs.ruby-lang.org/en/3.3/format_specifications_rdoc.html[String Formats]
|
18
19
|
:struct_link: link:https://alchemists.io/articles/ruby_structs[Struct]
|
19
20
|
:versionaire_link: link:https://alchemists.io/projects/versionaire[Versionaire]
|
20
21
|
:xdg_link: link:https://alchemists.io/projects/xdg[XDG]
|
@@ -28,7 +29,7 @@ ____
|
|
28
29
|
[Use] strong acid or mordant to cut into the unprotected parts of a metal surface to create a design in intaglio (incised) in the metal.
|
29
30
|
____
|
30
31
|
|
31
|
-
By using Etcher, you have a reliable way to load default configurations (i.e. {environment_link}, {json_link}, {yaml_link}) which can be validated and etched into _frozen_ records (i.e. {hash_link}, {data_link}, {struct_link}) for consumption within your application which doesn't violate the {demeter_link}. This comes complete with transformations and validations all via a simple Object API
|
32
|
+
By using Etcher, you have a reliable way to load default configurations (i.e. {hash_link}, {environment_link}, {json_link}, {yaml_link}) which can be validated and etched into _frozen_ records (i.e. {hash_link}, {data_link}, {struct_link}) for consumption within your application which doesn't violate the {demeter_link}. This comes complete with loaders, transformations, and validations all via a simple Object API that pairs well with the {xdg_link}, {runcom_link}, and {sod_link} gems.
|
32
33
|
|
33
34
|
toc::[]
|
34
35
|
|
@@ -36,7 +37,7 @@ toc::[]
|
|
36
37
|
|
37
38
|
* Supports contracts which respond to `#call` to validate a {hash_link} before building the final record. Pairs well with the {dry_schema_link} and {dry_validation_link} gems.
|
38
39
|
* Supports models which respond to `.[]` for consuming a splatted {hash_link} to instantiate new records. Pairs well with primitives such as: {hash_link}, {data_link}, and {struct_link}.
|
39
|
-
* Supports loading of default configurations from the {environment_link}, a {json_link} configuration, a {yaml_link} configuration, or anything that can answer a hash.
|
40
|
+
* Supports loading of default configurations from a {hash_link}, the {environment_link}, a {json_link} configuration, a {yaml_link} configuration, or anything that can answer a hash.
|
40
41
|
* Supports multiple transformations which can process loaded configuration hashes and answer a transformed hash.
|
41
42
|
* Supports {hash_link} overrides as a final customization which is handy for Command Line Interfaces (CLIs), as aided by {sod_link}, or anything that might require user input at runtime.
|
42
43
|
|
@@ -133,16 +134,16 @@ While this is a more advanced use case, you'll usually only need to register a c
|
|
133
134
|
As hinted at above, the complete sequence of steps are performed in the order listed:
|
134
135
|
|
135
136
|
. *Load*: Each loader, if any, is called and merged with the previous loader to build initial attributes.
|
136
|
-
. *Override*: Any overrides are merged with the result of the last loader to produce updated attributes. ⚠️ _In Version 2.0.0, this step will happen after the Transform step._
|
137
137
|
. *Transform*: Each transformer, if any, is called to transform and manipulate the attributes.
|
138
|
+
. *Override*: Overrides, if any, are merged with the result of the last transformer so you can fine tune the data as desired.
|
138
139
|
. *Validate*: The contract is called to validate the attributes as previously loaded, overwritten, and transformed.
|
139
140
|
. *Model*: The model consumes the attributes of the validated contract and creates a new record for you to use as needed.
|
140
141
|
|
141
|
-
You can use the above steps as a reference when using this gem. Each step is explained in greater
|
142
|
+
Each step _mutates_ the attributes of the previous step in order to produce a record (success) or error (failure). You can use the above steps as a reference when using this gem. Each step is explained in greater below.
|
142
143
|
|
143
144
|
=== Registry
|
144
145
|
|
145
|
-
The registry
|
146
|
+
The registry provides a way to register any/all behavior for before creating a new Etcher instance. Here's what you get by default:
|
146
147
|
|
147
148
|
[source,ruby]
|
148
149
|
----
|
@@ -179,7 +180,7 @@ Contracts are a critical piece of this workflow as they provide a way to validat
|
|
179
180
|
|
180
181
|
* `#call`: Must be able to consume a {hash_link} and answer an object which can respond to `#to_monad`.
|
181
182
|
|
182
|
-
|
183
|
+
Both {dry_schema_link} and {dry_validation_link} respond to the `#to_monad` message. Ensure the {dry_monads_link} extensions are loaded too, as briefly shown earlier, so the result will respond to the `#to_monad` message. Here's how to enable monad support if using both gems:
|
183
184
|
|
184
185
|
[source,ruby]
|
185
186
|
----
|
@@ -209,7 +210,7 @@ etcher.call from: "Mork", to: "Mindy"
|
|
209
210
|
# Success({:from=>"Mork", :to=>"Mindy"})
|
210
211
|
----
|
211
212
|
|
212
|
-
Here you can see the power of using a contract to validate your data both as a failure and a success. Unfortunately, with the success, we only get a {hash_link} as a record
|
213
|
+
Here you can see the power of using a contract to validate your data both as a failure and a success. Unfortunately, with the success, we only get a {hash_link} as a record but it would be better to have a data structure which will be explained shortly.
|
213
214
|
|
214
215
|
=== Types
|
215
216
|
|
@@ -279,7 +280,7 @@ Notice we get an failure if all attributes are not provided but if we supply the
|
|
279
280
|
|
280
281
|
=== Loaders
|
281
282
|
|
282
|
-
Loaders are a great way to load a _default_ configuration for your application which can be in multiple formats. Loaders can either be defined when creating a new registry instance or added after the fact. Here are a
|
283
|
+
Loaders are a great way to load a _default_ configuration for your application which can be in multiple formats. Loaders can either be defined when creating a new registry instance or added after the fact. Here are a couple examples:
|
283
284
|
|
284
285
|
[source,ruby]
|
285
286
|
----
|
@@ -290,10 +291,26 @@ registry = Etcher::Registry[loaders: [MyLoader.new]]
|
|
290
291
|
registry = Etcher::Registry.new.add_loader MyLoader.new
|
291
292
|
----
|
292
293
|
|
293
|
-
|
294
|
+
You can also remove a previously added loader by index:
|
294
295
|
|
295
|
-
|
296
|
-
|
296
|
+
[source,ruby]
|
297
|
+
----
|
298
|
+
registry = Etcher::Registry.new
|
299
|
+
|
300
|
+
# Application
|
301
|
+
registry.add_loader MyLoader.new
|
302
|
+
|
303
|
+
# RSpec
|
304
|
+
registry.remove_loader 0
|
305
|
+
----
|
306
|
+
|
307
|
+
The ability to remove a loader is especially handy in a testing environment where you might need to temporarily remove a loader or don't need a specific loader for testing purposes.
|
308
|
+
|
309
|
+
There are a few guidelines to using loaders:
|
310
|
+
|
311
|
+
* All loaders must respond to `#call` with no arguments.
|
312
|
+
* All loaders must answer either a success with attributes (i.e. `Success attributes`) or a failure with details about the failure (i.e. `Failure step: :load, constant: MyLoader, payload: "My error message.`)
|
313
|
+
* All keys are symbolized after the loader is called which helps streamline merging and overriding values from the same keys across multiple configurations.
|
297
314
|
* All nested keys will be flattened after being loaded. This means a key structure of `{demo: {one: "test"}}` will be flattened to `demo_one: "test"` which adheres to the {demeter_link} when a new recored is _etched_ for you.
|
298
315
|
* The order in which you define your loaders matters. This means the first loader defined will be processed first, then the second, and so forth. Loaders defined last take precedence over previously defined loaders when overriding the same keys.
|
299
316
|
|
@@ -341,6 +358,25 @@ loader.call
|
|
341
358
|
|
342
359
|
This loader is great for pulling from environment variables as a fallback configuration for your application.
|
343
360
|
|
361
|
+
==== Hash
|
362
|
+
|
363
|
+
Use `:hash` or `Etcher::Loaders::Hash` to load in-memory attributes. By default, this loader will answer an empty hash if not supplied with any attributes. Here's a few examples:
|
364
|
+
|
365
|
+
[source,ruby]
|
366
|
+
----
|
367
|
+
# Default behavior.
|
368
|
+
loader = Etcher::Loaders::Hash.new
|
369
|
+
loader.call
|
370
|
+
# Success({})
|
371
|
+
|
372
|
+
# With custom attributes
|
373
|
+
loader = Etcher::Loaders::Hash.new one: 1, two: 2
|
374
|
+
loader.call
|
375
|
+
# Success({:one=>1, :two=>2})
|
376
|
+
----
|
377
|
+
|
378
|
+
This loader is great for adding custom attributes, overriding/adjusting attributes from a previous loader, or customizing attributes for testing purposes within a test suite.
|
379
|
+
|
344
380
|
==== JSON
|
345
381
|
|
346
382
|
Use `Etcher::Loaders::JSON` to load configuration information from a {json_link} file. Here's how to use this loader (using a file that doesn't exist):
|
@@ -362,9 +398,12 @@ loader = Etcher::Loaders::JSON.new "your/path/to/configuration.json",
|
|
362
398
|
loader.call # Success({})
|
363
399
|
----
|
364
400
|
|
365
|
-
|
401
|
+
If the file exists with _valid_ content, you'll get a `Hash` wrapped as a `Success`. In situations in which the file doesn't exist, you'll get a `Success` with an empty hash and debug information logged instead. Any failures will be provided with step, constant, and payload details. Example:
|
366
402
|
|
367
|
-
|
403
|
+
[source,ruby]
|
404
|
+
----
|
405
|
+
Failure step: :load, constant: Etcher::Loaders::JSON, payload: "Danger!"
|
406
|
+
----
|
368
407
|
|
369
408
|
==== YAML
|
370
409
|
|
@@ -387,9 +426,12 @@ loader = Etcher::Loaders::YAML.new "your/path/to/configuration.yml",
|
|
387
426
|
loader.call # Success({})
|
388
427
|
----
|
389
428
|
|
390
|
-
|
429
|
+
If the file exists with _valid_ content, you'll get a `Hash` wrapped as a `Success`. In situations in which the file doesn't exist, you'll get a `Success` with an empty hash and debug information logged instead. Any failures will be provided with step, constant, and payload details. Example:
|
391
430
|
|
392
|
-
|
431
|
+
[source,ruby]
|
432
|
+
----
|
433
|
+
Failure step: :load, constant: Etcher::Loaders::YAML, payload: "Danger!"
|
434
|
+
----
|
393
435
|
|
394
436
|
==== Custom
|
395
437
|
|
@@ -402,26 +444,31 @@ require "dry/monads"
|
|
402
444
|
class Demo
|
403
445
|
include Dry::Monads[:result]
|
404
446
|
|
405
|
-
def initialize
|
406
|
-
@
|
447
|
+
def initialize processor: Processor.new
|
448
|
+
@processor = processor
|
407
449
|
end
|
408
450
|
|
409
|
-
def call
|
451
|
+
def call
|
452
|
+
Success processor.call
|
453
|
+
rescue ProcessorError => error
|
454
|
+
Failure step: :load, constant: self.class, payload: error.message
|
455
|
+
end
|
410
456
|
|
411
457
|
private
|
412
458
|
|
413
|
-
attr_reader :
|
459
|
+
attr_reader :processor
|
414
460
|
end
|
415
461
|
|
416
|
-
|
417
|
-
|
462
|
+
registry = Etcher::Registry[loaders: [Demo.new]]
|
463
|
+
|
464
|
+
Etcher.new(registry).call
|
418
465
|
----
|
419
466
|
|
420
|
-
While the above
|
467
|
+
While the above assumes you have some kind of `Processor` for loading attributes, you can see there is little effort required to implement and customize as desired.
|
421
468
|
|
422
469
|
=== Transformers
|
423
470
|
|
424
|
-
Transformers are great for
|
471
|
+
Transformers are great for _mutating_ specific keys and values. They give you fine grained customization over your configuration. Transformers can either be defined when creating a new registry instance or added after the fact. Here are a couple examples:
|
425
472
|
|
426
473
|
[source,ruby]
|
427
474
|
----
|
@@ -432,12 +479,28 @@ registry = Etcher::Registry[transformers: [MyTransformer]]
|
|
432
479
|
registry = Etcher::Registry.new.add_transformer MyTransformer
|
433
480
|
----
|
434
481
|
|
482
|
+
You can also remove a previously added transformer by index:
|
483
|
+
|
484
|
+
[source,ruby]
|
485
|
+
----
|
486
|
+
registry = Etcher::Registry.new
|
487
|
+
|
488
|
+
# Application
|
489
|
+
registry.add_transformer MyTransformer
|
490
|
+
|
491
|
+
# RSpec
|
492
|
+
registry.remove_transformer 0
|
493
|
+
----
|
494
|
+
|
495
|
+
The ability to remove a transformer is especially handy in a testing environment where you might need to temporarily remove a transformer or don't need a specific transformer for testing purposes.
|
496
|
+
|
435
497
|
The guidelines for using transformers are:
|
436
498
|
|
437
499
|
* They can be initialized with whatever requirements you need.
|
438
500
|
* They must respond to `#call` which takes a required `attributes` positional argument and answers a modified version of these attributes (`Hash`) wrapped as a monad.
|
439
|
-
*
|
440
|
-
* When using a
|
501
|
+
* They must answer either a success with attributes (i.e. `Success attributes`) or a failure with details about the failure (i.e. `Failure step: :transform, constant: MyTransformer, payload: "My error message.`)
|
502
|
+
* When using a proc/lambda, the first, _required_, parameter should be the `attributes` parameter followed by a second positional `key` parameter.
|
503
|
+
* When using a class, the `key` should be your first positional parameter. Additional parameters can be supplied after if desired.
|
441
504
|
* The `attributes` passed to your transformer will have symbolized keys so you don't need to transform them further.
|
442
505
|
|
443
506
|
For example, the following capitalizes all values (which may or may not be good depending on your data structure):
|
@@ -492,8 +555,8 @@ For convenience, all transformers -- only packaged with this gem -- can be regis
|
|
492
555
|
----
|
493
556
|
registry = Etcher::Registry.new
|
494
557
|
|
495
|
-
#
|
496
|
-
registry.add_transformer :
|
558
|
+
# Format
|
559
|
+
registry.add_transformer :format, :project_uri
|
497
560
|
|
498
561
|
# Time
|
499
562
|
registry.add_transformer :time
|
@@ -501,9 +564,47 @@ registry.add_transformer :time
|
|
501
564
|
|
502
565
|
Any positional or keyword arguments will be passed to the transformers's constructor. _This only works when using `Registry#add_transformer`, though._ The following sections provide more details on each.
|
503
566
|
|
504
|
-
====
|
567
|
+
==== Basename
|
568
|
+
|
569
|
+
Use `Etcher::Transformers::Basename` to dynamically obtain the name of the current directory as a value for a key. This is handy for scripting or CLI purposes when needing to know the name of the current project you are working in. Example:
|
570
|
+
|
571
|
+
[source,ruby]
|
572
|
+
----
|
573
|
+
transformer = Etcher::Transformers::Basename.new :demo
|
574
|
+
transformer.call({})
|
575
|
+
# Success({:demo=>"scratch"})
|
576
|
+
|
577
|
+
transformer = Etcher::Transformers::Basename.new :demo, fallback: "undefined"
|
578
|
+
transformer.call({})
|
579
|
+
# Success({:demo=>"undefined"})
|
580
|
+
|
581
|
+
transformer = Etcher::Transformers::Basename.new :demo
|
582
|
+
transformer.call({demo: "defined"})
|
583
|
+
# Success({:demo=>"defined"})
|
584
|
+
----
|
585
|
+
|
586
|
+
==== Root
|
587
|
+
|
588
|
+
Use `Etcher::Transformers::Root` to dynamically obtain the current path as a value for a key. This is handy for obtaining the absolute path to a new or existing directory. Example:
|
589
|
+
|
590
|
+
[source,ruby]
|
591
|
+
----
|
592
|
+
transformer = Etcher::Transformers::Root.new :demo
|
593
|
+
transformer.call({})
|
594
|
+
# Success({:demo=>#<Pathname:/Users/demo/Engineering/OSS/scratch>})
|
595
|
+
|
596
|
+
transformer = Etcher::Transformers::Root.new :demo, fallback: "undefined"
|
597
|
+
transformer.call({})
|
598
|
+
# Success({:demo=>#<Pathname:/Users/demo/Engineering/undefined>})
|
599
|
+
|
600
|
+
transformer = Etcher::Transformers::Root.new :demo
|
601
|
+
transformer.call({demo: "defined"})
|
602
|
+
# Success({:demo=>#<Pathname:/Users/demo/Engineering/defined>})
|
603
|
+
----
|
604
|
+
|
605
|
+
==== Format
|
505
606
|
|
506
|
-
Use `Etcher::Transformers::
|
607
|
+
Use `Etcher::Transformers::Format` to transform any key's value by using the configuration's existing attributes to format the value of a specific key using the {string_formats_link} Specification. To start, we'll use the same attributes for all examples:
|
507
608
|
|
508
609
|
[source,ruby]
|
509
610
|
----
|
@@ -512,10 +613,13 @@ attributes = {
|
|
512
613
|
project_name: "test",
|
513
614
|
project_uri: "%<organization_uri>s/projects/%<project_name>s"
|
514
615
|
}
|
616
|
+
----
|
515
617
|
|
516
|
-
|
618
|
+
Using the above `attributes`, you'll get a `Success` when all required keys exist:
|
517
619
|
|
518
|
-
|
620
|
+
[source,ruby]
|
621
|
+
----
|
622
|
+
Etcher::Transformers::Format.new(:project_uri).call attributes
|
519
623
|
# Success(
|
520
624
|
{
|
521
625
|
organization_uri: "https://acme.io",
|
@@ -523,56 +627,109 @@ transformer.call attributes
|
|
523
627
|
project_uri: "https://acme.io/projects/test"
|
524
628
|
}
|
525
629
|
)
|
630
|
+
----
|
526
631
|
|
632
|
+
When some required keys are missing, you'll get a `Failure`:
|
633
|
+
|
634
|
+
[source,ruby]
|
635
|
+
----
|
527
636
|
attributes.delete :project_name
|
528
|
-
|
529
|
-
|
637
|
+
Etcher::Transformers::Format.new(:project_uri).call attributes
|
638
|
+
|
639
|
+
# Failure(
|
640
|
+
# {
|
641
|
+
# step: :transform,
|
642
|
+
# constant: Etcher::Transformers::Format,
|
643
|
+
# payload: "Unable to transform :project_uri, missing specifier: \"<project_name>\"."
|
644
|
+
# }
|
645
|
+
# )
|
530
646
|
----
|
531
647
|
|
532
|
-
|
648
|
+
You can partially transform a value using _retainers_ and/or _mappings_ for situations where you need to format a value while preserving and/or remapping string specifiers for delayed formatting. Here's an example using a _retainer_ which preserves the `:project_name`.
|
649
|
+
|
650
|
+
[source,ruby]
|
651
|
+
----
|
652
|
+
Etcher::Transformers::Format.new(:project_uri, :project_name).call attributes
|
653
|
+
|
654
|
+
# Success(
|
655
|
+
# {
|
656
|
+
# organization_uri: "https://acme.io",
|
657
|
+
# project_name: "test",
|
658
|
+
# project_uri: "https://acme.io/projects/%<project_name>s"
|
659
|
+
# }
|
660
|
+
# )
|
661
|
+
----
|
662
|
+
|
663
|
+
Notice the `organization_uri` was formatted in the `project_uri` while the `project_name` was preserved. This allows you to format the `project_name` when you can supply the value later. Similarly, you can remap a string specifier. Example:
|
664
|
+
|
665
|
+
[source,ruby]
|
666
|
+
----
|
667
|
+
Etcher::Transformers::Format.new(:project_uri, project_name: "%<id>s").call attributes
|
533
668
|
|
534
|
-
|
669
|
+
# Success(
|
670
|
+
# {
|
671
|
+
# organization_uri: "https://acme.io",
|
672
|
+
# project_name: "test",
|
673
|
+
# project_uri: "https://acme.io/projects/%<id>s"
|
674
|
+
# }
|
675
|
+
# )
|
676
|
+
----
|
677
|
+
|
678
|
+
Notice the `organization_uri` was formatted in the `project_uri` (same as before) while the `project_name` was remapped as `%<id>s`. As shown mentioned earlier, this allows you to _delay_ supplying the `id` when you might not have a value for it yet.
|
535
679
|
|
536
|
-
|
680
|
+
You can also, safely, transform a value which _doesn't_ have string specifiers:
|
537
681
|
|
538
682
|
[source,ruby]
|
539
683
|
----
|
540
|
-
|
541
|
-
|
542
|
-
|
684
|
+
Etcher::Transformers::Format.new(:version).call(version: "1.2.3")
|
685
|
+
# Success({:version=>"1.2.3"})
|
686
|
+
----
|
687
|
+
|
688
|
+
Normally, you'd get a "too many arguments for format string" warning but this transformer detects and immediately skips formatting when no string specifiers are detected. This is handy for situations where your configuration supports values which may or may not need formatting.
|
543
689
|
|
690
|
+
==== Time
|
691
|
+
|
692
|
+
Use `Etcher::Transformers::Time` to transform the any key in your configuration when you want to know the current time at which the configuration was loaded. Handy for situations where you need to calculate relative time or format time based on when your configuration was loaded.
|
693
|
+
|
694
|
+
You must supply a key and `Time.now.utc` is the default fallback. You can customize as desired. Example:
|
695
|
+
|
696
|
+
[source,ruby]
|
697
|
+
----
|
544
698
|
transformer = Etcher::Transformers::Time.new :now
|
545
699
|
transformer.call({})
|
546
|
-
# Success({:now=>2024-
|
700
|
+
# Success({:now=>2024-06-15 22:43:29.178488 UTC})
|
547
701
|
|
548
702
|
transformer = Etcher::Transformers::Time.new :now, fallback: Time.utc(2000, 1, 1)
|
549
703
|
transformer.call({})
|
550
704
|
# Success({:now=>2000-01-01 00:00:00 UTC})
|
551
705
|
|
552
|
-
transformer = Etcher::Transformers::Time.new
|
553
|
-
transformer.call({
|
554
|
-
# Success({:
|
706
|
+
transformer = Etcher::Transformers::Time.new :now
|
707
|
+
transformer.call({now: Time.utc(2000, 1, 1)})
|
708
|
+
# Success({:now=>2000-01-01 00:00:00 UTC})
|
555
709
|
----
|
556
710
|
|
557
711
|
=== Overrides
|
558
712
|
|
559
|
-
Overrides are what you pass to the Etcher instance when called. Example:
|
713
|
+
Overrides are what you pass to the Etcher instance when called. They allow you to override any values that were loaded and/or transformed. Example:
|
560
714
|
|
561
715
|
[source,ruby]
|
562
716
|
----
|
563
717
|
etcher = Etcher.new
|
718
|
+
|
719
|
+
# With symbol keys.
|
564
720
|
etcher.call name: "test", label: "Test"
|
721
|
+
# Success({:name=>"test", :label=>"Test"})
|
565
722
|
|
723
|
+
# With string keys.
|
724
|
+
etcher.call "name" => "test", "label" => "Test"
|
566
725
|
# Success({:name=>"test", :label=>"Test"})
|
567
726
|
----
|
568
727
|
|
569
|
-
Overrides are applied _after_ any
|
570
|
-
|
571
|
-
⚠️ In Version 2.0.0, this step will be changed to occur _after_ the Transform step for maximum flexibility.
|
728
|
+
Overrides are applied _after_ any transforms and _before_ validations. They are a nice way to deal with user input during runtime or provide additional attributes not supplied by the loading and/or transforming of your default configuration while ensuring they are validated properly. Any string keys will be transformed to symbol keys to ensure consistency and reduce issues when merged.
|
572
729
|
|
573
730
|
=== Resolver
|
574
731
|
|
575
|
-
In situations where you'd like Etcher to handle the complete load, transform, validate, and
|
732
|
+
In situations where you'd like Etcher to handle the complete load, transform, override, validate, and model steps for you, then you can use the resolver. This is provided for use cases where you'd like Etcher to handle everything for you and abort if otherwise. Example:
|
576
733
|
|
577
734
|
[source,ruby]
|
578
735
|
----
|
@@ -599,23 +756,22 @@ registry = Etcher::Registry.new(contract:, model:)
|
|
599
756
|
|
600
757
|
Etcher.call registry
|
601
758
|
|
602
|
-
#
|
759
|
+
# 🛑 Etcher validate failure (Etcher::Builder). Unable to load configuration:
|
603
760
|
# - to is missing
|
604
761
|
# - from is missing
|
605
762
|
|
606
763
|
Etcher.call registry, to: "Mindy"
|
607
764
|
|
608
|
-
#
|
765
|
+
# 🛑 Etcher validate failure (Etcher::Builder). Unable to load configuration:
|
609
766
|
# - from is missing
|
610
767
|
|
611
|
-
|
612
768
|
registry = Etcher::Registry.new(model: Data.define(:name, :label))
|
613
769
|
Etcher.call registry, to: "Mindy"
|
614
770
|
|
615
|
-
#
|
771
|
+
# 🛑 Etcher model failure (Etcher::Builder). Missing keywords: :name, :label.
|
616
772
|
----
|
617
773
|
|
618
|
-
💡 When using a custom registry, make sure it's the first argument.
|
774
|
+
💡 When using a custom registry, make sure it's the first argument. Additional arguments can be supplied afterwards and they can be any number of key/value overrides which is similar to how `Etcher.new` works.
|
619
775
|
|
620
776
|
== Development
|
621
777
|
|
@@ -639,7 +795,7 @@ bin/console
|
|
639
795
|
|
640
796
|
The following illustrates the full sequences of events when _etching_ new records:
|
641
797
|
|
642
|
-
image::https://alchemists.io/images/projects/etcher/
|
798
|
+
image::https://alchemists.io/images/projects/etcher/architecture.png[Architecture Diagram,1250,1071,role=focal_point]
|
643
799
|
|
644
800
|
== Tests
|
645
801
|
|
data/etcher.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "etcher"
|
5
|
-
spec.version = "1.
|
5
|
+
spec.version = "2.1.0"
|
6
6
|
spec.authors = ["Brooke Kuhlmann"]
|
7
7
|
spec.email = ["brooke@alchemists.io"]
|
8
8
|
spec.homepage = "https://alchemists.io/projects/etcher"
|
@@ -23,11 +23,11 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.cert_chain = [Gem.default_cert_path]
|
24
24
|
|
25
25
|
spec.required_ruby_version = "~> 3.3"
|
26
|
-
spec.add_dependency "cogger", "~> 0.
|
26
|
+
spec.add_dependency "cogger", "~> 0.21"
|
27
27
|
spec.add_dependency "core", "~> 1.0"
|
28
28
|
spec.add_dependency "dry-monads", "~> 1.6"
|
29
29
|
spec.add_dependency "dry-types", "~> 1.7"
|
30
|
-
spec.add_dependency "refinements", "~> 12.
|
30
|
+
spec.add_dependency "refinements", "~> 12.5"
|
31
31
|
spec.add_dependency "versionaire", "~> 13.0"
|
32
32
|
spec.add_dependency "zeitwerk", "~> 2.6"
|
33
33
|
|
data/lib/etcher/builder.rb
CHANGED
@@ -16,27 +16,25 @@ module Etcher
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(**overrides)
|
19
|
-
load
|
20
|
-
|
21
|
-
|
19
|
+
load.bind { |attributes| transform attributes }
|
20
|
+
.fmap { |attributes| attributes.merge! overrides.symbolize_keys! }
|
21
|
+
.bind { |attributes| validate attributes }
|
22
|
+
.bind { |attributes| model attributes }
|
22
23
|
end
|
23
24
|
|
24
25
|
private
|
25
26
|
|
26
27
|
attr_reader :registry
|
27
28
|
|
28
|
-
def load
|
29
|
+
def load
|
29
30
|
registry.loaders
|
30
31
|
.map { |loader| loader.call.fmap { |pairs| pairs.flatten_keys.symbolize_keys! } }
|
31
|
-
.
|
32
|
-
.with_object({}) { |attributes, all| attributes.bind { |body| all.merge! body } }
|
33
|
-
.merge!(overrides.flatten_keys)
|
34
|
-
.then { |attributes| Success attributes }
|
32
|
+
.reduce(Success({})) { |all, result| merge all, result }
|
35
33
|
end
|
36
34
|
|
37
35
|
def transform attributes
|
38
|
-
registry.transformers.reduce attributes do |all, transformer|
|
39
|
-
all
|
36
|
+
registry.transformers.reduce Success(attributes) do |all, transformer|
|
37
|
+
merge all, transformer.call(attributes)
|
40
38
|
end
|
41
39
|
end
|
42
40
|
|
@@ -44,13 +42,22 @@ module Etcher
|
|
44
42
|
registry.contract
|
45
43
|
.call(attributes)
|
46
44
|
.to_monad
|
47
|
-
.or
|
45
|
+
.or do |result|
|
46
|
+
Failure step: __method__, constant: self.class, payload: result.errors.to_h
|
47
|
+
end
|
48
48
|
end
|
49
49
|
|
50
50
|
def model attributes
|
51
51
|
Success registry.model[**attributes.to_h].freeze
|
52
52
|
rescue ArgumentError => error
|
53
|
-
Failure step: __method__, payload: "#{error.message.capitalize}."
|
53
|
+
Failure step: __method__, constant: self.class, payload: "#{error.message.capitalize}."
|
54
|
+
end
|
55
|
+
|
56
|
+
def merge(*items)
|
57
|
+
case items
|
58
|
+
in Success(all), Success(subset) then Success(all.merge!(subset))
|
59
|
+
else items.last
|
60
|
+
end
|
54
61
|
end
|
55
62
|
end
|
56
63
|
end
|
@@ -9,16 +9,16 @@ module Etcher
|
|
9
9
|
class Environment
|
10
10
|
include Dry::Monads[:result]
|
11
11
|
|
12
|
-
def initialize
|
13
|
-
@
|
14
|
-
@
|
12
|
+
def initialize attributes = ENV, only: Core::EMPTY_ARRAY
|
13
|
+
@attributes = attributes
|
14
|
+
@only = Array only
|
15
15
|
end
|
16
16
|
|
17
|
-
def call = Success
|
17
|
+
def call = Success attributes.slice(*only).transform_keys(&:downcase)
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
attr_reader :
|
21
|
+
attr_reader :attributes, :only
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
|
5
|
+
module Etcher
|
6
|
+
module Loaders
|
7
|
+
# Loads (wraps) raw attributes.
|
8
|
+
class Hash
|
9
|
+
include Dry::Monads[:result]
|
10
|
+
|
11
|
+
def initialize(**attributes)
|
12
|
+
@attributes = attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def call = Success attributes
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :attributes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/etcher/loaders/json.rb
CHANGED
@@ -18,22 +18,33 @@ module Etcher
|
|
18
18
|
|
19
19
|
def call
|
20
20
|
Success ::JSON.load_file(path)
|
21
|
-
rescue
|
22
|
-
|
23
|
-
rescue ::JSON::ParserError => error
|
24
|
-
debug_and_fallback "#{error.message}. Path: #{path_info}. Using fallback."
|
21
|
+
rescue Errno::ENOENT, TypeError then debug_invalid_path
|
22
|
+
rescue ::JSON::ParserError => error then content_failure error
|
25
23
|
end
|
26
24
|
|
27
25
|
private
|
28
26
|
|
29
27
|
attr_reader :path, :fallback, :logger
|
30
28
|
|
31
|
-
def
|
32
|
-
|
33
|
-
def debug_and_fallback message
|
34
|
-
logger.debug { message }
|
29
|
+
def debug_invalid_path
|
30
|
+
logger.debug { "Invalid path: #{path_info}. Using fallback." }
|
35
31
|
Success fallback
|
36
32
|
end
|
33
|
+
|
34
|
+
def content_failure error
|
35
|
+
constant = self.class
|
36
|
+
token = error.message[/(?<token>'.+?')/, :token].to_s.tr "'", ""
|
37
|
+
|
38
|
+
if token.empty?
|
39
|
+
Failure step: :load, constant:, payload: "File is empty: #{path_info}."
|
40
|
+
else
|
41
|
+
Failure step: :load,
|
42
|
+
constant:,
|
43
|
+
payload: "Invalid content: #{token.inspect}. Path: #{path_info}."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def path_info = path.to_s.inspect
|
37
48
|
end
|
38
49
|
end
|
39
50
|
end
|
data/lib/etcher/loaders/yaml.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "core"
|
4
4
|
require "dry/monads"
|
5
|
+
require "refinements/string"
|
5
6
|
require "yaml"
|
6
7
|
|
7
8
|
module Etcher
|
@@ -10,6 +11,8 @@ module Etcher
|
|
10
11
|
class YAML
|
11
12
|
include Dry::Monads[:result]
|
12
13
|
|
14
|
+
using Refinements::String
|
15
|
+
|
13
16
|
def initialize path, fallback: Core::EMPTY_HASH, logger: LOGGER
|
14
17
|
@path = path
|
15
18
|
@fallback = fallback
|
@@ -18,10 +21,10 @@ module Etcher
|
|
18
21
|
|
19
22
|
def call
|
20
23
|
load
|
21
|
-
rescue
|
22
|
-
|
23
|
-
rescue Psych::
|
24
|
-
|
24
|
+
rescue Errno::ENOENT, TypeError then debug_invalid_path
|
25
|
+
rescue Psych::AliasesNotEnabled then alias_failure
|
26
|
+
rescue Psych::DisallowedClass => error then disallowed_failure error
|
27
|
+
rescue Psych::SyntaxError => error then syntax_failure error
|
25
28
|
end
|
26
29
|
|
27
30
|
private
|
@@ -31,17 +34,48 @@ module Etcher
|
|
31
34
|
def load
|
32
35
|
content = ::YAML.safe_load_file path
|
33
36
|
|
34
|
-
|
37
|
+
case content
|
38
|
+
in ::Hash then Success content
|
39
|
+
in nil then empty_failure
|
40
|
+
else invalid_failure content
|
41
|
+
end
|
42
|
+
end
|
35
43
|
|
36
|
-
|
44
|
+
def debug_invalid_path
|
45
|
+
logger.debug { "Invalid path: #{path_info}. Using fallback." }
|
46
|
+
Success fallback
|
37
47
|
end
|
38
48
|
|
39
|
-
def
|
49
|
+
def empty_failure
|
50
|
+
Failure step: :load, constant: self.class, payload: "File is empty: #{path_info}."
|
51
|
+
end
|
40
52
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
53
|
+
def invalid_failure content
|
54
|
+
Failure step: :load,
|
55
|
+
constant: self.class,
|
56
|
+
payload: "Invalid content: #{content.inspect}. Path: #{path_info}."
|
57
|
+
end
|
58
|
+
|
59
|
+
def alias_failure
|
60
|
+
Failure step: :load,
|
61
|
+
constant: self.class,
|
62
|
+
payload: "Aliases are disabled, please remove. Path: #{path_info}."
|
63
|
+
end
|
64
|
+
|
65
|
+
def disallowed_failure error
|
66
|
+
Failure step: :load,
|
67
|
+
constant: self.class,
|
68
|
+
payload: "Invalid type, #{error.message.down}. Path: #{path_info}."
|
44
69
|
end
|
70
|
+
|
71
|
+
def syntax_failure error
|
72
|
+
Failure step: :load,
|
73
|
+
constant: self.class,
|
74
|
+
payload: "Invalid syntax, #{error.message[/found.+/]}. " \
|
75
|
+
"Path: #{path_info}."
|
76
|
+
end
|
77
|
+
|
78
|
+
def path_info = path.to_s.inspect
|
45
79
|
end
|
46
80
|
end
|
47
81
|
end
|
data/lib/etcher/registry.rb
CHANGED
@@ -15,25 +15,30 @@ module Etcher
|
|
15
15
|
super
|
16
16
|
end
|
17
17
|
|
18
|
-
def add_loader(loader,
|
19
|
-
|
20
|
-
|
18
|
+
def add_loader(loader, ...) = add(loader, :Loaders, ...)
|
19
|
+
|
20
|
+
def remove_loader(index) = remove index, loaders
|
21
|
+
|
22
|
+
def add_transformer(transformer, ...) = add(transformer, :Transformers, ...)
|
23
|
+
|
24
|
+
def remove_transformer(index) = remove index, transformers
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def add(item, namespace, ...)
|
29
|
+
collection = __send__ namespace.downcase
|
30
|
+
|
31
|
+
if item.is_a? Symbol
|
32
|
+
self.class.find(namespace, item).then { |kind| collection.append kind.new(...) }
|
21
33
|
else
|
22
|
-
|
34
|
+
collection.append item
|
23
35
|
end
|
24
36
|
|
25
37
|
self
|
26
38
|
end
|
27
39
|
|
28
|
-
def
|
29
|
-
|
30
|
-
self.class.find(:Transformers, transformer).then do |constant|
|
31
|
-
transformers.append constant.new(*, **)
|
32
|
-
end
|
33
|
-
else
|
34
|
-
transformers.append transformer
|
35
|
-
end
|
36
|
-
|
40
|
+
def remove index, collection
|
41
|
+
collection.delete_at index
|
37
42
|
self
|
38
43
|
end
|
39
44
|
end
|
data/lib/etcher/resolver.rb
CHANGED
@@ -10,35 +10,33 @@ module Etcher
|
|
10
10
|
|
11
11
|
using Refinements::Array
|
12
12
|
|
13
|
-
def initialize registry = Registry.new,
|
13
|
+
def initialize registry = Registry.new, logger: LOGGER
|
14
14
|
@builder = Builder.new registry
|
15
|
-
@kernel = kernel
|
16
15
|
@logger = logger
|
17
16
|
end
|
18
17
|
|
19
18
|
def call(**overrides)
|
20
19
|
case builder.call(**overrides)
|
21
20
|
in Success(attributes) then attributes
|
22
|
-
in Failure(step:, payload: String => payload)
|
23
|
-
logger.
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
in Failure(step:, constant:, payload: String => payload)
|
22
|
+
logger.abort "#{step.capitalize} failure (#{constant}). #{payload}"
|
23
|
+
in Failure(step:, constant:, payload: Hash => payload)
|
24
|
+
log_and_abort step, constant, payload
|
25
|
+
in Failure(String => message) then logger.abort message
|
26
|
+
else logger.abort "Unable to parse failure."
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
attr_reader :builder, :
|
32
|
+
attr_reader :builder, :logger
|
33
33
|
|
34
|
-
def log_and_abort errors
|
35
|
-
|
36
|
-
|
37
|
-
.join
|
38
|
-
"Unable to load configuration due to the following issues:\n#{details}"
|
39
|
-
end
|
34
|
+
def log_and_abort step, constant, errors
|
35
|
+
details = errors.map { |key, message| " - #{key} #{message.to_sentence}\n" }
|
36
|
+
.join
|
40
37
|
|
41
|
-
|
38
|
+
logger.abort "#{step.capitalize} failure (#{constant}). " \
|
39
|
+
"Unable to load configuration:\n#{details}"
|
42
40
|
end
|
43
41
|
end
|
44
42
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
require "refinements/hash"
|
5
|
+
|
6
|
+
module Etcher
|
7
|
+
module Transformers
|
8
|
+
# Conditionally updates value based on path.
|
9
|
+
class Basename
|
10
|
+
include Dry::Monads[:result]
|
11
|
+
|
12
|
+
using Refinements::Hash
|
13
|
+
|
14
|
+
def initialize key, fallback: Pathname.pwd.basename.to_s
|
15
|
+
@key = key
|
16
|
+
@fallback = fallback
|
17
|
+
end
|
18
|
+
|
19
|
+
def call attributes
|
20
|
+
attributes.fetch_value(key) { attributes.merge! key => fallback }
|
21
|
+
Success attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :key, :fallback
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
|
5
|
+
module Etcher
|
6
|
+
module Transformers
|
7
|
+
# Formats given key using existing and/or placeholder attributes.
|
8
|
+
class Format
|
9
|
+
include Dry::Monads[:result]
|
10
|
+
|
11
|
+
def initialize key, *retainers, **mappings
|
12
|
+
@key = key
|
13
|
+
@retainers = retainers
|
14
|
+
@mappings = mappings
|
15
|
+
@pattern = /%<.+>s/o
|
16
|
+
end
|
17
|
+
|
18
|
+
def call attributes
|
19
|
+
value = attributes[key]
|
20
|
+
|
21
|
+
return Success attributes unless value && value.match?(pattern)
|
22
|
+
|
23
|
+
Success attributes.merge!(key => format(value, **attributes, **pass_throughs))
|
24
|
+
rescue KeyError => error
|
25
|
+
Failure step: :transform,
|
26
|
+
constant: self.class,
|
27
|
+
payload: "Unable to transform #{key.inspect}, missing specifier: " \
|
28
|
+
"\"#{error.message[/<.+>/]}\"."
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :key, :retainers, :mappings, :pattern
|
34
|
+
|
35
|
+
def pass_throughs
|
36
|
+
retainers.each
|
37
|
+
.with_object({}) { |key, expansions| expansions[key] = "%<#{key}>s" }
|
38
|
+
.merge! mappings
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
require "refinements/hash"
|
5
|
+
|
6
|
+
module Etcher
|
7
|
+
module Transformers
|
8
|
+
# Conditionally updates value based on path.
|
9
|
+
class Root
|
10
|
+
include Dry::Monads[:result]
|
11
|
+
|
12
|
+
using Refinements::Hash
|
13
|
+
|
14
|
+
def initialize key, fallback: Pathname.pwd
|
15
|
+
@key = key
|
16
|
+
@fallback = fallback
|
17
|
+
end
|
18
|
+
|
19
|
+
def call attributes
|
20
|
+
value = attributes.fetch_value(key) { fallback }
|
21
|
+
Success attributes.merge!(key => Pathname(value).expand_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :key, :fallback
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -8,14 +8,14 @@ module Etcher
|
|
8
8
|
class Time
|
9
9
|
include Dry::Monads[:result]
|
10
10
|
|
11
|
-
def initialize key
|
11
|
+
def initialize key, fallback: ::Time.now.utc
|
12
12
|
@key = key
|
13
13
|
@fallback = fallback
|
14
14
|
end
|
15
15
|
|
16
16
|
def call attributes
|
17
|
-
attributes.fetch(key) { fallback }
|
18
|
-
|
17
|
+
attributes.fetch(key) { attributes.merge! key => fallback }
|
18
|
+
Success attributes
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: etcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brooke Kuhlmann
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
|
36
36
|
gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2024-
|
38
|
+
date: 2024-07-10 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: cogger
|
@@ -43,14 +43,14 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0.
|
46
|
+
version: '0.21'
|
47
47
|
type: :runtime
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '0.
|
53
|
+
version: '0.21'
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
55
|
name: core
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,14 +99,14 @@ dependencies:
|
|
99
99
|
requirements:
|
100
100
|
- - "~>"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '12.
|
102
|
+
version: '12.5'
|
103
103
|
type: :runtime
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '12.
|
109
|
+
version: '12.5'
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: versionaire
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -152,11 +152,14 @@ files:
|
|
152
152
|
- lib/etcher/contract.rb
|
153
153
|
- lib/etcher/finder.rb
|
154
154
|
- lib/etcher/loaders/environment.rb
|
155
|
+
- lib/etcher/loaders/hash.rb
|
155
156
|
- lib/etcher/loaders/json.rb
|
156
157
|
- lib/etcher/loaders/yaml.rb
|
157
158
|
- lib/etcher/registry.rb
|
158
159
|
- lib/etcher/resolver.rb
|
159
|
-
- lib/etcher/transformers/
|
160
|
+
- lib/etcher/transformers/basename.rb
|
161
|
+
- lib/etcher/transformers/format.rb
|
162
|
+
- lib/etcher/transformers/root.rb
|
160
163
|
- lib/etcher/transformers/time.rb
|
161
164
|
- lib/etcher/types.rb
|
162
165
|
homepage: https://alchemists.io/projects/etcher
|
@@ -185,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
188
|
- !ruby/object:Gem::Version
|
186
189
|
version: '0'
|
187
190
|
requirements: []
|
188
|
-
rubygems_version: 3.5.
|
191
|
+
rubygems_version: 3.5.15
|
189
192
|
signing_key:
|
190
193
|
specification_version: 4
|
191
194
|
summary: A monadic configuration loader, transformer, and validator.
|
metadata.gz.sig
CHANGED
Binary file
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dry/monads"
|
4
|
-
|
5
|
-
module Etcher
|
6
|
-
module Transformers
|
7
|
-
# Formats given key using existing and/or ancillary attributes.
|
8
|
-
class String
|
9
|
-
include Dry::Monads[:result]
|
10
|
-
|
11
|
-
def initialize key, **ancillary
|
12
|
-
@key = key
|
13
|
-
@ancillary = ancillary
|
14
|
-
end
|
15
|
-
|
16
|
-
def call attributes
|
17
|
-
value = attributes[key]
|
18
|
-
|
19
|
-
return Success attributes unless value
|
20
|
-
|
21
|
-
Success attributes.merge(key => format(value, **attributes, **ancillary))
|
22
|
-
rescue KeyError => error
|
23
|
-
Failure "Unable to transform #{key.inspect}, missing specifier: " \
|
24
|
-
"\"#{error.message[/<.+>/]}\"."
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
attr_reader :key, :ancillary
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|