yiffspace-config 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cd4b38dbf53747f6a11d19eee67a466ef257d5be641ed96ac04367582ae1e2db
4
+ data.tar.gz: 4d730250f89f9e52ddf1727f59486804f78dbce5415565b2c868a524bc4b92a2
5
+ SHA512:
6
+ metadata.gz: 31fccac5adfd9589fa22c84076cf798ee2763427735ea8d104b4eaa828181fc14149474ee91ace45ba1c587aa841ccbe78bfa0b3046a8f5b71a3d5a58c818c80
7
+ data.tar.gz: 385a6b2e1065488ca3d841683e1db9a0a8adaf6163a02fcf4fc0f0b9331b3b98e505501afb561717dbcb5fff6c5132913b8b6a06690ce3f3b6f00258cd96db95
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.0.1
2
+
3
+ - Initial release, extracted from the `yiffspace` gem's `YiffSpace::Config::Builder`.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Donovan_DMC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # YiffSpace::Config
2
+
3
+ Standalone ENV-backed config DSL (`YiffSpace::Config::Builder`), for
4
+ https://yiff.space and related projects. Not a Rails engine - no dependency
5
+ on `yiffspace` itself, only ActiveSupport.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem "yiffspace-config"
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ class Config < YiffSpace::Config::Builder
25
+ config(:some_value, required: true)
26
+ config(:some_flag, :boolean) { false }
27
+ end
28
+ ```
29
+
30
+ See `YiffSpace::Config::Builder` for the full API.
31
+
32
+ ## Contributing
33
+
34
+ Go away
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("bundler/gem_tasks")
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("singleton")
4
+ require("active_support/core_ext/module/attribute_accessors")
5
+ require("active_support/hash_with_indifferent_access")
6
+
7
+ # This is intended to only be used externally, it is NOT reusable, do not use it within this project, or attempt to use it multiple times!!
8
+ module YiffSpace
9
+ module Config
10
+ class Builder
11
+ include(Singleton)
12
+
13
+ cattr_accessor(:list, default: [])
14
+ cattr_accessor(:env_set, default: ActiveSupport::HashWithIndifferentAccess.new)
15
+ cattr_accessor(:unset, default: [])
16
+ cattr_accessor(:required, default: [])
17
+ cattr_accessor(:reviver_map, default: ActiveSupport::HashWithIndifferentAccess.new)
18
+ cattr_accessor(:subconfigs, default: ActiveSupport::HashWithIndifferentAccess.new { |h, k| h[k] = {} })
19
+ cattr_accessor(:env_name, default: "CONFIG")
20
+
21
+ def self.config(name, type = :string, env: true, required: false, blank: false, &block)
22
+ name = name.to_sym
23
+ remove_config(name) if list.include?(name)
24
+ list << name
25
+ self.required << name if required
26
+ if block.nil?
27
+ unset << name
28
+ block = -> { raise(NotImplementedError, "Config option #{name} is not set") }
29
+ end
30
+ define_method(name) do |*args|
31
+ env_or_value(name, args, blank: blank, &block)
32
+ end
33
+ if type == :boolean
34
+ define_method("#{name}?") do |*args|
35
+ public_send(name, *args)
36
+ end
37
+ end
38
+ reviver(name, type) if type && type != :string
39
+ env_set[name] = env
40
+ end
41
+
42
+ def self.remove_config(name, reviver: false)
43
+ name = name.to_sym
44
+ list.delete(name)
45
+ required.delete(name)
46
+ unset.delete(name)
47
+ reviver_map.delete(name) if reviver
48
+ env_set.delete(name)
49
+ remove_method(name)
50
+ remove_method("#{name}?") if method_defined?("#{name}?")
51
+ end
52
+
53
+ def env_or_value(name, args, blank: false, &)
54
+ value = env(name)
55
+ value = nil if !value.nil? && value.blank? && !blank
56
+ if value.nil?
57
+ instance_exec(*args, &)
58
+ else
59
+ reviver = reviver_map.fetch(name, proc(&:itself))
60
+ instance_exec(value, *args, &reviver)
61
+ end
62
+ end
63
+
64
+ def env(name)
65
+ ENV.fetch("#{self.class.env_name}_#{name.to_s.upcase}", nil)
66
+ end
67
+
68
+ def self.reviver(name, type = nil, &block)
69
+ name = name.to_sym
70
+ if block
71
+ reviver_map[name] = block
72
+ return
73
+ end
74
+ method = case type
75
+ when :boolean
76
+ ->(v) { !v.match?(/\A(false|f|no|n|off|0)\z/i) }
77
+ when :integer, :number
78
+ ->(v) { v.to_i }
79
+ when :symbol
80
+ ->(v) { v.to_sym }
81
+ when :array
82
+ ->(v) { v.split(/\s*,\s*/) }
83
+ else
84
+ raise(ArgumentError, "not sure how to revive #{type} for #{method}")
85
+ end
86
+ reviver_map[name] = method
87
+ end
88
+
89
+ def self.subconfig(prefix, parent: [], &block)
90
+ raise(ArgumentError, "block required") unless block
91
+
92
+ prefix = prefix.to_sym
93
+ path = parent + [prefix]
94
+
95
+ node = subconfigs
96
+ path.each do |p|
97
+ node[p] ||= {}
98
+ node = node[p]
99
+ end
100
+
101
+ define_method(prefix) { SubconfigProxy.new(self, path) } unless method_defined?(prefix)
102
+
103
+ collector = Module.new do
104
+ def self.collected
105
+ @collected ||= []
106
+ end
107
+
108
+ def self.config(name, type = :string, env: true, required: false, blank: false, &block)
109
+ collected << [:config, name, type, env, required, blank, block]
110
+ end
111
+
112
+ def self.reviver(name, type = nil, &block)
113
+ collected << [:reviver, name, type, block]
114
+ end
115
+
116
+ def self.subconfig(name, &block)
117
+ collected << [:subconfig, name, block]
118
+ end
119
+ end
120
+
121
+ collector.module_eval(&block)
122
+
123
+ collector.collected.each do |kind, name, *args|
124
+ case kind
125
+ when :config
126
+ full = (path + [name]).join("_").to_sym
127
+ config(full, args[0], env: args[1], required: args[2], blank: args[3], &args[4])
128
+ when :reviver
129
+ full = (path + [name]).join("_").to_sym
130
+ reviver(full, args[0], &args[1])
131
+ when :subconfig
132
+ subconfig(name, parent: path, &args[0])
133
+ end
134
+ end
135
+ end
136
+
137
+ def self.ensure_required_set!
138
+ unset = []
139
+ required.each do |name|
140
+ value = begin
141
+ instance.public_send(name)
142
+ rescue StandardError
143
+ nil
144
+ end
145
+ unset << name if value.blank?
146
+ end
147
+
148
+ raise("Missing required configuration options: #{unset.join(', ')}") if unset.any?
149
+ end
150
+
151
+ def present?(*names)
152
+ names.flatten.all? { |name| public_send(name).present? }
153
+ end
154
+
155
+ class SubconfigProxy
156
+ def initialize(owner, path)
157
+ @owner = owner
158
+ @path = path
159
+ end
160
+
161
+ def method_missing(name, *, &)
162
+ name = name.to_sym
163
+ full_path = @path + [name]
164
+
165
+ flat = full_path.join("_").to_sym
166
+ return @owner.public_send(flat, *, &) if @owner.respond_to?(flat)
167
+
168
+ return SubconfigProxy.new(@owner, full_path) if subconfig_path?(full_path)
169
+
170
+ super
171
+ end
172
+
173
+ def respond_to_missing?(name, include_private = false)
174
+ name = name.to_sym
175
+ flat = (@path + [name]).join("_").to_sym
176
+
177
+ @owner.respond_to?(flat, include_private) ||
178
+ subconfig_path?(@path + [name]) ||
179
+ super
180
+ end
181
+
182
+ private
183
+
184
+ def subconfig_path?(path)
185
+ node = Builder.subconfigs
186
+ path.each do |p|
187
+ return false unless node.key?(p)
188
+
189
+ node = node[p]
190
+ end
191
+ true
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Config
5
+ VERSION = "0.0.1"
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("zeitwerk")
4
+
5
+ module YiffSpace
6
+ module Config
7
+ end
8
+ end
9
+
10
+ loader = Zeitwerk::Loader.for_gem_extension(YiffSpace)
11
+ loader.setup
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yiffspace-config
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Donovan_DMC
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-09-04 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: zeitwerk
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.6'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.6'
40
+ description: Standalone ENV-backed config DSL (YiffSpace::Config::Builder), for https://yiff.space
41
+ and related projects
42
+ email:
43
+ - hewwo@yiff.rocks
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - lib/yiffspace/config.rb
53
+ - lib/yiffspace/config/builder.rb
54
+ - lib/yiffspace/config/version.rb
55
+ homepage: https://yiff.space
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ allowed_push_host: https://rubygems.org
60
+ homepage_uri: https://yiff.space
61
+ source_code_uri: https://github.com/YiffSpace/Gem/tree/master/yiffspace-config
62
+ changelog_uri: https://github.com/YiffSpace/Gem/blob/master/yiffspace-config/CHANGELOG.md
63
+ rubygems_mfa_required: 'true'
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.4.1
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.2
79
+ specification_version: 4
80
+ summary: Standalone ENV-backed config DSL (YiffSpace::Config::Builder), for https://yiff.space
81
+ and related projects
82
+ test_files: []