etcher 1.2.0 → 1.4.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 +27 -28
- 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.tar.gz.sig +0 -0
- metadata +3 -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: 4a7381f41e75ad83f44a491f33f396be3ffb5b401a1a72fdcef2086984424fff
|
4
|
+
data.tar.gz: '09b9c86d96af82fbc821e23c92e7966c97234e61625d3d23838471b930906629'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d3008b4581dfd66426ba9e9c2ed0158241d130c2e68372f124369dd42c5893fed86aff36fe9e1b14612fff2139f39cf7e4b07492b5faac13699a3f0d87f609c
|
7
|
+
data.tar.gz: 885d3f522cefba39eeb8765fe671ea822d52edefc700d1633997825fd7cb61c834db7d6111cbd92881c5c9d232519e70bb4aa120027cc6ad432e82286d15fb99
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
|
5
5
|
:data_link: link:https://alchemists.io/articles/ruby_data[Data]
|
6
6
|
:demeter_link: link:https://en.wikipedia.org/wiki/Law_of_Demeter[Law of Demeter]
|
7
|
-
:dry_container_link: link:https://dry-rb.org/gems/dry-container[Dry Container]
|
8
7
|
:dry_monads_link: link:https://dry-rb.org/gems/dry-monads[Dry Monads]
|
9
8
|
:dry_schema_link: link:https://dry-rb.org/gems/dry-schema[Dry Schema]
|
10
9
|
:dry_types_link: link:https://dry-rb.org/gems/dry-types[Dry Types]
|
@@ -13,10 +12,10 @@
|
|
13
12
|
:gitt_link: link:https://alchemists.io/projects/gitt[Gitt]
|
14
13
|
:hash_link: link:https://rubyapi.org/o/hash[Hash]
|
15
14
|
:json_link: link:https://rubyapi.org/o/json[JSON]
|
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
18
|
:struct_link: link:https://alchemists.io/articles/ruby_structs[Struct]
|
19
|
-
:transactable_link: link:https://alchemists.io/projects/transactable[Transactable]
|
20
19
|
:versionaire_link: link:https://alchemists.io/projects/versionaire[Versionaire]
|
21
20
|
:xdg_link: link:https://alchemists.io/projects/xdg[XDG]
|
22
21
|
:yaml_link: link:https://rubyapi.org/o/yaml[YAML]
|
@@ -29,7 +28,7 @@ ____
|
|
29
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.
|
30
29
|
____
|
31
30
|
|
32
|
-
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.
|
33
32
|
|
34
33
|
toc::[]
|
35
34
|
|
@@ -89,7 +88,7 @@ etcher.call({one: 1, two: 2})
|
|
89
88
|
# Success({:one=>1, :two=>2})
|
90
89
|
----
|
91
90
|
|
92
|
-
Notice you get a monad -- either a `Success` or `Failure` -- as provided by the {dry_monads_link} gem. This allows you to create more sophisticated pipelines as found with the {
|
91
|
+
Notice you get a monad -- either a `Success` or `Failure` -- as provided by the {dry_monads_link} gem. This allows you to create more sophisticated pipelines as found with the {pipeable_link} gem or any kind of failsafe workflow you might need.
|
93
92
|
|
94
93
|
By default, any attributes you message the instance with will only pass through what you gave it and always answer a `Success`. This is nice for initial experimentation but true power comes with full customization of the instance. Here's an advanced configuration showing all features:
|
95
94
|
|
@@ -107,8 +106,8 @@ end
|
|
107
106
|
|
108
107
|
model = Data.define :user, :home
|
109
108
|
|
110
|
-
transformer = lambda do |
|
111
|
-
Dry::Monads::Success
|
109
|
+
transformer = lambda do |attributes, key = :user|
|
110
|
+
Dry::Monads::Success attributes.merge! key => attributes[key].upcase
|
112
111
|
end
|
113
112
|
|
114
113
|
Etcher::Registry.new(contract:, model:, transformers: [transformer])
|
@@ -123,7 +122,7 @@ The above can be broken down into a series of steps:
|
|
123
122
|
. A {dry_schema_link} contract -- loaded with {dry_monads_link} extensions -- is created to verify untrusted attributes.
|
124
123
|
. A model is created with attributes: `user` and `home`.
|
125
124
|
. A registry is constructed with a custom contract, model, loader, and transformer.
|
126
|
-
. Finally, we see a _successfully_ built configuration for further use.
|
125
|
+
. Finally, we see a _successfully_ built configuration for further use within your application.
|
127
126
|
|
128
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.
|
129
128
|
|
@@ -133,11 +132,11 @@ While this is a more advanced use case, you'll usually only need to register a c
|
|
133
132
|
|
134
133
|
As hinted at above, the complete sequence of steps are performed in the order listed:
|
135
134
|
|
136
|
-
. *Load*: Each loader, if any, is called and merged with the previous loader to build initial
|
137
|
-
. *Override*: Any overrides are merged with the result of the last loader to produce updated
|
138
|
-
. *Transform*: Each transformer, if any, is called to transform and manipulate the
|
139
|
-
. *Validate*: The contract is called to validate the
|
140
|
-
. *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.
|
141
140
|
|
142
141
|
You can use the above steps as a reference when using this gem. Each step is explained in greater detail below.
|
143
142
|
|
@@ -151,7 +150,7 @@ Etcher::Registry.new
|
|
151
150
|
# #<data Etcher::Registry contract=#<Proc:0x000000010e393550 contract.rb:7 (lambda)>, model=Hash, loaders=[], transformers=[]>
|
152
151
|
----
|
153
152
|
|
154
|
-
Since the registry is
|
153
|
+
Since the registry is {data_link}, you can initialize with everything you need:
|
155
154
|
|
156
155
|
[source,ruby]
|
157
156
|
----
|
@@ -176,11 +175,11 @@ registry = Etcher::Registry.new
|
|
176
175
|
|
177
176
|
=== Contracts
|
178
177
|
|
179
|
-
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:
|
180
179
|
|
181
180
|
* `#call`: Must be able to consume a {hash_link} and answer an object which can respond to `#to_monad`.
|
182
181
|
|
183
|
-
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:
|
184
183
|
|
185
184
|
[source,ruby]
|
186
185
|
----
|
@@ -210,7 +209,7 @@ etcher.call from: "Mork", to: "Mindy"
|
|
210
209
|
# Success({:from=>"Mork", :to=>"Mindy"})
|
211
210
|
----
|
212
211
|
|
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 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.
|
214
213
|
|
215
214
|
=== Types
|
216
215
|
|
@@ -268,7 +267,7 @@ model = Data.define :from, :to
|
|
268
267
|
etcher = Etcher::Registry[model:].then { |registry| Etcher.new registry }
|
269
268
|
|
270
269
|
etcher.call
|
271
|
-
# Failure({:step=>:
|
270
|
+
# Failure({:step=>:model, :payload=>"Missing keywords: :from, :to."})
|
272
271
|
|
273
272
|
etcher.call from: "Mork", to: "Mindy"
|
274
273
|
# Success(#<data Model from="Mork", to="Mindy">)
|
@@ -345,7 +344,7 @@ loader = Etcher::Loaders::JSON.new "your/path/to/configuration.json",
|
|
345
344
|
loader.call # Success({})
|
346
345
|
----
|
347
346
|
|
348
|
-
|
347
|
+
Otherwise, if the file exists with content, you'll get a `Hash` wrapped as a `Success`.
|
349
348
|
|
350
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.
|
351
350
|
|
@@ -370,7 +369,7 @@ loader = Etcher::Loaders::YAML.new "your/path/to/configuration.yml",
|
|
370
369
|
loader.call # Success({})
|
371
370
|
----
|
372
371
|
|
373
|
-
|
372
|
+
Otherwise, if the file exists with content, you'll get a `Hash` wrapped as a `Success`.
|
374
373
|
|
375
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.
|
376
375
|
|
@@ -418,9 +417,9 @@ registry = Etcher::Registry.new.add_transformer MyTransformer
|
|
418
417
|
Here are a few guidelines to using them:
|
419
418
|
|
420
419
|
* They can be initialized with whatever requirements you might need.
|
421
|
-
* They must respond to `#call` which takes a required `
|
422
|
-
* A second _optional_ positional `key` parameter should follow your `
|
423
|
-
* The `
|
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
|
+
* A second _optional_ positional `key` parameter should follow your `attributes` parameter when implementing your transformer. This allows you to quickly refactor the key later while also reducing key duplication throughout your implementation.
|
422
|
+
* The `attributes` passed to your transformer will have symbolized keys so you don't need to transform them further.
|
424
423
|
|
425
424
|
Here are a few examples of where you could go with this:
|
426
425
|
|
@@ -430,7 +429,7 @@ The following capitalizes all values (which may or may not be good depending on
|
|
430
429
|
----
|
431
430
|
require "dry/monads"
|
432
431
|
|
433
|
-
Capitalize = ->
|
432
|
+
Capitalize = -> attributes { Dry::Monads::Success attributes.transform_values!(&:capitalize) }
|
434
433
|
Capitalize.call(name: "test")
|
435
434
|
|
436
435
|
# Success({:name=>"Test"})
|
@@ -442,9 +441,9 @@ The following updates current time relative to when configuration was transforme
|
|
442
441
|
----
|
443
442
|
require "dry/monads"
|
444
443
|
|
445
|
-
CurrentTime = lambda do |
|
446
|
-
|
447
|
-
|
444
|
+
CurrentTime = lambda do |attributes, key = :at, at: Time.now|
|
445
|
+
attributes.fetch(key) { at }
|
446
|
+
.then { |value| Dry::Monads::Success attributes.merge!(key => value) }
|
448
447
|
end
|
449
448
|
|
450
449
|
CurrentTime.call({})
|
@@ -470,7 +469,7 @@ class GitEmail
|
|
470
469
|
@git = git
|
471
470
|
end
|
472
471
|
|
473
|
-
def call(
|
472
|
+
def call(attributes) = git.get("user.email").fmap { |value| attributes[key] = value }
|
474
473
|
|
475
474
|
private
|
476
475
|
|
@@ -517,7 +516,7 @@ Etcher.call name: "demo"
|
|
517
516
|
# {:name=>"demo"}
|
518
517
|
----
|
519
518
|
|
520
|
-
When called
|
519
|
+
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:
|
521
520
|
|
522
521
|
[source,ruby]
|
523
522
|
----
|
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
|
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.4.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-11 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: cogger
|
@@ -182,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
182
|
- !ruby/object:Gem::Version
|
183
183
|
version: '0'
|
184
184
|
requirements: []
|
185
|
-
rubygems_version: 3.5.
|
185
|
+
rubygems_version: 3.5.10
|
186
186
|
signing_key:
|
187
187
|
specification_version: 4
|
188
188
|
summary: A monadic configuration loader, transformer, and validator.
|
metadata.gz.sig
CHANGED
Binary file
|