etcher 1.3.0 → 1.5.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 +55 -50
- data/etcher.gemspec +1 -1
- data/lib/etcher/builder.rb +12 -15
- data/lib/etcher/contract.rb +3 -3
- data/lib/etcher/resolver.rb +1 -1
- data/lib/etcher/transformers/time.rb +26 -0
- data.tar.gz.sig +0 -0
- metadata +4 -3
- 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: 75820bd97bf8b61960802c1bb85cb91d5ad583778ca5860d4a887eea59f516a5
|
4
|
+
data.tar.gz: b83d2f5ed78a7ef5ba8b622b63855226413fb4a574cf44577a9f825509a33d82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 891f2042e506243598e6545846ea5a4aeae21658315f5c25e32121c45279a64721cea0c275cf8ce7d723724007e108d3e09b7d538b6d28c7a049c7599da09c73
|
7
|
+
data.tar.gz: 0cb1f523435a2244994e88a9a51852f2a67b83c10bb3b817b147c9c80dd615bf777ecec069b2121af497dc225c6f1a032f53e856d41961145255b792eb70c80c
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -28,7 +28,7 @@ ____
|
|
28
28
|
[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
29
|
____
|
30
30
|
|
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
|
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 and pairs well with the {xdg_link}, {runcom_link}, and {sod_link} gems.
|
32
32
|
|
33
33
|
toc::[]
|
34
34
|
|
@@ -106,8 +106,8 @@ end
|
|
106
106
|
|
107
107
|
model = Data.define :user, :home
|
108
108
|
|
109
|
-
transformer = lambda do |
|
110
|
-
Dry::Monads::Success
|
109
|
+
transformer = lambda do |attributes, key = :user|
|
110
|
+
Dry::Monads::Success attributes.merge! key => attributes[key].upcase
|
111
111
|
end
|
112
112
|
|
113
113
|
Etcher::Registry.new(contract:, model:, transformers: [transformer])
|
@@ -122,7 +122,7 @@ The above can be broken down into a series of steps:
|
|
122
122
|
. A {dry_schema_link} contract -- loaded with {dry_monads_link} extensions -- is created to verify untrusted attributes.
|
123
123
|
. A model is created with attributes: `user` and `home`.
|
124
124
|
. A registry is constructed with a custom contract, model, loader, and transformer.
|
125
|
-
. Finally, we see a _successfully_ built configuration for further use.
|
125
|
+
. Finally, we see a _successfully_ built configuration for further use within your application.
|
126
126
|
|
127
127
|
While this is a more advanced use case, you'll usually only need to register a contract and model. The loaders and transformers provide additional firepower in situations where you need to do more with your data. We'll look at each of these components in greater detail next.
|
128
128
|
|
@@ -132,11 +132,11 @@ While this is a more advanced use case, you'll usually only need to register a c
|
|
132
132
|
|
133
133
|
As hinted at above, the complete sequence of steps are performed in the order listed:
|
134
134
|
|
135
|
-
. *Load*: Each loader, if any, is called and merged with the previous loader to build initial
|
136
|
-
. *Override*: Any overrides are merged with the result of the last loader to produce updated
|
137
|
-
. *Transform*: Each transformer, if any, is called to transform and manipulate the
|
138
|
-
. *Validate*: The contract is called to validate the
|
139
|
-
. *Model*: The model consumes the
|
135
|
+
. *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
|
+
. *Transform*: Each transformer, if any, is called to transform and manipulate the attributes.
|
138
|
+
. *Validate*: The contract is called to validate the attributes as previously loaded, overwritten, and transformed.
|
139
|
+
. *Model*: The model consumes the attributes of the validated contract and creates a new record for you to use as needed.
|
140
140
|
|
141
141
|
You can use the above steps as a reference when using this gem. Each step is explained in greater detail below.
|
142
142
|
|
@@ -150,7 +150,7 @@ Etcher::Registry.new
|
|
150
150
|
# #<data Etcher::Registry contract=#<Proc:0x000000010e393550 contract.rb:7 (lambda)>, model=Hash, loaders=[], transformers=[]>
|
151
151
|
----
|
152
152
|
|
153
|
-
Since the registry is
|
153
|
+
Since the registry is {data_link}, you can initialize with everything you need:
|
154
154
|
|
155
155
|
[source,ruby]
|
156
156
|
----
|
@@ -175,11 +175,11 @@ registry = Etcher::Registry.new
|
|
175
175
|
|
176
176
|
=== Contracts
|
177
177
|
|
178
|
-
Contracts are critical piece of this workflow as they provide a way to validate incoming data, remove unwanted data, and create a sanitized record for use in your application. Any contract that has the following behavior will work:
|
178
|
+
Contracts are a critical piece of this workflow as they provide a way to validate incoming data, remove unwanted data, and create a sanitized record for use in your application. Any contract that has the following behavior will work:
|
179
179
|
|
180
180
|
* `#call`: Must be able to consume a {hash_link} and answer an object which can respond to `#to_monad`.
|
181
181
|
|
182
|
-
The best gems which adhere to this interface are: {dry_schema_link} and {dry_validation_link}. You'll also want to make sure the {dry_monads_link} extensions are loaded as briefly shown earlier so the result will respond to `#to_monad`. Here's how to enable monad support if using both gems:
|
182
|
+
The best gems which adhere to this interface are: {dry_schema_link} and {dry_validation_link}. You'll also want to make sure the {dry_monads_link} extensions are loaded, as briefly shown earlier, so the result will respond to `#to_monad`. Here's how to enable monad support if using both gems:
|
183
183
|
|
184
184
|
[source,ruby]
|
185
185
|
----
|
@@ -209,7 +209,7 @@ etcher.call from: "Mork", to: "Mindy"
|
|
209
209
|
# Success({:from=>"Mork", :to=>"Mindy"})
|
210
210
|
----
|
211
211
|
|
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 and it would be nice to structured
|
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 and it would be nice to structured record which will be explained shortly.
|
213
213
|
|
214
214
|
=== Types
|
215
215
|
|
@@ -267,7 +267,7 @@ model = Data.define :from, :to
|
|
267
267
|
etcher = Etcher::Registry[model:].then { |registry| Etcher.new registry }
|
268
268
|
|
269
269
|
etcher.call
|
270
|
-
# Failure({:step=>:
|
270
|
+
# Failure({:step=>:model, :payload=>"Missing keywords: :from, :to."})
|
271
271
|
|
272
272
|
etcher.call from: "Mork", to: "Mindy"
|
273
273
|
# Success(#<data Model from="Mork", to="Mindy">)
|
@@ -344,7 +344,7 @@ loader = Etcher::Loaders::JSON.new "your/path/to/configuration.json",
|
|
344
344
|
loader.call # Success({})
|
345
345
|
----
|
346
346
|
|
347
|
-
|
347
|
+
Otherwise, if the file exists with content, you'll get a `Hash` wrapped as a `Success`.
|
348
348
|
|
349
349
|
ℹ️ The logger is only used to log debug information when issues are encountered when reading from the file. This is done to reduce noise in your console when a configuration might have issues and can safely revert to the fallback in order to load the rest of the configuration.
|
350
350
|
|
@@ -369,7 +369,7 @@ loader = Etcher::Loaders::YAML.new "your/path/to/configuration.yml",
|
|
369
369
|
loader.call # Success({})
|
370
370
|
----
|
371
371
|
|
372
|
-
|
372
|
+
Otherwise, if the file exists with content, you'll get a `Hash` wrapped as a `Success`.
|
373
373
|
|
374
374
|
ℹ️ The logger is only used to log debug information when issues are encountered when reading from the file. This is done to reduce noise in your console when a configuration might have issues and can safely revert to the fallback in order to load the rest of the configuration.
|
375
375
|
|
@@ -414,49 +414,27 @@ registry = Etcher::Registry[transformers: [MyTransformer]]
|
|
414
414
|
registry = Etcher::Registry.new.add_transformer MyTransformer
|
415
415
|
----
|
416
416
|
|
417
|
-
|
417
|
+
The guidelines for using transformers are:
|
418
418
|
|
419
|
-
* They can be initialized with whatever requirements you
|
420
|
-
* They must respond to `#call` which takes a required `
|
421
|
-
*
|
422
|
-
*
|
419
|
+
* They can be initialized with whatever requirements you need.
|
420
|
+
* 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
|
+
* When using a proc/lambda, the first, _required_, parameter should be the `attributes` parameter followed by an _optional_ positional `key` parameter with a default value. This allows you to quickly refactor the key later while also reducing key duplication throughout your implementation.
|
422
|
+
* When using a class, the `key` should be your first positional parameter with a default value. Additional parameters can be supplied after if desired.
|
423
|
+
* The `attributes` passed to your transformer will have symbolized keys so you don't need to transform them further.
|
423
424
|
|
424
|
-
|
425
|
-
|
426
|
-
The following capitalizes all values (which may or may not be good depending on your data structure).
|
425
|
+
For example, the following capitalizes all values (which may or may not be good depending on your data structure):
|
427
426
|
|
428
427
|
[source,ruby]
|
429
428
|
----
|
430
429
|
require "dry/monads"
|
431
430
|
|
432
|
-
Capitalize = ->
|
431
|
+
Capitalize = -> attributes { Dry::Monads::Success attributes.transform_values!(&:capitalize) }
|
433
432
|
Capitalize.call(name: "test")
|
434
433
|
|
435
434
|
# Success({:name=>"Test"})
|
436
435
|
----
|
437
436
|
|
438
|
-
The following
|
439
|
-
|
440
|
-
[source,ruby]
|
441
|
-
----
|
442
|
-
require "dry/monads"
|
443
|
-
|
444
|
-
CurrentTime = lambda do |content, key = :at, at: Time.now|
|
445
|
-
content.fetch(key) { at }
|
446
|
-
.then { |value| Dry::Monads::Success content.merge!(key => value) }
|
447
|
-
end
|
448
|
-
|
449
|
-
CurrentTime.call({})
|
450
|
-
# Success({:at=>2023-04-23 15:22:23.746408 -0600})
|
451
|
-
|
452
|
-
CurrentTime.call({at: Time.utc(2023, 10, 15)})
|
453
|
-
# Success({:at=>2023-10-15 00:00:00 UTC})
|
454
|
-
|
455
|
-
CurrentTime.call({}, at: Time.utc(2023, 1, 10))
|
456
|
-
# Success({:at=>2023-01-10 00:00:00 UTC})
|
457
|
-
----
|
458
|
-
|
459
|
-
The following obtains the current Git user's email address from the global Git configuration using the {gitt_link} gem.
|
437
|
+
The following obtains the current Git user's email address from the global Git configuration using the {gitt_link} gem:
|
460
438
|
|
461
439
|
[source,ruby]
|
462
440
|
----
|
@@ -469,7 +447,7 @@ class GitEmail
|
|
469
447
|
@git = git
|
470
448
|
end
|
471
449
|
|
472
|
-
def call(
|
450
|
+
def call(attributes) = git.get("user.email").fmap { |value| attributes[key] = value }
|
473
451
|
|
474
452
|
private
|
475
453
|
|
@@ -485,11 +463,38 @@ To use all of the above, you'd only need to register and use them:
|
|
485
463
|
|
486
464
|
[source,ruby]
|
487
465
|
----
|
488
|
-
registry = Etcher::Registry[transformers: [Capitalize,
|
466
|
+
registry = Etcher::Registry[transformers: [Capitalize, GitEmail.new]]
|
489
467
|
etcher = Etcher.new(registry)
|
490
468
|
etcher.call
|
491
469
|
----
|
492
470
|
|
471
|
+
If you'd like prebuilt transformers, the following details what is supplied by this gem.
|
472
|
+
|
473
|
+
==== Time
|
474
|
+
|
475
|
+
Use `Etcher::Transformers::Time` to transform the `loaded_at` 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.
|
476
|
+
|
477
|
+
Even though `loaded_at` is the default key and `Time.now.utc` is the default fallback, you're not limited to using different keys and fallbacks. Example:
|
478
|
+
|
479
|
+
[source,ruby]
|
480
|
+
----
|
481
|
+
transformer = Etcher::Transformers::Time.new
|
482
|
+
transformer.call({})
|
483
|
+
# Success({:loaded_at=>2024-05-23 22:18:27.92767 UTC})
|
484
|
+
|
485
|
+
transformer = Etcher::Transformers::Time.new :now
|
486
|
+
transformer.call({})
|
487
|
+
# Success({:now=>2024-05-23 22:18:49.93189 UTC})
|
488
|
+
|
489
|
+
transformer = Etcher::Transformers::Time.new :now, fallback: Time.utc(2000, 1, 1)
|
490
|
+
transformer.call({})
|
491
|
+
# Success({:now=>2000-01-01 00:00:00 UTC})
|
492
|
+
|
493
|
+
transformer = Etcher::Transformers::Time.new
|
494
|
+
transformer.call({loaded_at: Time.utc(2000, 1, 1)})
|
495
|
+
# Success({:loaded_at=>2000-01-01 00:00:00 UTC})
|
496
|
+
----
|
497
|
+
|
493
498
|
=== Overrides
|
494
499
|
|
495
500
|
Overrides are what you pass to the Etcher instance when called. Example:
|
@@ -516,7 +521,7 @@ Etcher.call name: "demo"
|
|
516
521
|
# {:name=>"demo"}
|
517
522
|
----
|
518
523
|
|
519
|
-
When called
|
524
|
+
When called -- and there are no issues -- you'll get the fully formed record as a result (in this case a Hash which is the default model). You'll never a get a monad when using `Etcher.call` because this is meant to resolve the monadic pipeline for you. If any failure is encountered, then Etcher will _abort_ with a fatal log message. Here's a variation of earlier examples which demonstrates fatals:
|
520
525
|
|
521
526
|
[source,ruby]
|
522
527
|
----
|
data/etcher.gemspec
CHANGED
data/lib/etcher/builder.rb
CHANGED
@@ -16,42 +16,39 @@ module Etcher
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(**overrides)
|
19
|
-
load(overrides.symbolize_keys!).then { |
|
20
|
-
.bind { |
|
21
|
-
.bind { |
|
19
|
+
load(overrides.symbolize_keys!).then { |attributes| transform attributes }
|
20
|
+
.bind { |attributes| validate attributes }
|
21
|
+
.bind { |attributes| model attributes }
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
26
|
attr_reader :registry
|
27
27
|
|
28
|
-
# :reek:NestedIterators
|
29
|
-
# :reek:TooManyStatements
|
30
28
|
def load overrides
|
31
29
|
registry.loaders
|
32
|
-
.map { |loader| loader.call.fmap { |
|
30
|
+
.map { |loader| loader.call.fmap { |pairs| pairs.flatten_keys.symbolize_keys! } }
|
33
31
|
.each
|
34
|
-
.with_object({}) { |
|
32
|
+
.with_object({}) { |attributes, all| attributes.bind { |body| all.merge! body } }
|
35
33
|
.merge!(overrides.flatten_keys)
|
36
|
-
.then { |
|
34
|
+
.then { |attributes| Success attributes }
|
37
35
|
end
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
registry.transformers.reduce content do |all, transformer|
|
37
|
+
def transform attributes
|
38
|
+
registry.transformers.reduce attributes do |all, transformer|
|
42
39
|
all.bind { |body| transformer.call body }
|
43
40
|
end
|
44
41
|
end
|
45
42
|
|
46
|
-
def validate
|
43
|
+
def validate attributes
|
47
44
|
registry.contract
|
48
|
-
.call(
|
45
|
+
.call(attributes)
|
49
46
|
.to_monad
|
50
47
|
.or { |result| Failure step: __method__, payload: result.errors.to_h }
|
51
48
|
end
|
52
49
|
|
53
|
-
def
|
54
|
-
Success registry.model[**
|
50
|
+
def model attributes
|
51
|
+
Success registry.model[**attributes.to_h].freeze
|
55
52
|
rescue ArgumentError => error
|
56
53
|
Failure step: __method__, payload: "#{error.message.capitalize}."
|
57
54
|
end
|
data/lib/etcher/contract.rb
CHANGED
@@ -4,8 +4,8 @@ require "dry/monads"
|
|
4
4
|
|
5
5
|
module Etcher
|
6
6
|
# A simple passthrough contract.
|
7
|
-
Contract = lambda do |
|
8
|
-
def
|
9
|
-
|
7
|
+
Contract = lambda do |result|
|
8
|
+
def result.to_monad = Dry::Monads::Success self unless result.respond_to? :to_monad
|
9
|
+
result
|
10
10
|
end
|
11
11
|
end
|
data/lib/etcher/resolver.rb
CHANGED
@@ -18,7 +18,7 @@ module Etcher
|
|
18
18
|
|
19
19
|
def call(**overrides)
|
20
20
|
case builder.call(**overrides)
|
21
|
-
in Success(
|
21
|
+
in Success(attributes) then attributes
|
22
22
|
in Failure(step:, payload: String => payload)
|
23
23
|
logger.fatal { "Build failure: #{step.inspect}. #{payload}" }
|
24
24
|
kernel.abort
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
|
5
|
+
module Etcher
|
6
|
+
module Transformers
|
7
|
+
# Conditionally transforms current time for key.
|
8
|
+
class Time
|
9
|
+
include Dry::Monads[:result]
|
10
|
+
|
11
|
+
def initialize key = :loaded_at, fallback: ::Time.now.utc
|
12
|
+
@key = key
|
13
|
+
@fallback = fallback
|
14
|
+
end
|
15
|
+
|
16
|
+
def call attributes
|
17
|
+
attributes.fetch(key) { fallback }
|
18
|
+
.then { |value| Success attributes.merge!(key => value) }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :key, :fallback
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
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: 1.5.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-05-23 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: cogger
|
@@ -155,6 +155,7 @@ files:
|
|
155
155
|
- lib/etcher/loaders/yaml.rb
|
156
156
|
- lib/etcher/registry.rb
|
157
157
|
- lib/etcher/resolver.rb
|
158
|
+
- lib/etcher/transformers/time.rb
|
158
159
|
- lib/etcher/types.rb
|
159
160
|
homepage: https://alchemists.io/projects/etcher
|
160
161
|
licenses:
|
@@ -182,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
183
|
- !ruby/object:Gem::Version
|
183
184
|
version: '0'
|
184
185
|
requirements: []
|
185
|
-
rubygems_version: 3.5.
|
186
|
+
rubygems_version: 3.5.10
|
186
187
|
signing_key:
|
187
188
|
specification_version: 4
|
188
189
|
summary: A monadic configuration loader, transformer, and validator.
|
metadata.gz.sig
CHANGED
Binary file
|