nero 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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