nero 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/CHANGELOG.md +59 -0
- data/README.md +152 -39
- data/lib/nero/railtie.rb +10 -0
- data/lib/nero/version.rb +1 -1
- data/lib/nero.rb +449 -119
- data/rakelib/yard.rake +12 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 136899b1a5b7fd355608891703ad0e8b0c628dfcb3ae74d9a5344fb0a78b41b8
|
4
|
+
data.tar.gz: 47fce2793b077af0a637d7cac6a5e31b471c16bbbff71561f97bdf4aa904a3d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 979f099766b6c33577346e608fa8e82f890d4d3ef5b49913a142cdbcb7385ecff78776882e5ee78420f0b285388972aef8e6c4c980d4aff120b8c142efec2212
|
7
|
+
data.tar.gz: e43d9f3916bfc43b3abe49939b3953a72679fec64e8de4de5b682600d6d5ef2f66f97557e262a5558046c9e332532bc908a4317d4a114bc46754d9ff60d3242c
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,65 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
...
|
3
3
|
|
4
|
+
## [0.6.0] - 2025-04-10
|
5
|
+
|
6
|
+
### deprecations
|
7
|
+
|
8
|
+
- `Nero.load_config` - use `Nero.load_file` or `Nero.config_for`.
|
9
|
+
|
10
|
+
### other
|
11
|
+
|
12
|
+
- API docs live at https://eval.github.io/nero/
|
13
|
+
- Config for Rails
|
14
|
+
The `config.config_dir` is automatically setup, so `Nero.config_for` (formerly `Nero.load_config`) just works.
|
15
|
+
- `Nero::Config.dig!` ⛏️💥
|
16
|
+
Any (Hash-)result from `Nero.load/load_file/config_for` is now an instance of `Nero::Config`.
|
17
|
+
This class contains `dig!`, a fail-hard variant of `dig`:
|
18
|
+
```ruby
|
19
|
+
Nero.load(<<~Y).dig!(:smtp_settings, :hose) # 💥 typo
|
20
|
+
smtp_settings:
|
21
|
+
host: 127.0.0.1
|
22
|
+
port: 1025
|
23
|
+
Y
|
24
|
+
#=> 'Nero::DigExt#dig!': path not found [:smtp_settings, :hose] (ArgumentError)
|
25
|
+
```
|
26
|
+
|
27
|
+
## [0.5.0] - 2025-03-20
|
28
|
+
|
29
|
+
- tag-classes
|
30
|
+
Added [`Nero::BaseTag`](https://rubydoc.info/github/eval/nero/main/Nero/BaseTag) that is the basis of all existing tags.
|
31
|
+
This means that building upon existing tags is easier and custom tags can be more powerful.
|
32
|
+
|
33
|
+
Create new tags can be done in 3 ways:
|
34
|
+
By block (as before, but slightly changed interface):
|
35
|
+
```ruby
|
36
|
+
Nero.configure do |nero|
|
37
|
+
nero.add_tag("foo") do |tag|
|
38
|
+
# tag of type Nero::BaseTag
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
By re-using existing tags via options:
|
43
|
+
```ruby
|
44
|
+
nero.add_tag("env/upcase", klass: Nero::EnvTag[coerce: :upcase])
|
45
|
+
```
|
46
|
+
Finally, by subclassing [Nero::BaseTag](https://rubydoc.info/github/eval/nero/main/Nero/BaseTag). See the section ["custom tags"](https://github.com/eval/nero?tab=readme-ov-file#custom-tags) from the README.
|
47
|
+
|
48
|
+
- `!env/float` and `!env/float?`
|
49
|
+
- `!env/git_root` and `!env/rails_root`
|
50
|
+
Construct a path relative to some root-path:
|
51
|
+
```yaml
|
52
|
+
asset_path: !path/rails_root [ public/assets ]
|
53
|
+
```
|
54
|
+
Easy to use for your own tags:
|
55
|
+
```ruby
|
56
|
+
config.add_tag("path/project_root", klass: Nero::PathRootTag[containing: '.git']) do |path|
|
57
|
+
# possible post-processing
|
58
|
+
end
|
59
|
+
```
|
60
|
+
- [#2](https://github.com/eval/nero/pull/2) Add irb to gemfile (@dlibanori)
|
61
|
+
- [#3](https://github.com/eval/nero/pull/3) Fix missing require (@dlibanori)
|
62
|
+
|
4
63
|
## [0.4.0] - 2025-02-15
|
5
64
|
|
6
65
|
- Add `!ref`-tag:
|
data/README.md
CHANGED
@@ -11,14 +11,24 @@ Additionally, it allows you to create your own.
|
|
11
11
|
development:
|
12
12
|
# env-var with default value
|
13
13
|
secret: !env [SECRET, "dummy"]
|
14
|
+
|
14
15
|
# optional env-var with coercion
|
15
16
|
debug?: !env/bool? DEBUG
|
17
|
+
|
16
18
|
production:
|
17
|
-
# required env-var (
|
19
|
+
# required env-var (not required during development)
|
18
20
|
secret: !env SECRET
|
19
|
-
|
21
|
+
|
22
|
+
# coercion
|
20
23
|
max_threads: !env/integer [MAX_THREADS, 5]
|
21
|
-
|
24
|
+
|
25
|
+
# refer to other keys
|
26
|
+
min_threads: !env/integer [MIN_THREADS, !ref max_threads ]
|
27
|
+
|
28
|
+
# descriptive names
|
29
|
+
asset_folder: !path/rails_root [ public/assets ]
|
30
|
+
|
31
|
+
# easy to add custom tags
|
22
32
|
cache_ttl: !duration [2, hours]
|
23
33
|
```
|
24
34
|
|
@@ -26,7 +36,7 @@ production:
|
|
26
36
|
|
27
37
|
* 💎 declarative YAML-tags for e.g. requiring and coercing env-vars
|
28
38
|
* 🛠️ add custom tags
|
29
|
-
* 🛤️ `Rails.application.config_for`
|
39
|
+
* 🛤️ `Rails.application.config_for` drop-in
|
30
40
|
* ♻️ Zeitwerk-only dependency
|
31
41
|
|
32
42
|
## Installation
|
@@ -37,11 +47,28 @@ Install the gem and add to the application's Gemfile by executing:
|
|
37
47
|
bundle add nero
|
38
48
|
```
|
39
49
|
|
50
|
+
## Configuration
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Nero.configure do |nero|
|
54
|
+
# Path that `Nero.config_for` uses to resolve Symbol or String files, e.g. `Nero.config_for(:app)`
|
55
|
+
nero.config_dir = "config"
|
56
|
+
|
57
|
+
# Add custom tags (also see section about custom tags)
|
58
|
+
nero.add_tag("upcase") do |tag|
|
59
|
+
# tag is an instance of [Nero::BaseTag](https://eval.github.io/nero/Nero/BaseTag.html).
|
60
|
+
tag.args.join.upcase
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
40
65
|
## Usage
|
41
66
|
|
42
67
|
> [!WARNING]
|
43
68
|
> It's early days - the API and included tags will certainly change. Check the CHANGELOG when upgrading.
|
44
69
|
|
70
|
+
### loading a config
|
71
|
+
|
45
72
|
Given the following config:
|
46
73
|
```yaml
|
47
74
|
# config/settings.yml
|
@@ -61,7 +88,7 @@ Loading this config:
|
|
61
88
|
|
62
89
|
```ruby
|
63
90
|
# Loading development
|
64
|
-
Nero.
|
91
|
+
Nero.load_file("config/settings", root: :development)
|
65
92
|
# ...and no ENV-vars were provided
|
66
93
|
#=> {secret: "dummy", debug?: false}
|
67
94
|
|
@@ -69,13 +96,27 @@ Nero.load_config("config/settings", root: :development)
|
|
69
96
|
#=> {secret: "dummy", debug?: true}
|
70
97
|
|
71
98
|
# Loading production
|
72
|
-
Nero.
|
99
|
+
Nero.load_file("config/settings", root: :production)
|
73
100
|
# ...and no ENV-vars were provided
|
74
101
|
# raises error: key not found: "SECRET" (KeyError)
|
75
102
|
|
76
103
|
# ...with ENV {"SECRET" => "s3cr3t", "MAX_THREADS" => "3"}
|
77
104
|
#=> {secret: "s3cr3t", max_threads: 3}
|
78
105
|
```
|
106
|
+
> [!TIP]
|
107
|
+
> You can also use `Nero.config_for` (similar to [Rails.application.config_for](https://api.rubyonrails.org/classes/Rails/Application.html#method-i-config_for)).
|
108
|
+
> In Rails applications this gets configured for you. For other application you might need to adjust the `config_dir`:
|
109
|
+
```ruby
|
110
|
+
Nero.configure do |config|
|
111
|
+
config.config_dir = "config"
|
112
|
+
end
|
113
|
+
|
114
|
+
Nero.config_for(:settings, env: Rails.env)
|
115
|
+
```
|
116
|
+
|
117
|
+
[API Documentation](https://eval.github.io/nero/).
|
118
|
+
|
119
|
+
### built-in tags
|
79
120
|
|
80
121
|
The following tags are provided:
|
81
122
|
- `!env KEY`, `!env? KEY`
|
@@ -90,10 +131,11 @@ The following tags are provided:
|
|
90
131
|
secret: !env? SECRET
|
91
132
|
```
|
92
133
|
- to coerce env-values:
|
93
|
-
- `env/integer`, `env/integer?`:
|
134
|
+
- `env/integer`, `env/integer?`, `env/float`, `env/float?`:
|
94
135
|
```yaml
|
95
136
|
port: !env/integer [PORT, 3000]
|
96
137
|
threads: !env/integer? THREADS # nil when not provided
|
138
|
+
threshold: !env/float CUTOFF
|
97
139
|
```
|
98
140
|
- `env/bool`, `env/bool?`:
|
99
141
|
```yaml
|
@@ -104,6 +146,11 @@ The following tags are provided:
|
|
104
146
|
# ...or false:
|
105
147
|
debug?: !env/bool? DEBUG
|
106
148
|
```
|
149
|
+
> [!TIP]
|
150
|
+
> Make all env-var's optional by providing `ENV["NERO_ENV_ALL_OPTIONAL"]`, e.g.
|
151
|
+
```shell
|
152
|
+
$ env NERO_ENV_ALL_OPTIONAL=1 SECRET_KEY_BASE_DUMMY=1 rails asset:precompile
|
153
|
+
```
|
107
154
|
- `!path`
|
108
155
|
Create a [Pathname](https://rubyapi.org/3.4/o/pathname):
|
109
156
|
```yaml
|
@@ -113,6 +160,15 @@ The following tags are provided:
|
|
113
160
|
- !env PROJECT_ROOT
|
114
161
|
- /public/assets
|
115
162
|
```
|
163
|
+
- `!path/git_root`, `!path/rails_root`
|
164
|
+
Create a Pathname relative to some root-path.
|
165
|
+
The root-path is expected to be an existing ancestor folder of the yaml-config being parsed.
|
166
|
+
It's found by traversing up and checking for the presence of specific files/folders, e.g. '.git' (`!path/git_root`) or 'config.ru' (`!path/rails_root`).
|
167
|
+
While the root-path needs to exist, the resulting Pathname doesn't need to.
|
168
|
+
```yaml
|
169
|
+
project_root: !path/git_root
|
170
|
+
config_folder: !path/rails_root [ config ]
|
171
|
+
```
|
116
172
|
- `!uri`
|
117
173
|
Create a [URI](https://rubyapi.org/3.4/o/uri):
|
118
174
|
```yaml
|
@@ -139,7 +195,7 @@ The following tags are provided:
|
|
139
195
|
Include values from elsewhere:
|
140
196
|
```yaml
|
141
197
|
# simple
|
142
|
-
min_threads: !env [MIN_THREADS, !ref [max_threads]]
|
198
|
+
min_threads: !env/integer [MIN_THREADS, !ref [max_threads]]
|
143
199
|
max_threads: 5
|
144
200
|
|
145
201
|
# oauth_callback -refs-> base.url -refs-> base.host
|
@@ -158,39 +214,96 @@ The following tags are provided:
|
|
158
214
|
max_threads: !env[MAX_THREADS, !ref[dev, max_threads]]
|
159
215
|
```
|
160
216
|
NOTE future version should raise properly over ref-ing a non-existing path.
|
161
|
-
|
162
217
|
|
163
|
-
|
164
|
-
```ruby
|
165
|
-
Nero.configure do |nero|
|
166
|
-
nero.add_tag("foo") do |coder|
|
167
|
-
# coder.type is one of :scalar, :seq or :map
|
168
|
-
# e.g. respective YAML:
|
169
|
-
# ---
|
170
|
-
# !foo bar
|
171
|
-
# ---
|
172
|
-
# !foo
|
173
|
-
# - bar
|
174
|
-
# ---
|
175
|
-
# !foo
|
176
|
-
# bar: baz
|
177
|
-
#
|
178
|
-
# Find the value in the respective attribute, e.g. `coder.scalar`:
|
179
|
-
coder.scalar.upcase
|
180
|
-
|
181
|
-
# NOTE when needing just one argument, supporting both scalar and seq allows for chaining:
|
182
|
-
# a: !my/inc 4 # scalar suffices
|
183
|
-
# ...but when chaining, a seq is required:
|
184
|
-
# a: !my/inc [!my/square 2]
|
185
|
-
end
|
218
|
+
### custom tags
|
186
219
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
220
|
+
There's three ways to create your own tags.
|
221
|
+
|
222
|
+
For all these methods it's helpful to see the API-docs for [Nero::BaseTag](https://eval.github.io/nero/Nero/BaseTag.html).
|
223
|
+
|
224
|
+
1. **a proc**
|
225
|
+
```ruby
|
226
|
+
Nero.configure do |nero|
|
227
|
+
nero.add_tag("upcase") do |tag|
|
228
|
+
# `tag` is a `Nero::BaseTag`.
|
229
|
+
# In YAML args are provided as scalar, seq or map:
|
230
|
+
# ---
|
231
|
+
# k: !upcase bar
|
232
|
+
# ---
|
233
|
+
# k: !upcase [bar] # equivalent to:
|
234
|
+
# k: !upcase
|
235
|
+
# - bar
|
236
|
+
# ---
|
237
|
+
# k: !upcase
|
238
|
+
# bar: baz
|
239
|
+
#
|
240
|
+
# Find these args via `tag.args` (Array or Hash):
|
241
|
+
case tag.args
|
242
|
+
when Hash
|
243
|
+
tag.args.each_with_object({}) {|(k,v), acc| acc[k] = v.upcase }
|
244
|
+
else
|
245
|
+
tag.args.map(&:upcase)
|
246
|
+
end
|
247
|
+
|
248
|
+
# NOTE though a tag might just need one argument (ie scalar),
|
249
|
+
# it's helpful to accept a seq as it allows for chaining:
|
250
|
+
# a: !my/inc 4 # scalar suffices
|
251
|
+
# ...but when chaining, it needs to be a seq:
|
252
|
+
# a: !my/inc [ !my/square 2 ]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
```
|
256
|
+
Blocks are passed instances of [Nero::BaseTag](https://eval.github.io/nero/Nero/BaseTag.html).
|
257
|
+
1. **re-use existing tag-class**
|
258
|
+
You can add an existing tag under a better fitting name this way.
|
259
|
+
Also: some tag-classes have options that allow for simple customizations (like `coerce` below):
|
260
|
+
```ruby
|
261
|
+
Nero.configure do |nero|
|
262
|
+
nero.add_tag("env/upcase", klass: Nero::EnvTag[coerce: :upcase])
|
263
|
+
|
264
|
+
# Alias for path/git_root:
|
265
|
+
nero.add_tag("path/project_root", klass: Nero::PathRootTag[containing: '.git'])
|
266
|
+
end
|
267
|
+
```
|
268
|
+
1. **custom class**
|
269
|
+
```ruby
|
270
|
+
class RotTag < Nero::BaseTag
|
271
|
+
# Configure:
|
272
|
+
# ```
|
273
|
+
# config.add_tag("rot/12", klass: RotTag[n: 12])
|
274
|
+
# config.add_tag("rot/10", klass: RotTag[n: 10]) do |secret|
|
275
|
+
# "#{secret} (try breaking this!)"
|
276
|
+
# end
|
277
|
+
# ```
|
278
|
+
#
|
279
|
+
# Usage in YAML:
|
280
|
+
# ```
|
281
|
+
# secret: !rot/12 some message
|
282
|
+
# very_secret: !rot/10 [ !env [ MSG, some message ] ]
|
283
|
+
# ```
|
284
|
+
# => {secret: "EAyq yqEEmsq", very_secret: "Cywo woCCkqo (try breaking this!)"}
|
285
|
+
|
286
|
+
# By overriding `init_options` we can restrict/require options,
|
287
|
+
# provide default values and do any other setup.
|
288
|
+
# By default an option is available via `options[:foo]`.
|
289
|
+
def init_options(n: 10)
|
290
|
+
super # no specific assignments, so available via `options[:n]`.
|
291
|
+
end
|
292
|
+
|
293
|
+
def chars
|
294
|
+
@chars ||= (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a)
|
295
|
+
end
|
296
|
+
|
297
|
+
def resolve(**) # currently no keywords are passed, but `**` allows for future ones.
|
298
|
+
# Here we actually do the work: get the args, rotate strings and delegate to the block.
|
299
|
+
# `args` are the resolved nested args (so e.g. `!env MSG` is already resolved).
|
300
|
+
# `config` is the tag's config, and contains e.g. the block.
|
301
|
+
block = config.fetch(:block, :itself.to_proc)
|
302
|
+
# String#tr replaces any character from the first collection with the same position in the other:
|
303
|
+
args.join.tr(chars.join, chars.rotate(options[:n]).join).then(&block)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
```
|
194
307
|
|
195
308
|
## Development
|
196
309
|
|
data/lib/nero/railtie.rb
ADDED
data/lib/nero/version.rb
CHANGED
data/lib/nero.rb
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
require "zeitwerk"
|
4
4
|
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.do_not_eager_load("#{__dir__}/nero/railtie.rb")
|
5
6
|
loader.setup
|
6
7
|
|
7
|
-
require "uri"
|
8
|
+
require "uri"
|
8
9
|
require "yaml"
|
10
|
+
require "pathname"
|
9
11
|
|
10
12
|
# TODO fail on unknown tag
|
11
13
|
# TODO show missing env's at once
|
@@ -13,57 +15,105 @@ require "yaml"
|
|
13
15
|
module Nero
|
14
16
|
class Error < StandardError; end
|
15
17
|
|
18
|
+
module DigExt
|
19
|
+
# ⛏️💥 Like `dig`, but raises `ArgumentError` when `path` does not exist.
|
20
|
+
# @example like dig
|
21
|
+
# {a: {b: 2}}.dig!(:a, :b) #=> 2
|
22
|
+
# {a: {b: 2}}.dig!(:a, :c) #=> ArgumentError, path not found [:a, :c] (ArgumentError)
|
23
|
+
# @raise [ArgumentError] when `path` does not exist.
|
24
|
+
# @overload dig!(*path)
|
25
|
+
# @param path nested keys into config
|
26
|
+
def dig!(k0, *k)
|
27
|
+
k.unshift(k0)
|
28
|
+
|
29
|
+
unless paths.include?(k)
|
30
|
+
raise ArgumentError, "path not found #{k}"
|
31
|
+
end
|
32
|
+
dig(*k)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def paths
|
38
|
+
@paths ||= gather_paths(self).to_set
|
39
|
+
end
|
40
|
+
|
41
|
+
def gather_paths(item, acc: [], path: [])
|
42
|
+
acc += [path]
|
43
|
+
|
44
|
+
case item
|
45
|
+
when NilClass
|
46
|
+
[]
|
47
|
+
when Hash
|
48
|
+
item.flat_map { |(k, v)| gather_paths(v, acc: acc, path: path + [k]) }
|
49
|
+
when Array
|
50
|
+
item.each_with_index.flat_map do |item, ix|
|
51
|
+
gather_paths(item, acc: acc, path: path + [ix])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
acc
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Config < Hash
|
60
|
+
include DigExt
|
61
|
+
|
62
|
+
def self.for(v)
|
63
|
+
case v
|
64
|
+
when self then v
|
65
|
+
when Hash then self.[](v)
|
66
|
+
else
|
67
|
+
v
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
16
72
|
module Resolvable
|
17
|
-
def try_resolve(
|
73
|
+
def try_resolve(object)
|
18
74
|
if object.respond_to?(:resolve)
|
19
|
-
object.resolve
|
75
|
+
object.resolve
|
20
76
|
else
|
21
77
|
object
|
22
78
|
end
|
23
79
|
end
|
24
80
|
|
25
|
-
def
|
26
|
-
method(:try_resolve)
|
81
|
+
def deep_resolve(object)
|
82
|
+
Util.deep_transform_values(object, &method(:try_resolve))
|
27
83
|
end
|
28
84
|
|
29
|
-
def
|
30
|
-
|
85
|
+
def resolve_nested!(coder)
|
86
|
+
case coder.type
|
87
|
+
when :seq
|
88
|
+
coder.seq.map!(&method(:try_resolve))
|
89
|
+
when :map
|
90
|
+
coder.map = deep_resolve(coder.map)
|
91
|
+
end
|
31
92
|
end
|
32
93
|
end
|
33
94
|
extend Resolvable
|
34
95
|
private_class_method \
|
35
96
|
:deep_resolve,
|
36
|
-
:gen_resolve_tryer,
|
37
97
|
:try_resolve
|
38
98
|
|
39
|
-
class
|
40
|
-
|
41
|
-
|
42
|
-
def init_with(coder)
|
43
|
-
@coder = coder
|
44
|
-
end
|
99
|
+
class Configuration
|
100
|
+
attr_reader :config_dir
|
45
101
|
|
46
|
-
def
|
47
|
-
|
48
|
-
ctx[:tags][@coder.tag].call(@coder, ctx)
|
102
|
+
def tags
|
103
|
+
@tags ||= {}
|
49
104
|
end
|
50
105
|
|
51
|
-
def
|
52
|
-
|
53
|
-
when :seq
|
54
|
-
@coder.seq.map!(&gen_resolve_tryer(ctx))
|
55
|
-
when :map
|
56
|
-
@coder.map = deep_resolve(@coder.map, **ctx)
|
57
|
-
end
|
106
|
+
def config_dir=(dir)
|
107
|
+
@config_dir = Pathname(dir).expand_path
|
58
108
|
end
|
59
|
-
end
|
60
109
|
|
61
|
-
|
62
|
-
|
63
|
-
attr_accessor :config_dir
|
110
|
+
def add_tag(name, klass: BaseTag, &block)
|
111
|
+
klass, klass_options = klass
|
64
112
|
|
65
|
-
|
66
|
-
|
113
|
+
tags[name] = {klass:}.tap do |h|
|
114
|
+
h[:block] = block if block
|
115
|
+
h[:options] = klass_options if klass_options
|
116
|
+
end
|
67
117
|
end
|
68
118
|
end
|
69
119
|
|
@@ -71,96 +121,295 @@ module Nero
|
|
71
121
|
@configuration ||= Configuration.new
|
72
122
|
end
|
73
123
|
|
124
|
+
def self.add_tags!
|
125
|
+
configuration.tags.each do |tag_name, tag|
|
126
|
+
YAML.add_tag("!#{tag_name}", tag[:klass])
|
127
|
+
end
|
128
|
+
end
|
129
|
+
private_class_method :add_tags!
|
130
|
+
|
74
131
|
def self.configure
|
75
132
|
yield configuration if block_given?
|
133
|
+
ensure
|
134
|
+
add_tags!
|
76
135
|
end
|
77
136
|
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
137
|
+
# Superclass for all tags.
|
138
|
+
#
|
139
|
+
# Writing your own tag-class would look something like this:
|
140
|
+
#
|
141
|
+
# Wanted usage in YAML:
|
142
|
+
# ```ruby
|
143
|
+
# Nero.load(<<~YAML)
|
144
|
+
# secret: !rot/12 "some message"
|
145
|
+
# other_secret: !rot/13 [ !env [SECRET, some message] ]
|
146
|
+
# YAML
|
147
|
+
# ```
|
148
|
+
#
|
149
|
+
# Required config:
|
150
|
+
# ```ruby
|
151
|
+
# config.add_tag("rot/12", klass: RotTag[n: 12])
|
152
|
+
# config.add_tag("rot/13", klass: RotTag[n: 13]) do |secret|
|
153
|
+
# "#{secret} (try breaking this!)"
|
154
|
+
# end
|
155
|
+
# ```
|
156
|
+
# The class then would look like this:
|
157
|
+
# ```ruby
|
158
|
+
# class RotTag < Nero::BaseTag
|
159
|
+
# attr_reader :n
|
160
|
+
#
|
161
|
+
# # Overriding this method...:
|
162
|
+
# # - restricts options
|
163
|
+
# # ie `RotTag[x: 1]` would raise.
|
164
|
+
# # - sets default values
|
165
|
+
# # - makes options available via getters
|
166
|
+
# # (otherwise available via `options[:n]`).
|
167
|
+
# def init_options(n: 10)
|
168
|
+
# super
|
169
|
+
# @n = n
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# # This is where the magic happens.
|
173
|
+
# # (Accepting any keyword arguments keeps the method fw-compatible).
|
174
|
+
# def resolve(**)
|
175
|
+
# # `args` are the resolved arguments (Array or Hash).
|
176
|
+
# # `config` the config of the tag (containing e.g. the proc).
|
177
|
+
# block = config.fetch(:block, :itself.to_proc)
|
178
|
+
# args.join.tr(chars.join, chars.rotate(n).join).then(&block)
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# # Just some helper method with all characters that can be rotated.
|
182
|
+
# def chars
|
183
|
+
# %w(a b c) # etc
|
184
|
+
# end
|
82
185
|
# end
|
186
|
+
# ```
|
187
|
+
#
|
188
|
+
class BaseTag
|
189
|
+
include Resolvable
|
83
190
|
|
84
|
-
|
85
|
-
# extend TagHelpers
|
191
|
+
attr_reader :coder, :options, :ctx
|
86
192
|
|
87
|
-
|
88
|
-
|
89
|
-
|
193
|
+
# Convenience method simplifying {Nero::Configuration#add_tag}:
|
194
|
+
#
|
195
|
+
# ```ruby
|
196
|
+
# config.add_tag("foo", klass: SomeTag[some_option: 1])
|
197
|
+
# ```
|
198
|
+
def self.[](**options)
|
199
|
+
[self, options]
|
200
|
+
end
|
201
|
+
|
202
|
+
# @private used by YAML
|
203
|
+
def init_with(coder)
|
204
|
+
@coder = coder
|
205
|
+
end
|
90
206
|
|
91
|
-
|
92
|
-
|
207
|
+
def init(ctx:, options:)
|
208
|
+
init_ctx(ctx)
|
209
|
+
init_options(**options)
|
210
|
+
end
|
211
|
+
|
212
|
+
def init_ctx(ctx)
|
213
|
+
@ctx = ctx
|
214
|
+
end
|
215
|
+
|
216
|
+
def init_options(**options)
|
217
|
+
@options = options
|
218
|
+
end
|
219
|
+
|
220
|
+
def tag_name
|
221
|
+
coder.tag[1..]
|
222
|
+
end
|
223
|
+
|
224
|
+
def args
|
225
|
+
@args ||= begin
|
226
|
+
resolve_nested!(coder)
|
227
|
+
case coder.type
|
228
|
+
when :map then Util.deep_symbolize_keys(coder.map)
|
229
|
+
else
|
230
|
+
Array(coder.public_send(coder.type))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def config
|
236
|
+
ctx.dig(:tags, tag_name)
|
237
|
+
end
|
238
|
+
|
239
|
+
def resolve(**)
|
240
|
+
if (block = config[:block])
|
241
|
+
if block.parameters.map(&:last).include?(:coder)
|
242
|
+
# legacy
|
243
|
+
block.call(coder, ctx)
|
244
|
+
else
|
245
|
+
block.call(self)
|
246
|
+
end
|
247
|
+
else
|
248
|
+
args
|
93
249
|
end
|
250
|
+
end
|
251
|
+
end
|
94
252
|
|
95
|
-
|
96
|
-
|
253
|
+
# Requires an env-var to be available and coerces the value.
|
254
|
+
# When tag-name ends with "?", the env-var is optional.
|
255
|
+
#
|
256
|
+
# Given config:
|
257
|
+
# ```ruby
|
258
|
+
# config.add_tag("env/upcase", klass: Nero::EnvTag[coerce: :upcase])
|
259
|
+
# config.add_tag("env/upcase?", klass: Nero::EnvTag[coerce: :upcase])
|
260
|
+
# ```
|
261
|
+
#
|
262
|
+
# Then YAML => result:
|
263
|
+
# ```ruby
|
264
|
+
# "--- env/upcase [MSG, Hello World]" #=> "HELLO WORLD"
|
265
|
+
# "--- env/upcase MSG" #=> raises when not ENV.has_key? "MSG"
|
266
|
+
# "--- env/upcase? MSG" #=> nil
|
267
|
+
# ```
|
268
|
+
#
|
269
|
+
# YAML-args supported:
|
270
|
+
# - scalar —
|
271
|
+
# name of env-var, e.g. `!env HOME`
|
272
|
+
# - seq —
|
273
|
+
# name of env-var and fallback, e.g. `!env [HOME, /root]`
|
274
|
+
#
|
275
|
+
# Options:
|
276
|
+
# - `coerce` —
|
277
|
+
# symbol or proc to be applied to value of env-var.
|
278
|
+
# when using coerce, the block is ignored.
|
279
|
+
#
|
280
|
+
class EnvTag < BaseTag
|
281
|
+
def resolve(**)
|
282
|
+
if coercer
|
283
|
+
coercer.call(env_value) unless env_value.nil?
|
284
|
+
elsif ctx.dig(:tags, tag_name, :block)
|
285
|
+
super
|
286
|
+
else
|
287
|
+
env_value
|
97
288
|
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def coercer
|
292
|
+
return unless @coerce
|
98
293
|
|
99
|
-
|
100
|
-
|
294
|
+
@coercer ||= case @coerce
|
295
|
+
when Symbol then @coerce.to_proc
|
296
|
+
else
|
297
|
+
@coerce
|
101
298
|
end
|
299
|
+
end
|
102
300
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
when re_false then false
|
112
|
-
else
|
113
|
-
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
|
114
|
-
end
|
115
|
-
end
|
301
|
+
def init_options(coerce: nil)
|
302
|
+
@coerce = coerce
|
303
|
+
end
|
304
|
+
|
305
|
+
def optional
|
306
|
+
tag_name.end_with?("?") || !!ENV["NERO_ENV_ALL_OPTIONAL"]
|
307
|
+
end
|
308
|
+
alias_method :optional?, :optional
|
116
309
|
|
117
|
-
|
310
|
+
def env_value
|
311
|
+
self.class.env_value(*args, optional:)
|
312
|
+
end
|
313
|
+
|
314
|
+
def self.env_value(k, fallback = nil, optional: false)
|
315
|
+
if fallback.nil? && !optional
|
316
|
+
ENV.fetch(k)
|
317
|
+
else
|
318
|
+
ENV.fetch(k, fallback)
|
118
319
|
end
|
320
|
+
end
|
119
321
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
case s
|
126
|
-
when TrueClass, FalseClass then s
|
127
|
-
when re_true then true
|
128
|
-
when re_false then false
|
129
|
-
else
|
130
|
-
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
|
131
|
-
end
|
132
|
-
end
|
322
|
+
def self.coerce_bool(v)
|
323
|
+
return false unless v
|
324
|
+
|
325
|
+
re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
|
326
|
+
re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
|
133
327
|
|
134
|
-
|
328
|
+
case v
|
329
|
+
when TrueClass, FalseClass then v
|
330
|
+
when re_true then true
|
331
|
+
when re_false then false
|
332
|
+
else
|
333
|
+
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{v.inspect})"
|
135
334
|
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# Construct path relative to some root-path.
|
339
|
+
# Root-paths are expected to be ancestors of the yaml-file being parsed.
|
340
|
+
# They are found by traversing up and checking for specific files/folders, e.g. '.git' or 'Gemfile'.
|
341
|
+
# Any argument is appended to the root-path, constructing a path-instance that may exist.
|
342
|
+
class PathRootTag < BaseTag
|
343
|
+
# Config:
|
344
|
+
# config.add_tag("path/git_root", klass: PathRootTag[containing: ".git"])
|
345
|
+
# config.add_tag("path/rails_root", klass: PathRootTag[containing: "Gemfile"])
|
346
|
+
#
|
347
|
+
# YAML:
|
348
|
+
# project_root: !path/git_root
|
349
|
+
# config_path: !path/git_root [ config ]
|
350
|
+
def init_options(containing:)
|
351
|
+
super
|
352
|
+
end
|
353
|
+
|
354
|
+
def resolve(**)
|
355
|
+
# TODO validate upfront
|
356
|
+
raise <<~ERR unless root_path
|
357
|
+
#{tag_name}: failed to find root-path (ie an ancestor of #{ctx[:yaml_file]} containing #{options[:containing].inspect}).
|
358
|
+
ERR
|
359
|
+
root_path.join(*args).then(&config.fetch(:block, :itself.to_proc))
|
360
|
+
end
|
361
|
+
|
362
|
+
def root_path
|
363
|
+
find_up(ctx[:yaml_file], options[:containing])
|
364
|
+
end
|
365
|
+
|
366
|
+
def find_up(path, containing)
|
367
|
+
(path = path.parent) until path.root? || (path / containing).exist?
|
368
|
+
path unless path.root?
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def self.add_default_tags!
|
373
|
+
configure do |config|
|
374
|
+
config.add_tag("ref") do |tag|
|
375
|
+
# validate: non-empty coder.seq, only strs, path must exists in ctx[:config]
|
136
376
|
|
137
|
-
|
138
|
-
|
377
|
+
path = tag.args.map(&:to_sym)
|
378
|
+
deep_resolve(tag.ctx[:yaml].dig(*path))
|
139
379
|
end
|
140
380
|
|
141
|
-
config.add_tag("env
|
142
|
-
|
143
|
-
|
381
|
+
config.add_tag("env", klass: EnvTag)
|
382
|
+
config.add_tag("env?", klass: EnvTag)
|
383
|
+
config.add_tag("env/float", klass: EnvTag[coerce: :to_f])
|
384
|
+
config.add_tag("env/float?", klass: EnvTag[coerce: :to_f])
|
385
|
+
|
386
|
+
config.add_tag("env/integer", klass: EnvTag[coerce: :to_i])
|
387
|
+
config.add_tag("env/integer?", klass: EnvTag[coerce: :to_i])
|
388
|
+
|
389
|
+
config.add_tag("env/bool", klass: EnvTag) do |tag|
|
390
|
+
EnvTag.coerce_bool(tag.env_value)
|
391
|
+
end
|
392
|
+
config.add_tag("env/bool?", klass: EnvTag) do |tag|
|
393
|
+
EnvTag.coerce_bool(tag.env_value)
|
144
394
|
end
|
145
395
|
|
146
|
-
config.add_tag("path") do |
|
147
|
-
Pathname.new(
|
396
|
+
config.add_tag("path") do |tag|
|
397
|
+
Pathname.new(tag.args.join("/"))
|
148
398
|
end
|
399
|
+
config.add_tag("path/git_root", klass: PathRootTag[containing: ".git"])
|
400
|
+
config.add_tag("path/rails_root", klass: PathRootTag[containing: "config.ru"])
|
149
401
|
|
150
|
-
config.add_tag("uri") do |
|
151
|
-
URI(
|
402
|
+
config.add_tag("uri") do |tag|
|
403
|
+
URI.join(*tag.args.join)
|
152
404
|
end
|
153
405
|
|
154
|
-
config.add_tag("str/format") do |
|
155
|
-
case
|
156
|
-
when
|
157
|
-
|
158
|
-
|
159
|
-
m = Util.deep_symbolize_keys(coder.map)
|
160
|
-
fmt = m.delete(:fmt)
|
161
|
-
sprintf(fmt, m)
|
406
|
+
config.add_tag("str/format") do |tag|
|
407
|
+
case tag.args
|
408
|
+
when Hash
|
409
|
+
fmt = tag.args.delete(:fmt)
|
410
|
+
sprintf(fmt, tag.args)
|
162
411
|
else
|
163
|
-
|
412
|
+
sprintf(*tag.args)
|
164
413
|
end
|
165
414
|
end
|
166
415
|
end
|
@@ -171,64 +420,128 @@ module Nero
|
|
171
420
|
@configuration = nil
|
172
421
|
|
173
422
|
configure do |config|
|
174
|
-
config.config_dir = Pathname.
|
423
|
+
config.config_dir = Pathname.new("config").expand_path
|
175
424
|
end
|
176
425
|
|
177
426
|
add_default_tags!
|
427
|
+
add_tags!
|
178
428
|
end
|
179
429
|
reset_configuration!
|
180
430
|
|
181
|
-
def self.
|
182
|
-
|
431
|
+
def self.default_yaml_options
|
432
|
+
{
|
433
|
+
permitted_classes: [Symbol] + configuration.tags.values.map { _1[:klass] },
|
434
|
+
aliases: true
|
435
|
+
}
|
436
|
+
end
|
437
|
+
private_class_method :default_yaml_options
|
438
|
+
|
439
|
+
def self.yaml_options(yaml_options)
|
440
|
+
epc = yaml_options.delete(:extra_permitted_classes)
|
441
|
+
default_yaml_options.merge(yaml_options).tap do
|
442
|
+
_1[:permitted_classes].push(*epc)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
private_class_method :yaml_options
|
446
|
+
|
447
|
+
# Like `YAML.load` with extra options.
|
448
|
+
#
|
449
|
+
# @param [Symbol, String] root return the value of this root key.
|
450
|
+
# @param [Boolean] resolve (for debug purposes) not resolving would leave the Nero-tags as-is.
|
451
|
+
# @param [Array<ClassName>] extra_permitted_classes classes that are added
|
452
|
+
# to the default permitted_classes and passed to `YAML.load`.
|
453
|
+
# @param [Hash] yaml_options options passed to `YAML.load`.
|
454
|
+
# @return [Nero::Config (when the data is a Hash)]
|
455
|
+
# @example
|
456
|
+
# Nero.load(<<~YAML, extra_permitted_classes: [Time])
|
457
|
+
# home: !env HOME,
|
458
|
+
# created_at: 2010-02-11 11:02:57
|
459
|
+
# project_root: !path/git_root
|
460
|
+
# YAML
|
461
|
+
# #=> {
|
462
|
+
# # home: "/Users/gert",
|
463
|
+
# # created_at: 2010-02-11 12:02:57 +0100,
|
464
|
+
# # project_root: #<Pathname:/Users/gert/projects/nero>
|
465
|
+
# # }
|
466
|
+
def self.load(yaml, root: nil, resolve: true, **yaml_options)
|
467
|
+
process_yaml(yaml_load(yaml, yaml_options(yaml_options)), root:, resolve:)
|
468
|
+
end
|
183
469
|
|
184
|
-
|
470
|
+
# Like `YAML.load_file`. See {load} for options.
|
471
|
+
# @return [Nero::Config (when the YAML-data is a Hash)]
|
472
|
+
def self.load_file(file, root: nil, resolve: true, **yaml_options)
|
473
|
+
config_file = (file.is_a?(Pathname) ? file : Pathname.new(file)).expand_path
|
474
|
+
process_yaml(yaml_load_file(config_file, yaml_options(yaml_options)), root:, config_file:, resolve:)
|
185
475
|
end
|
186
|
-
private_class_method :env_fetch
|
187
476
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
}
|
477
|
+
# Convenience wrapper for {load_file} that works like `Rails.application.config_for`.
|
478
|
+
# @see https://api.rubyonrails.org/classes/Rails/Application.html#method-i-config_for Rails' config_for documentation
|
479
|
+
#
|
480
|
+
# The file-argument is expanded like so `(configuration.config_dir / "#{file}.yml").expand_path`.
|
481
|
+
#
|
482
|
+
# @param [Symbol, String, Pathname] file `Symbol` or `String` are expanded as shown above. A `Pathname` is used as-is.
|
483
|
+
# @param [Symbol, String] env return the value of this root key.
|
484
|
+
# @param [Symbol, String] root return the value of this root key.
|
485
|
+
# @param [Boolean] resolve (for debug purposes) not resolving would leave the Nero-tags as-is.
|
486
|
+
# @param [Array<ClassName>] extra_permitted_classes classes that are added
|
487
|
+
# to the default permitted_classes and passed to `YAML.load`.
|
488
|
+
# @param [Hash] yaml_options options passed to `YAML.load_file`.
|
489
|
+
# @return [Nero::Config (when the data is a Hash)]
|
490
|
+
# @example
|
491
|
+
# Nero.config_for(:app, env: Rails.env) #=> {...}
|
492
|
+
def self.config_for(file, root: nil, env: nil, **yaml_options)
|
493
|
+
root ||= env
|
494
|
+
|
495
|
+
load_file(resolve_file(file), root:, **yaml_options)
|
496
|
+
end
|
192
497
|
|
193
|
-
|
498
|
+
# @deprecated Use `load_file` or `config_for` instead.
|
499
|
+
def self.load_config(file, root: nil, env: nil, resolve: true)
|
500
|
+
warn "[DEPRECATION] `load_config` is deprecated. Use `load_file` or `config_for` instead."
|
194
501
|
root ||= env
|
195
502
|
add_tags!
|
196
503
|
|
197
|
-
|
504
|
+
config_file = resolve_file(file)
|
198
505
|
|
199
|
-
if
|
200
|
-
process_yaml(yaml_load_file(
|
506
|
+
if config_file.exist?
|
507
|
+
process_yaml(yaml_load_file(config_file, yaml_options), root:, config_file:, resolve:)
|
201
508
|
else
|
202
|
-
raise "Can't find file #{
|
509
|
+
raise "Can't find file #{config_file}"
|
203
510
|
end
|
204
511
|
end
|
205
512
|
|
206
513
|
def self.resolve_file(file)
|
207
514
|
case file
|
208
515
|
when Pathname then file
|
209
|
-
# TODO expand full path
|
210
516
|
else
|
211
|
-
configuration.config_dir / "#{file}.yml"
|
517
|
+
(configuration.config_dir / "#{file}.yml").expand_path
|
212
518
|
end
|
213
519
|
end
|
214
520
|
private_class_method :resolve_file
|
215
521
|
|
216
|
-
def self.
|
217
|
-
|
218
|
-
add_tags!
|
219
|
-
|
220
|
-
process_yaml(yaml_load(raw, @yaml_options), root:)
|
221
|
-
end
|
522
|
+
def self.process_yaml(yaml, root: nil, resolve: true, config_file: nil)
|
523
|
+
config_file ||= (Pathname.pwd / __FILE__)
|
222
524
|
|
223
|
-
def self.process_yaml(yaml, root: nil)
|
224
525
|
unresolved = Util.deep_symbolize_keys(yaml).then do
|
225
526
|
root ? _1[root.to_sym] : _1
|
226
527
|
end
|
528
|
+
ctx = {tags: configuration.tags, yaml: unresolved, yaml_file: config_file}
|
529
|
+
init_tags!(collect_tags(unresolved), ctx:)
|
227
530
|
|
228
|
-
|
531
|
+
return unresolved unless resolve
|
532
|
+
|
533
|
+
Config.for(deep_resolve(unresolved))
|
229
534
|
end
|
230
535
|
private_class_method :process_yaml
|
231
536
|
|
537
|
+
def self.init_tags!(tags, ctx:)
|
538
|
+
tags.each do |tag|
|
539
|
+
options = ctx.dig(:tags, tag.tag_name, :options) || {}
|
540
|
+
tag.init(ctx:, options:)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
private_class_method :init_tags!
|
544
|
+
|
232
545
|
def self.yaml_load_file(file, opts = {})
|
233
546
|
if Psych::VERSION < "4"
|
234
547
|
YAML.load_file(file)
|
@@ -247,12 +560,29 @@ module Nero
|
|
247
560
|
end
|
248
561
|
private_class_method :yaml_load
|
249
562
|
|
250
|
-
def self.
|
251
|
-
|
252
|
-
|
563
|
+
def self.collect_tags(obj)
|
564
|
+
case obj
|
565
|
+
when Hash
|
566
|
+
obj.each_value.flat_map { collect_tags(_1) }.compact
|
567
|
+
when Nero::BaseTag
|
568
|
+
[obj] +
|
569
|
+
case obj.coder.type
|
570
|
+
when :seq
|
571
|
+
collect_tags(obj.coder.seq)
|
572
|
+
when :map
|
573
|
+
collect_tags(obj.coder.map)
|
574
|
+
else
|
575
|
+
[]
|
576
|
+
end
|
577
|
+
when Array
|
578
|
+
obj.flat_map { collect_tags(_1) }.compact
|
579
|
+
else
|
580
|
+
[]
|
253
581
|
end
|
254
582
|
end
|
255
|
-
private_class_method :
|
583
|
+
private_class_method :collect_tags
|
256
584
|
end
|
257
585
|
|
586
|
+
require "nero/railtie" if defined?(Rails::Railtie)
|
587
|
+
|
258
588
|
loader.eager_load if ENV.key?("CI")
|
data/rakelib/yard.rake
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "yard"
|
2
|
+
|
3
|
+
YARD::Rake::YardocTask.new(:docs) do |t|
|
4
|
+
# Options defined in `.yardopts` are read first, then merged with
|
5
|
+
# options defined here.
|
6
|
+
#
|
7
|
+
# It's recommended to define options in `.yardopts` instead of here,
|
8
|
+
# as `.yardopts` can be read by external YARD tools, like the
|
9
|
+
# hot-reload YARD server `yard server --reload`.
|
10
|
+
|
11
|
+
# t.options += ['--title', "Something custom"]
|
12
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gert Goet
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-10 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: zeitwerk
|
@@ -53,6 +53,7 @@ files:
|
|
53
53
|
- ".envrc"
|
54
54
|
- ".rspec"
|
55
55
|
- ".standard.yml"
|
56
|
+
- ".yardopts"
|
56
57
|
- Appraisals
|
57
58
|
- CHANGELOG.md
|
58
59
|
- LICENSE.txt
|
@@ -61,9 +62,11 @@ files:
|
|
61
62
|
- gemfiles/psych_3.gemfile
|
62
63
|
- gemfiles/psych_4.gemfile
|
63
64
|
- lib/nero.rb
|
65
|
+
- lib/nero/railtie.rb
|
64
66
|
- lib/nero/util.rb
|
65
67
|
- lib/nero/version.rb
|
66
68
|
- rakelib/gem.rake
|
69
|
+
- rakelib/yard.rake
|
67
70
|
- sig/nero.rbs
|
68
71
|
homepage: https://github.com/eval/nero
|
69
72
|
licenses:
|