-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbase.rb
168 lines (134 loc) · 3.94 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# frozen_string_literal: true
require "yaml"
require "erb"
require_relative "settings"
require_relative "configuration"
module SettingsCabinet
class Base
module AccessorBuilder
RESERVED_METHODS = %i[instance define_accessors! define_class_accessors!].freeze
private_constant :RESERVED_METHODS
refine Base do
private
def define_accessors!(values)
values.each_key do |key|
name = key.to_s
next unless name.match?(/\A\w+\z/)
self.class.class_eval <<~METHOD, __FILE__, __LINE__ + 1
def #{name} # def foo
self[:#{name.dump}] # self[:foo]
end # end
METHOD
end
end
def define_class_accessors!
names = public_methods(false)
.difference(::Object.public_methods, self.class.public_methods(false), RESERVED_METHODS)
names.each do |name|
self.class.class_eval <<~METHOD, __FILE__, __LINE__ + 1
def self.#{name}(...) # def self.foo(...)
@instance.#{name}(...) # @instance.foo(...)
end # end
METHOD
end
end
end
end
private_constant :AccessorBuilder
module SettingsLoader
refine Base do
def load_settings_hash(config)
YAMLSettingsLoader.new(config).call
end
end
class YAMLSettingsLoader
def initialize(config)
@source = config.source
@namespace = config.namespace
@permitted_classes = config.permitted_classes
end
def call
erb_str = ::File.read(source)
yaml_str = ::ERB.new(erb_str).result
::YAML
.safe_load(yaml_str,
permitted_classes: permitted_classes,
aliases: true,
filename: source,
fallback: {},
symbolize_names: true)
.then { |h| namespace ? h.fetch(namespace.to_sym) : h }
end
private
attr_reader :source, :namespace, :permitted_classes
end
private_constant :YAMLSettingsLoader
end
require_relative "instance"
using Instance
using SettingsLoader
using AccessorBuilder
def initialize(config)
settings_hash = load_settings_hash(config)
@settings = Settings.new(settings_hash)
define_accessors!(settings_hash)
define_class_accessors!
end
def [](...)
@settings.[](...)
end
def dig(...)
@settings.dig(...)
end
def fetch(...)
@settings.fetch(...)
end
def fetch_values(...)
@settings.fetch_values(...)
end
def values_at(...)
@settings.values_at(...)
end
def to_h(...)
@settings.to_h(...)
end
def self.[](...)
instance.[](...)
end
def self.dig(...)
instance.dig(...)
end
def self.fetch(...)
instance.fetch(...)
end
def self.fetch_values(...)
instance.fetch_values(...)
end
def self.values_at(...)
instance.values_at(...)
end
def self.to_h(...)
instance.to_h(...)
end
def self.method_missing(...)
instance.public_send(...)
end
private_class_method :method_missing
def self.respond_to_missing?(symbol, _include_private)
instance.respond_to?(symbol, false)
end
private_class_method :respond_to_missing?
def self.new(*)
raise ::NotImplementedError, "#{self} is an abstract class and cannot be instantiated." if self == Base
super
end
private_class_method :new
def self.inherited(subclass)
super
config = Configuration.new(source: nil, namespace: nil, permitted_classes: [])
subclass.instance_variable_set(:@config, config)
subclass.instance_variable_set(:@instance_lock, ::Mutex.new)
end
private_class_method :inherited
end
end