etcher 1.5.0 → 2.0.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 +191 -48
- data/etcher.gemspec +3 -3
- data/lib/etcher/builder.rb +19 -12
- data/lib/etcher/finder.rb +21 -0
- 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 +29 -4
- data/lib/etcher/resolver.rb +13 -15
- data/lib/etcher/transformers/basename.rb +29 -0
- data/lib/etcher/transformers/format.rb +35 -0
- data/lib/etcher/transformers/root.rb +29 -0
- data/lib/etcher/transformers/time.rb +3 -3
- data.tar.gz.sig +0 -0
- metadata +12 -7
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18c97ac57c0742fedf8872da7aa401ceae29552ccc9e78ce0d8df72fc8fff08c
|
4
|
+
data.tar.gz: feb2442ddc16419bc49dc97ba1e449e226713d57a2c1fd6714e8592479faf636
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd2eb81d5e6a950c8d2cc7205fa09d41608bf2a835e25aa13dc5bbbb9bf4f6a60eea36716d5a132047aba29290a3706c8b3b6b54c175df9f95d0e25943719d25
|
7
|
+
data.tar.gz: af20a0db0eaefd28a6a69c1bf0193084ec841a1042cc6f842667c3cc2da803d064e244670429c30a621905615b4a6aee963cab081028b6d10d7b563444fa6969
|
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
|
|
@@ -111,7 +112,7 @@ transformer = lambda do |attributes, key = :user|
|
|
111
112
|
end
|
112
113
|
|
113
114
|
Etcher::Registry.new(contract:, model:, transformers: [transformer])
|
114
|
-
.add_loader(
|
115
|
+
.add_loader(:environment, %w[USER HOME])
|
115
116
|
.then { |registry| Etcher.new(registry).call }
|
116
117
|
|
117
118
|
# Success(#<data user="DEMO", home="/Users/demo">)
|
@@ -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
|
----
|
@@ -292,16 +293,35 @@ registry = Etcher::Registry.new.add_loader MyLoader.new
|
|
292
293
|
|
293
294
|
There are a few guidelines to using them:
|
294
295
|
|
295
|
-
*
|
296
|
-
* All
|
296
|
+
* All loaders must respond to `#call` with no arguments.
|
297
|
+
* 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.`)
|
298
|
+
* All keys are symbolized after the loader is called which helps streamline merging and overriding values from the same keys across multiple configurations.
|
297
299
|
* 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
300
|
* 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
301
|
|
300
|
-
|
302
|
+
For convenience, all loaders -- only packaged with this gem -- can be registered by symbol instead of constant/instance. Example:
|
303
|
+
|
304
|
+
[source,ruby]
|
305
|
+
----
|
306
|
+
registry = Etcher::Registry.new
|
307
|
+
|
308
|
+
# Environment
|
309
|
+
registry.add_loader :environment
|
310
|
+
|
311
|
+
# JSON
|
312
|
+
registry.add_loader :json, "path/to/configuration.json"
|
313
|
+
|
314
|
+
# YAML
|
315
|
+
registry.add_loader :yaml, "path/to/configuration.yml"
|
316
|
+
----
|
317
|
+
|
318
|
+
Any positional or keyword arguments will be passed to the loader's constructor. _This only works when using `Registry#add_loader`, though._
|
319
|
+
|
320
|
+
The next sections will help you learn about the supported loaders and how to build your own custom loader.
|
301
321
|
|
302
322
|
==== Environment
|
303
323
|
|
304
|
-
Use `Etcher::Loaders::Environment` to load configuration information from your {environment_link}. By default, this object wraps `ENV`, uses an empty array for keys
|
324
|
+
Use `:environment` or `Etcher::Loaders::Environment` to load configuration information from your {environment_link}. By default, this object wraps `ENV`, uses an empty array for included keys, and answers a filtered hash where all keys are downcased. _If you don't specify keys to include, then an empty hash is answered back_. Here's a few examples:
|
305
325
|
|
306
326
|
[source,ruby]
|
307
327
|
----
|
@@ -323,6 +343,25 @@ loader.call
|
|
323
343
|
|
324
344
|
This loader is great for pulling from environment variables as a fallback configuration for your application.
|
325
345
|
|
346
|
+
==== Hash
|
347
|
+
|
348
|
+
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:
|
349
|
+
|
350
|
+
[source,ruby]
|
351
|
+
----
|
352
|
+
# Default behavior.
|
353
|
+
loader = Etcher::Loaders::Hash.new
|
354
|
+
loader.call
|
355
|
+
# Success({})
|
356
|
+
|
357
|
+
# With custom attributes
|
358
|
+
loader = Etcher::Loaders::Hash.new one: 1, two: 2
|
359
|
+
loader.call
|
360
|
+
# Success({:one=>1, :two=>2})
|
361
|
+
----
|
362
|
+
|
363
|
+
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.
|
364
|
+
|
326
365
|
==== JSON
|
327
366
|
|
328
367
|
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):
|
@@ -344,9 +383,12 @@ loader = Etcher::Loaders::JSON.new "your/path/to/configuration.json",
|
|
344
383
|
loader.call # Success({})
|
345
384
|
----
|
346
385
|
|
347
|
-
|
386
|
+
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:
|
348
387
|
|
349
|
-
|
388
|
+
[source,ruby]
|
389
|
+
----
|
390
|
+
Failure step: :load, constant: Etcher::Loaders::JSON, payload: "Danger!"
|
391
|
+
----
|
350
392
|
|
351
393
|
==== YAML
|
352
394
|
|
@@ -369,9 +411,12 @@ loader = Etcher::Loaders::YAML.new "your/path/to/configuration.yml",
|
|
369
411
|
loader.call # Success({})
|
370
412
|
----
|
371
413
|
|
372
|
-
|
414
|
+
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:
|
373
415
|
|
374
|
-
|
416
|
+
[source,ruby]
|
417
|
+
----
|
418
|
+
Failure step: :load, constant: Etcher::Loaders::YAML, payload: "Danger!"
|
419
|
+
----
|
375
420
|
|
376
421
|
==== Custom
|
377
422
|
|
@@ -384,26 +429,31 @@ require "dry/monads"
|
|
384
429
|
class Demo
|
385
430
|
include Dry::Monads[:result]
|
386
431
|
|
387
|
-
def initialize
|
388
|
-
@
|
432
|
+
def initialize processor: Processor.new
|
433
|
+
@processor = processor
|
389
434
|
end
|
390
435
|
|
391
|
-
def call
|
436
|
+
def call
|
437
|
+
Success processor.call
|
438
|
+
rescue ProcessorError => error
|
439
|
+
Failure step: :load, constant: self.class, payload: error.message
|
440
|
+
end
|
392
441
|
|
393
442
|
private
|
394
443
|
|
395
|
-
attr_reader :
|
444
|
+
attr_reader :processor
|
396
445
|
end
|
397
446
|
|
398
|
-
|
399
|
-
|
447
|
+
registry = Etcher::Registry[loaders: [Demo.new]]
|
448
|
+
|
449
|
+
Etcher.new(registry).call
|
400
450
|
----
|
401
451
|
|
402
|
-
While the above
|
452
|
+
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.
|
403
453
|
|
404
454
|
=== Transformers
|
405
455
|
|
406
|
-
Transformers are great for
|
456
|
+
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:
|
407
457
|
|
408
458
|
[source,ruby]
|
409
459
|
----
|
@@ -418,8 +468,9 @@ The guidelines for using transformers are:
|
|
418
468
|
|
419
469
|
* They can be initialized with whatever requirements you need.
|
420
470
|
* 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.
|
421
|
-
*
|
422
|
-
* When using a
|
471
|
+
* 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.`)
|
472
|
+
* When using a proc/lambda, the first, _required_, parameter should be the `attributes` parameter followed by a second positional `key` parameter.
|
473
|
+
* When using a class, the `key` should be your first positional parameter. Additional parameters can be supplied after if desired.
|
423
474
|
* The `attributes` passed to your transformer will have symbolized keys so you don't need to transform them further.
|
424
475
|
|
425
476
|
For example, the following capitalizes all values (which may or may not be good depending on your data structure):
|
@@ -468,52 +519,145 @@ etcher = Etcher.new(registry)
|
|
468
519
|
etcher.call
|
469
520
|
----
|
470
521
|
|
471
|
-
|
522
|
+
For convenience, all transformers -- only packaged with this gem -- can be registered by symbol instead of constant/instance. Example:
|
472
523
|
|
473
|
-
|
524
|
+
[source,ruby]
|
525
|
+
----
|
526
|
+
registry = Etcher::Registry.new
|
527
|
+
|
528
|
+
# Format
|
529
|
+
registry.add_transformer :format, :project_uri
|
530
|
+
|
531
|
+
# Time
|
532
|
+
registry.add_transformer :time
|
533
|
+
----
|
534
|
+
|
535
|
+
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.
|
536
|
+
|
537
|
+
==== Basename
|
474
538
|
|
475
|
-
Use `Etcher::Transformers::
|
539
|
+
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:
|
476
540
|
|
477
|
-
|
541
|
+
[source,ruby]
|
542
|
+
----
|
543
|
+
transformer = Etcher::Transformers::Basename.new :demo
|
544
|
+
transformer.call({})
|
545
|
+
# Success({:demo=>"scratch"})
|
546
|
+
|
547
|
+
transformer = Etcher::Transformers::Basename.new :demo, fallback: "undefined"
|
548
|
+
transformer.call({})
|
549
|
+
# Success({:demo=>"undefined"})
|
550
|
+
|
551
|
+
transformer = Etcher::Transformers::Basename.new :demo
|
552
|
+
transformer.call({demo: "defined"})
|
553
|
+
# Success({:demo=>"defined"})
|
554
|
+
----
|
555
|
+
|
556
|
+
==== Root
|
557
|
+
|
558
|
+
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:
|
478
559
|
|
479
560
|
[source,ruby]
|
480
561
|
----
|
481
|
-
transformer = Etcher::Transformers::
|
562
|
+
transformer = Etcher::Transformers::Root.new :demo
|
563
|
+
transformer.call({})
|
564
|
+
# Success({:demo=>#<Pathname:/Users/demo/Engineering/OSS/scratch>})
|
565
|
+
|
566
|
+
transformer = Etcher::Transformers::Root.new :demo, fallback: "undefined"
|
482
567
|
transformer.call({})
|
483
|
-
# Success({:
|
568
|
+
# Success({:demo=>#<Pathname:/Users/demo/Engineering/undefined>})
|
569
|
+
|
570
|
+
transformer = Etcher::Transformers::Root.new :demo
|
571
|
+
transformer.call({demo: "defined"})
|
572
|
+
# Success({:demo=>#<Pathname:/Users/demo/Engineering/defined>})
|
573
|
+
----
|
574
|
+
|
575
|
+
==== Format
|
576
|
+
|
577
|
+
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. Example:
|
578
|
+
|
579
|
+
[source,ruby]
|
580
|
+
----
|
581
|
+
attributes = {
|
582
|
+
organization_uri: "https://acme.io",
|
583
|
+
project_name: "test",
|
584
|
+
project_uri: "%<organization_uri>s/projects/%<project_name>s"
|
585
|
+
}
|
586
|
+
|
587
|
+
Etcher::Transformers::Format.new(:project_uri).call attributes
|
588
|
+
# Success(
|
589
|
+
{
|
590
|
+
organization_uri: "https://acme.io",
|
591
|
+
project_name: "test",
|
592
|
+
project_uri: "https://acme.io/projects/test"
|
593
|
+
}
|
594
|
+
)
|
595
|
+
|
596
|
+
attributes.delete :project_name
|
597
|
+
transformer.call attributes
|
484
598
|
|
599
|
+
# Failure(
|
600
|
+
# {
|
601
|
+
# step: :transform,
|
602
|
+
# constant: Etcher::Transformers::Format,
|
603
|
+
# payload: "Unable to transform :project_uri, missing specifier: \"<project_name>\"."
|
604
|
+
# }
|
605
|
+
# )
|
606
|
+
----
|
607
|
+
|
608
|
+
You can also, safely, transform a value which _doesn't_ have string specifiers:
|
609
|
+
|
610
|
+
[source,ruby]
|
611
|
+
----
|
612
|
+
Etcher::Transformers::Format.new(:version).call(version: "1.2.3")
|
613
|
+
# Success({:version=>"1.2.3"})
|
614
|
+
----
|
615
|
+
|
616
|
+
Normally, you'd get a "too many arguments for format string" warning but this transformer detects and immediate 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.
|
617
|
+
|
618
|
+
==== Time
|
619
|
+
|
620
|
+
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.
|
621
|
+
|
622
|
+
You must supply a key and `Time.now.utc` is the default fallback. You can customize as desired. Example:
|
623
|
+
|
624
|
+
[source,ruby]
|
625
|
+
----
|
485
626
|
transformer = Etcher::Transformers::Time.new :now
|
486
627
|
transformer.call({})
|
487
|
-
# Success({:now=>2024-
|
628
|
+
# Success({:now=>2024-06-15 22:43:29.178488 UTC})
|
488
629
|
|
489
630
|
transformer = Etcher::Transformers::Time.new :now, fallback: Time.utc(2000, 1, 1)
|
490
631
|
transformer.call({})
|
491
632
|
# Success({:now=>2000-01-01 00:00:00 UTC})
|
492
633
|
|
493
|
-
transformer = Etcher::Transformers::Time.new
|
494
|
-
transformer.call({
|
495
|
-
# Success({:
|
634
|
+
transformer = Etcher::Transformers::Time.new :now
|
635
|
+
transformer.call({now: Time.utc(2000, 1, 1)})
|
636
|
+
# Success({:now=>2000-01-01 00:00:00 UTC})
|
496
637
|
----
|
497
638
|
|
498
639
|
=== Overrides
|
499
640
|
|
500
|
-
Overrides are what you pass to the Etcher instance when called. Example:
|
641
|
+
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:
|
501
642
|
|
502
643
|
[source,ruby]
|
503
644
|
----
|
504
645
|
etcher = Etcher.new
|
646
|
+
|
647
|
+
# With symbol keys.
|
505
648
|
etcher.call name: "test", label: "Test"
|
649
|
+
# Success({:name=>"test", :label=>"Test"})
|
506
650
|
|
651
|
+
# With string keys.
|
652
|
+
etcher.call "name" => "test", "label" => "Test"
|
507
653
|
# Success({:name=>"test", :label=>"Test"})
|
508
654
|
----
|
509
655
|
|
510
|
-
Overrides are applied _after_ any
|
511
|
-
|
512
|
-
⚠️ In Version 2.0.0, this step will be changed to occur _after_ the Transform step for maximum flexibility.
|
656
|
+
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.
|
513
657
|
|
514
658
|
=== Resolver
|
515
659
|
|
516
|
-
In situations where you'd like Etcher to handle the complete load, transform, validate, and
|
660
|
+
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:
|
517
661
|
|
518
662
|
[source,ruby]
|
519
663
|
----
|
@@ -540,23 +684,22 @@ registry = Etcher::Registry.new(contract:, model:)
|
|
540
684
|
|
541
685
|
Etcher.call registry
|
542
686
|
|
543
|
-
#
|
687
|
+
# 🛑 Etcher validate failure (Etcher::Builder). Unable to load configuration:
|
544
688
|
# - to is missing
|
545
689
|
# - from is missing
|
546
690
|
|
547
691
|
Etcher.call registry, to: "Mindy"
|
548
692
|
|
549
|
-
#
|
693
|
+
# 🛑 Etcher validate failure (Etcher::Builder). Unable to load configuration:
|
550
694
|
# - from is missing
|
551
695
|
|
552
|
-
|
553
696
|
registry = Etcher::Registry.new(model: Data.define(:name, :label))
|
554
697
|
Etcher.call registry, to: "Mindy"
|
555
698
|
|
556
|
-
#
|
699
|
+
# 🛑 Etcher model failure (Etcher::Builder). Missing keywords: :name, :label.
|
557
700
|
----
|
558
701
|
|
559
|
-
💡 When using a custom registry, make sure it's the first argument.
|
702
|
+
💡 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.
|
560
703
|
|
561
704
|
== Development
|
562
705
|
|
@@ -580,7 +723,7 @@ bin/console
|
|
580
723
|
|
581
724
|
The following illustrates the full sequences of events when _etching_ new records:
|
582
725
|
|
583
|
-
image::https://alchemists.io/images/projects/etcher/
|
726
|
+
image::https://alchemists.io/images/projects/etcher/architecture.png[Architecture Diagram,1250,1071,role=focal_point]
|
584
727
|
|
585
728
|
== Tests
|
586
729
|
|
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 = "
|
5
|
+
spec.version = "2.0.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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
|
5
|
+
# Finds internal constant if moniker matches, otherwise answers a failure.
|
6
|
+
module Etcher
|
7
|
+
include Dry::Monads[:result]
|
8
|
+
|
9
|
+
Finder = lambda do |namespace, moniker|
|
10
|
+
Etcher.const_get(namespace)
|
11
|
+
.constants
|
12
|
+
.find { |constant| constant.downcase == moniker }
|
13
|
+
.then do |constant|
|
14
|
+
return Dry::Monads::Success Etcher.const_get("#{namespace}::#{constant}") if constant
|
15
|
+
|
16
|
+
Dry::Monads::Failure "Unable to select #{moniker.inspect} within #{namespace.downcase}."
|
17
|
+
end
|
18
|
+
rescue NameError
|
19
|
+
Dry::Monads::Failure "Invalid namespace: #{namespace.inspect}."
|
20
|
+
end
|
21
|
+
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
@@ -3,17 +3,42 @@
|
|
3
3
|
module Etcher
|
4
4
|
# Provides a registry of customization for loading and resolving a configuration.
|
5
5
|
Registry = Data.define :contract, :model, :loaders, :transformers do
|
6
|
+
def self.find namespace, moniker, logger: LOGGER
|
7
|
+
case Finder.call namespace, moniker
|
8
|
+
in Success(constant) then constant
|
9
|
+
in Failure(message) then logger.abort message
|
10
|
+
else logger.abort "Unable to find constant in registry."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
def initialize contract: Contract, model: Hash, loaders: [], transformers: []
|
7
15
|
super
|
8
16
|
end
|
9
17
|
|
10
|
-
def add_loader loader
|
11
|
-
|
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(...) }
|
33
|
+
else
|
34
|
+
collection.append item
|
35
|
+
end
|
36
|
+
|
12
37
|
self
|
13
38
|
end
|
14
39
|
|
15
|
-
def
|
16
|
-
|
40
|
+
def remove index, collection
|
41
|
+
collection.delete_at index
|
17
42
|
self
|
18
43
|
end
|
19
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,35 @@
|
|
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, **pass_throughs
|
12
|
+
@key = key
|
13
|
+
@pass_throughs = pass_throughs
|
14
|
+
@pattern = /%<.+>s/o
|
15
|
+
end
|
16
|
+
|
17
|
+
def call attributes
|
18
|
+
value = attributes[key]
|
19
|
+
|
20
|
+
return Success attributes unless value && value.match?(pattern)
|
21
|
+
|
22
|
+
Success attributes.merge!(key => format(value, **attributes, **pass_throughs))
|
23
|
+
rescue KeyError => error
|
24
|
+
Failure step: :transform,
|
25
|
+
constant: self.class,
|
26
|
+
payload: "Unable to transform #{key.inspect}, missing specifier: " \
|
27
|
+
"\"#{error.message[/<.+>/]}\"."
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :key, :pass_throughs, :pattern
|
33
|
+
end
|
34
|
+
end
|
35
|
+
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:
|
4
|
+
version: 2.0.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-05
|
38
|
+
date: 2024-07-05 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
|
@@ -150,11 +150,16 @@ files:
|
|
150
150
|
- lib/etcher.rb
|
151
151
|
- lib/etcher/builder.rb
|
152
152
|
- lib/etcher/contract.rb
|
153
|
+
- lib/etcher/finder.rb
|
153
154
|
- lib/etcher/loaders/environment.rb
|
155
|
+
- lib/etcher/loaders/hash.rb
|
154
156
|
- lib/etcher/loaders/json.rb
|
155
157
|
- lib/etcher/loaders/yaml.rb
|
156
158
|
- lib/etcher/registry.rb
|
157
159
|
- lib/etcher/resolver.rb
|
160
|
+
- lib/etcher/transformers/basename.rb
|
161
|
+
- lib/etcher/transformers/format.rb
|
162
|
+
- lib/etcher/transformers/root.rb
|
158
163
|
- lib/etcher/transformers/time.rb
|
159
164
|
- lib/etcher/types.rb
|
160
165
|
homepage: https://alchemists.io/projects/etcher
|
@@ -183,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
188
|
- !ruby/object:Gem::Version
|
184
189
|
version: '0'
|
185
190
|
requirements: []
|
186
|
-
rubygems_version: 3.5.
|
191
|
+
rubygems_version: 3.5.14
|
187
192
|
signing_key:
|
188
193
|
specification_version: 4
|
189
194
|
summary: A monadic configuration loader, transformer, and validator.
|
metadata.gz.sig
CHANGED
Binary file
|