Skip to content

Commit

Permalink
Allow for configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
eval committed Feb 2, 2025
1 parent 0472874 commit 69d6b19
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 70 deletions.
172 changes: 108 additions & 64 deletions lib/nero.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ def deep_resolve(object, **ctx)
end
end
extend Resolvable
private_class_method :try_resolve, :gen_resolve_tryer, :deep_resolve
private_class_method \
:deep_resolve,
:gen_resolve_tryer,
:try_resolve

class TagResolver
include Resolvable
Expand All @@ -42,7 +45,7 @@ def init_with(coder)

def resolve(ctx)
resolve_nested!(ctx)
ctx[:resolvers][@coder.tag].call(@coder)
ctx[:tags][@coder.tag].call(@coder)
end

def resolve_nested!(ctx)
Expand All @@ -55,88 +58,117 @@ def resolve_nested!(ctx)
end
end

def self.add_resolver(name, &block)
(@resolvers ||= {})["!#{name}"] = block
end
class Configuration
attr_reader :tags
attr_accessor :config_dir

def self.env_fetch(k, fallback = nil, all_optional: "dummy")
fallback ||= all_optional if ENV["NERO_ENV_ALL_OPTIONAL"]

fallback.nil? ? ENV.fetch(k) : ENV.fetch(k, fallback)
def add_tag(name, &block)
(@tags ||= {})["!#{name}"] = block
end
end
private_class_method :env_fetch

add_resolver("env/integer") do |coder|
Integer(env_fetch(*(coder.scalar || coder.seq), all_optional: "999"))
def self.configuration
@configuration ||= Configuration.new
end

add_resolver("env/integer?") do |coder|
Integer(ENV[coder.scalar]) if ENV[coder.scalar]
def self.configure
yield configuration if block_given?
end

add_resolver("env/bool") do |coder|
re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
def self.add_default_tags!
configure do |config|
config.add_tag("env/integer") do |coder|
Integer(env_fetch(*(coder.scalar || coder.seq), all_optional: "999"))
end

coerce = ->(s) do
case s
when TrueClass, FalseClass then s
when re_true then true
when re_false then false
else
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
config.add_tag("env/integer?") do |coder|
Integer(ENV[coder.scalar]) if ENV[coder.scalar]
end
end

coerce[env_fetch(*(coder.scalar || coder.seq), all_optional: "false")]
end
config.add_tag("env/bool") do |coder|
re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/

coerce = ->(s) do
case s
when TrueClass, FalseClass then s
when re_true then true
when re_false then false
else
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
end
end

coerce[env_fetch(*(coder.scalar || coder.seq), all_optional: "false")]
end

add_resolver("env/bool?") do |coder|
re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/
config.add_tag("env/bool?") do |coder|
re_true = /y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON/
re_false = /n|N|no|No|NO|false|False|FALSE|off|Off|OFF/

coerce = ->(s) do
case s
when TrueClass, FalseClass then s
when re_true then true
when re_false then false
else
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
end
end

ENV[coder.scalar] ? coerce[ENV[coder.scalar]] : false
end

coerce = ->(s) do
case s
when TrueClass, FalseClass then s
when re_true then true
when re_false then false
else
raise "bool value should be one of y(es)/n(o), on/off, true/false (got #{s.inspect})"
config.add_tag("env") do |coder|
env_fetch(*(coder.scalar || coder.seq))
end
end

ENV[coder.scalar] ? coerce[ENV[coder.scalar]] : false
end
config.add_tag("env?") do |coder|
fetch_args = coder.scalar ? [coder.scalar, nil] : coder.seq
ENV.fetch(*fetch_args)
end

add_resolver("env") do |coder|
env_fetch(*(coder.scalar || coder.seq))
end
config.add_tag("path") do |coder|
Pathname.new(coder.scalar || coder.seq.join("/"))
end

add_resolver("env?") do |coder|
fetch_args = coder.scalar ? [coder.scalar, nil] : coder.seq
ENV.fetch(*fetch_args)
end
config.add_tag("uri") do |coder|
URI(coder.scalar || coder.seq.join)
end

add_resolver("path") do |coder|
Pathname.new(coder.scalar || coder.seq.join("/"))
config.add_tag("str/format") do |coder|
case coder.type
when :seq
sprintf(*coder.seq)
when :map
m = Util.deep_symbolize_keys(coder.map)
fmt = m.delete(:fmt)
sprintf(fmt, m)
else
coder.scalar
end
end
end
end
private_class_method :add_default_tags!

add_resolver("uri") do |coder|
URI(coder.scalar || coder.seq.join)
end
def self.reset_configuration!
@configuration = nil

add_resolver("str/format") do |coder|
case coder.type
when :seq
sprintf(*coder.seq)
when :map
m = Util.deep_symbolize_keys(coder.map)
fmt = m.delete(:fmt)
sprintf(fmt, m)
else
coder.scalar
configure do |config|
config.config_dir = Pathname.pwd
end

add_default_tags!
end
reset_configuration!

def self.env_fetch(k, fallback = nil, all_optional: "dummy")
fallback ||= all_optional if ENV["NERO_ENV_ALL_OPTIONAL"]

fallback.nil? ? ENV.fetch(k) : ENV.fetch(k, fallback)
end
private_class_method :env_fetch

@yaml_options = {
permitted_classes: [Symbol, TagResolver],
Expand All @@ -146,13 +178,25 @@ def self.env_fetch(k, fallback = nil, all_optional: "dummy")
def self.load_config(file, root: nil)
add_tags!

file = resolve_file(file)

if file.exist?
process_yaml(YAML.load_file(file, **@yaml_options), root:)
else
raise "Can't find file #{file}"
end
end

def self.resolve_file(file)
case file
when Pathname then file
# TODO expand full path
else
configuration.config_dir / "#{file}.yml"
end
end
private_class_method :resolve_file

def self.load(raw, root: nil)
add_tags!

Expand All @@ -164,12 +208,12 @@ def self.process_yaml(yaml, root: nil)
root ? _1[root.to_sym] : _1
end

deep_resolve(unresolved, resolvers: @resolvers)
deep_resolve(unresolved, tags: configuration.tags)
end
private_class_method :process_yaml

def self.add_tags!
@resolvers.keys.each do
configuration.tags.keys.each do
YAML.add_tag(_1, TagResolver)
end
end
Expand Down
17 changes: 11 additions & 6 deletions spec/nero_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
require "yaml"

RSpec.describe Nero do
after(:each) { ensure_config_cleaned! }
after(:each) { delete_config_file! }
after(:each) { described_class.reset_configuration! }

def ensure_config_cleaned!
def delete_config_file!
@config_file&.tap do
_1.close
File.unlink(_1.path)
Expand All @@ -28,6 +29,8 @@ def given_config(s)
end
end

def nero_config(...) = described_class.configure(...)

def config_file
Pathname.new(@config_file.path)
end
Expand Down Expand Up @@ -358,10 +361,12 @@ def config_file
end
end

describe "adding a custom resolver" do
it "works" do
described_class.add_resolver("inc") do |coder|
Integer(coder.scalar).next
describe "adding a custom tag" do
specify "is added to the nero-config" do
nero_config do |cfg|
cfg.add_tag("inc") do |coder|
Integer(coder.scalar).next
end
end
given_config(<<~YAML)
---
Expand Down

0 comments on commit 69d6b19

Please # to comment.