nero 0.2.1 → 0.3.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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +18 -16
  4. data/lib/nero/version.rb +1 -1
  5. data/lib/nero.rb +128 -68
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aefcd9c69dd9c20501632b97f8738fa7bbe95115f694c086c89b20da5b55359a
4
- data.tar.gz: 63c7d94edc645ad1bf4912f7f7ef4256aec15b6ee063ffc568ab322c2a8299c0
3
+ metadata.gz: 3da331b2512fc63cf1a9258991a33dda876e8b0aed2445bca34ed207f8cfc1fb
4
+ data.tar.gz: 79c6afff69d21841b247de206f7b3f6482bf74496ad91c4986528fd0620e6551
5
5
  SHA512:
6
- metadata.gz: 9911e7bf5881fe44a2524adc67e875ff1b44318306cd00661a52d698fe12e87ac0d4bfaf91e41ad0122d531ffa7f1426ce86a1d81e72e36baac9088f093d185e
7
- data.tar.gz: c25d5bf01e86ace03358cd1a829e784cc4479910601cb69ba9eebe5c8e71c176ebb17f53e319a6e710d575c14f6dc862af156e46d86cfd58b6591d1e6b0f6116
6
+ metadata.gz: e80cb4098d709df1231e7ee1cfc077be097517709ffabee489cdce8bc9da30aac7e6f89157823c147e691042945c9da806f337f1ca64ab90dc376d390f46d0b9
7
+ data.tar.gz: 7637f0ae5983c3902a04092c2d1cf3ed9aacb0ae036f73d7c1067f7677a8c175ffe4c4826c972cb129c47cfe37517115e0bd51f81c1f6b4f132a8946b4311a3e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-02-02
4
+
5
+ - Add configuration
6
+ For custom tags:
7
+ ```ruby
8
+ Nero.configure do |nero|
9
+ nero.add_tag("duration") do |coder|
10
+ num, duration = coder.seq
11
+ mult = case duration
12
+ when /^seconds?/ then 1
13
+ when /^minutes?$/ then 60
14
+ when /^hours?$/ then 60 *60
15
+ when /^days?$/ then 24 * 60 * 60
16
+ else
17
+ raise ArgumentError, "Unknown duration #{coder.seq.inspect}"
18
+ end
19
+ num * mult
20
+ end
21
+ end
22
+ ```
23
+ ...and config_dir:
24
+ ```ruby
25
+ Nero.configure {|nero| nero.config_dir = Rails.root / "config" }
26
+ ```
27
+ - Allow for a `Rails.application.config_for` like experience
28
+ ```ruby
29
+ Nero.configure {|nero| nero.config_dir = Rails.root / "config" }
30
+
31
+ Nero.load_config(:stripe, root: Rails.env)
32
+ # Returns content of Rails.root / "config/stripe.yml"
33
+ ```
34
+ - Add `Nero.load` like `YAML.load`
35
+ ```ruby
36
+ Nero.load(<<~YAML)
37
+ cache_ttl: !duration [1, day]
38
+ end
39
+ # => {cache_ttl: 86400}
40
+ ```
41
+
3
42
  ## [0.1.0] - 2025-01-24
4
43
 
5
44
  - Initial release
data/README.md CHANGED
@@ -4,32 +4,28 @@
4
4
 
5
5
  Nero is a RubyGem that offers predefined tags and allows you to effortlessly create custom ones for YAML configuration files.
6
6
 
7
- E.g. instead of having the following settings file in your Ruby/Rails project:
7
+ E.g. instead of having the following settings file in your Rails project:
8
8
 
9
9
  ```yaml
10
10
  development:
11
- # env-var with a fallback
12
11
  secret: <%= ENV.fetch("SECRET", "dummy") %>
13
- # NOTE *any* value provided is taken as `true`
14
- debug?: <%= !!ENV["DEBUG"] %>
12
+ # custom logic how to get a boolean...
13
+ debug?: <%= ENV["DEBUG"] == "true" %>
15
14
  production:
16
- # NOTE we can't fail-fast on ENV-var absence (i.e. use `ENV.fetch`),
17
- # as it would require the env-var for development as well
15
+ # any ENV.fetch in this section would hamper local development...
18
16
  secret: <%= ENV["SECRET"] %>
17
+ # custom coercion logic
19
18
  max_threads: <%= ENV.fetch("MAX_THREADS", 5).to_i %>
20
19
  ```
21
20
 
22
21
  ...turn it into this:
23
22
  ```yaml
24
23
  development:
25
- # env-var with a fallback
26
24
  secret: !env [SECRET, "dummy"]
27
- # Though the default is false, explicitly providing "false"/"off"/"n"/"no" is also possible.
28
25
  debug?: !env/bool? DEBUG
29
26
  production:
30
- # fail-fast on absence of SECRET
27
+ # required _only_ when loading production
31
28
  secret: !env SECRET
32
- # always an integer
33
29
  max_threads: !env/integer [MAX_THREADS, 5]
34
30
  ```
35
31
 
@@ -52,7 +48,7 @@ Given the following config:
52
48
  development:
53
49
  # env-var with a fallback
54
50
  secret: !env [SECRET, "dummy"]
55
- # Though the default is false, explicitly providing "false"/"off"/"n"/"no" is also possible.
51
+ # Though the default is false, explicitly providing "false"/"off"/"n"/"no" also works.
56
52
  debug?: !env/bool? DEBUG
57
53
  production:
58
54
  # fail-fast on absence of SECRET
@@ -65,7 +61,7 @@ Loading this config:
65
61
 
66
62
  ```ruby
67
63
  # Loading development
68
- Nero.load_config(Pathname.pwd / "config/settings.yml", root: :development)
64
+ Nero.load_config("config/settings", root: :development)
69
65
  # ...and no ENV-vars were provided
70
66
  #=> {secret: "dummy", debug?: false}
71
67
 
@@ -73,7 +69,7 @@ Nero.load_config(Pathname.pwd / "config/settings.yml", root: :development)
73
69
  #=> {secret: "dummy", debug?: true}
74
70
 
75
71
  # Loading production
76
- Nero.load_config(Pathname.pwd / "config/settings.yml", root: :production)
72
+ Nero.load_config("config/settings", root: :production)
77
73
  # ...and no ENV-vars were provided
78
74
  # raises error: key not found: "SECRET" (KeyError)
79
75
 
@@ -139,10 +135,10 @@ The following tags are provided:
139
135
  pass: !env SMTP_PASS
140
136
  ```
141
137
 
142
- TBD Add one yourself:
138
+ Add one yourself:
143
139
  ```ruby
144
- Nero.configure do
145
- add_tag("foo") do |coder|
140
+ Nero.configure do |nero|
141
+ nero.add_tag("foo") do |coder|
146
142
  # coder.type is one of :scalar, :seq or :map
147
143
  # e.g. respective YAML:
148
144
  # ---
@@ -157,6 +153,12 @@ Nero.configure do
157
153
  # Find the value in the respective attribute, e.g. `coder.scalar`:
158
154
  coder.scalar.upcase
159
155
  end
156
+
157
+ # Other configuration options:
158
+ #
159
+ # `config_dir` (default: Pathname.pwd) - path used for expanding non-Pathnames passed to `load_config`, e.g.
160
+ # `Nero.load_config(:app)` loads file `Pathname.pwd / "app.yml"`.
161
+ nero.config_dir = Rails.root / "config"
160
162
  end
161
163
  ```
162
164
 
data/lib/nero/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Nero
4
4
  # NOTE this is written upon release via:
5
5
  # $ rake gem:build[version=0.3.0]
6
- VERSION = "0.2.1"
6
+ VERSION = "0.3.0"
7
7
  end
data/lib/nero.rb CHANGED
@@ -5,6 +5,7 @@ loader = Zeitwerk::Loader.for_gem
5
5
  loader.setup
6
6
 
7
7
  require "uri" # why needed?
8
+ require "yaml"
8
9
 
9
10
  # TODO fail on unknown tag
10
11
  # TODO show missing env's at once
@@ -30,7 +31,10 @@ module Nero
30
31
  end
31
32
  end
32
33
  extend Resolvable
33
- private_class_method :try_resolve, :gen_resolve_tryer, :deep_resolve
34
+ private_class_method \
35
+ :deep_resolve,
36
+ :gen_resolve_tryer,
37
+ :try_resolve
34
38
 
35
39
  class TagResolver
36
40
  include Resolvable
@@ -41,7 +45,7 @@ module Nero
41
45
 
42
46
  def resolve(ctx)
43
47
  resolve_nested!(ctx)
44
- ctx[:resolvers][@coder.tag].call(@coder)
48
+ ctx[:tags][@coder.tag].call(@coder)
45
49
  end
46
50
 
47
51
  def resolve_nested!(ctx)
@@ -54,106 +58,162 @@ module Nero
54
58
  end
55
59
  end
56
60
 
57
- def self.add_resolver(name, &block)
58
- (@resolvers ||= {})["!#{name}"] = block
59
- end
60
-
61
- def self.env_fetch(k, fallback = nil, all_optional: "dummy")
62
- fallback ||= all_optional if ENV["NERO_ENV_ALL_OPTIONAL"]
61
+ class Configuration
62
+ attr_reader :tags
63
+ attr_accessor :config_dir
63
64
 
64
- fallback.nil? ? ENV.fetch(k) : ENV.fetch(k, fallback)
65
+ def add_tag(name, &block)
66
+ (@tags ||= {})["!#{name}"] = block
67
+ end
65
68
  end
66
- private_class_method :env_fetch
67
69
 
68
- add_resolver("env/integer") do |coder|
69
- Integer(env_fetch(*(coder.scalar || coder.seq), all_optional: "999"))
70
+ def self.configuration
71
+ @configuration ||= Configuration.new
70
72
  end
71
73
 
72
- add_resolver("env/integer?") do |coder|
73
- Integer(ENV[coder.scalar]) if ENV[coder.scalar]
74
+ def self.configure
75
+ yield configuration if block_given?
74
76
  end
75
77
 
76
- add_resolver("env/bool") do |coder|
77
- re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
78
- re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
78
+ def self.add_default_tags!
79
+ configure do |config|
80
+ config.add_tag("env/integer") do |coder|
81
+ Integer(env_fetch(*(coder.scalar || coder.seq), all_optional: "999"))
82
+ end
79
83
 
80
- coerce = ->(s) do
81
- case s
82
- when TrueClass, FalseClass then s
83
- when re_true then true
84
- when re_false then false
85
- else
86
- raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
84
+ config.add_tag("env/integer?") do |coder|
85
+ Integer(ENV[coder.scalar]) if ENV[coder.scalar]
87
86
  end
88
- end
89
87
 
90
- coerce[env_fetch(*(coder.scalar || coder.seq), all_optional: "false")]
91
- end
88
+ config.add_tag("env/bool") do |coder|
89
+ re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
90
+ re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
91
+
92
+ coerce = ->(s) do
93
+ case s
94
+ when TrueClass, FalseClass then s
95
+ when re_true then true
96
+ when re_false then false
97
+ else
98
+ raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
99
+ end
100
+ end
101
+
102
+ coerce[env_fetch(*(coder.scalar || coder.seq), all_optional: "false")]
103
+ end
92
104
 
93
- add_resolver("env/bool?") do |coder|
94
- re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
95
- re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
105
+ config.add_tag("env/bool?") do |coder|
106
+ re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
107
+ re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
108
+
109
+ coerce = ->(s) do
110
+ case s
111
+ when TrueClass, FalseClass then s
112
+ when re_true then true
113
+ when re_false then false
114
+ else
115
+ raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
116
+ end
117
+ end
118
+
119
+ ENV[coder.scalar] ? coerce[ENV[coder.scalar]] : false
120
+ end
96
121
 
97
- coerce = ->(s) do
98
- case s
99
- when TrueClass, FalseClass then s
100
- when re_true then true
101
- when re_false then false
102
- else
103
- raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
122
+ config.add_tag("env") do |coder|
123
+ env_fetch(*(coder.scalar || coder.seq))
104
124
  end
105
- end
106
125
 
107
- ENV[coder.scalar] ? coerce[ENV[coder.scalar]] : false
108
- end
126
+ config.add_tag("env?") do |coder|
127
+ fetch_args = coder.scalar ? [coder.scalar, nil] : coder.seq
128
+ ENV.fetch(*fetch_args)
129
+ end
130
+
131
+ config.add_tag("path") do |coder|
132
+ Pathname.new(coder.scalar || coder.seq.join("/"))
133
+ end
134
+
135
+ config.add_tag("uri") do |coder|
136
+ URI(coder.scalar || coder.seq.join)
137
+ end
109
138
 
110
- add_resolver("env") do |coder|
111
- env_fetch(*(coder.scalar || coder.seq))
139
+ config.add_tag("str/format") do |coder|
140
+ case coder.type
141
+ when :seq
142
+ sprintf(*coder.seq)
143
+ when :map
144
+ m = Util.deep_symbolize_keys(coder.map)
145
+ fmt = m.delete(:fmt)
146
+ sprintf(fmt, m)
147
+ else
148
+ coder.scalar
149
+ end
150
+ end
151
+ end
112
152
  end
153
+ private_class_method :add_default_tags!
154
+
155
+ def self.reset_configuration!
156
+ @configuration = nil
113
157
 
114
- add_resolver("env?") do |coder|
115
- fetch_args = coder.scalar ? [coder.scalar, nil] : coder.seq
116
- ENV.fetch(*fetch_args)
158
+ configure do |config|
159
+ config.config_dir = Pathname.pwd
160
+ end
161
+
162
+ add_default_tags!
117
163
  end
164
+ reset_configuration!
165
+
166
+ def self.env_fetch(k, fallback = nil, all_optional: "dummy")
167
+ fallback ||= all_optional if ENV["NERO_ENV_ALL_OPTIONAL"]
118
168
 
119
- add_resolver("path") do |coder|
120
- Pathname.new(coder.scalar || coder.seq.join("/"))
169
+ fallback.nil? ? ENV.fetch(k) : ENV.fetch(k, fallback)
121
170
  end
171
+ private_class_method :env_fetch
172
+
173
+ @yaml_options = {
174
+ permitted_classes: [Symbol, TagResolver],
175
+ aliases: true
176
+ }
177
+
178
+ def self.load_config(file, root: nil)
179
+ add_tags!
180
+
181
+ file = resolve_file(file)
122
182
 
123
- add_resolver("uri") do |coder|
124
- URI(coder.scalar || coder.seq.join)
183
+ if file.exist?
184
+ process_yaml(YAML.load_file(file, **@yaml_options), root:)
185
+ else
186
+ raise "Can't find file #{file}"
187
+ end
125
188
  end
126
189
 
127
- add_resolver("str/format") do |coder|
128
- case coder.type
129
- when :seq
130
- sprintf(*coder.seq)
131
- when :map
132
- m = Util.deep_symbolize_keys(coder.map)
133
- fmt = m.delete(:fmt)
134
- sprintf(fmt, m)
190
+ def self.resolve_file(file)
191
+ case file
192
+ when Pathname then file
193
+ # TODO expand full path
135
194
  else
136
- coder.scalar
195
+ configuration.config_dir / "#{file}.yml"
137
196
  end
138
197
  end
198
+ private_class_method :resolve_file
139
199
 
140
- def self.load_config(file, root: nil)
200
+ def self.load(raw, root: nil)
141
201
  add_tags!
142
202
 
143
- if file.exist?
144
- unresolved = Util.deep_symbolize_keys(YAML.load_file(file,
145
- permitted_classes: [Symbol, TagResolver], aliases: true)).then do
146
- root ? _1[root.to_sym] : _1
147
- end
203
+ process_yaml(YAML.load(raw, **@yaml_options), root:)
204
+ end
148
205
 
149
- deep_resolve(unresolved, resolvers: @resolvers)
150
- else
151
- raise "Can't find file #{file}"
206
+ def self.process_yaml(yaml, root: nil)
207
+ unresolved = Util.deep_symbolize_keys(yaml).then do
208
+ root ? _1[root.to_sym] : _1
152
209
  end
210
+
211
+ deep_resolve(unresolved, tags: configuration.tags)
153
212
  end
213
+ private_class_method :process_yaml
154
214
 
155
215
  def self.add_tags!
156
- @resolvers.keys.each do
216
+ configuration.tags.keys.each do
157
217
  YAML.add_tag(_1, TagResolver)
158
218
  end
159
219
  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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gert Goet
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-01-29 00:00:00.000000000 Z
10
+ date: 2025-02-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk