ace-config 0.1.0.beta.rc1b

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 673610da9904345d9482fa26d53c0717ef5bb3aa5a5bd7124d2ff5cf64b36a2f
4
+ data.tar.gz: 1b7036dcfba77239068380f9912b7a2efe0fb596e0534eebcfb329dbef58bad8
5
+ SHA512:
6
+ metadata.gz: 96a2009741d8ce31cc20fb5a9a1ac22874ef9d1b67170f720826896fb364799cf7986231cd2d9a9babf3cfe07b594e67277197527eb5858661e7cbd2370e8675
7
+ data.tar.gz: 0d32fe09d35a104bc697a8e3e2d84286215ab7a15409ee0c54cbb4e7326a9aab7ba5e2f9afa8910b3cb66d1e0b75f3d679c929fee7c42f1d53e5067d1573a2b0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-09-12
4
+
5
+ - Preview Candidate 0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 yurigitsu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,382 @@
1
+ # ace-config
2
+
3
+ Ruby gem created to simplify managing application configurations and enhance the development of other gems that require configuration management. It offers a simple interface for defining, setting, and retrieving configuration options with type validation, helping ensure configurations are correct and reliable.
4
+
5
+ - **ace-config** provides built-in support for importing and exporting configurations in JSON, YAML, and Hash formats, enhancing versatility.
6
+
7
+ - **ace-config** offers various built-in types like basic types, data structures, numeric types, and time types.
8
+
9
+ - **ace-config** supports infinite nested configurations and 'classy' access providing a flexible and powerful configuration management solution.
10
+
11
+
12
+ ## Features
13
+
14
+ - **Simple Configuration Management**: Easily define, set, and retrieve configuration options.
15
+ - **Type Validation**: Ensure configurations are correct with built-in type validation.
16
+ - **Multiple Formats**: Import and export configurations in JSON, YAML, and Hash formats.
17
+ - **Nested Configurations**: Support for infinite nested configurations for complex applications.
18
+ - **Classy Access**: Access configurations in a 'classy' manner for better organization and readability.
19
+ - **Built-in Types**: Utilize various built-in types including basic types, data structures, numeric types, and time types.
20
+ - **Extensible**: Easily extendable to accommodate custom configuration needs.
21
+
22
+ ## Table of Contents
23
+ - [Installation](#installation)
24
+ - [Basic Usage](#basic-usage)
25
+ - [Configuration Container Usage](#configuration-container-usage)
26
+ - [DSL Syntax](#dsl-syntax)
27
+ - Typing
28
+ - [Define Configuration Validation](#set-configuration-type-validation)
29
+ - [Declaring Validation](#configure-type-validation)
30
+ - [Type Schema](#type_schema)
31
+ - [Built-in Types](#built-in-types)
32
+ - Import:
33
+ - [Loading Configurations](#loading-configuration-data)
34
+ - [Loading from a JSON String](#loading-from-a-json-string)
35
+ - [Loading from a YAML File](#loading-from-a-yaml-file)
36
+ - Export:
37
+ - [Exporting Configurations](#exporting-configuration-data)
38
+ - [to_h](#to_h)
39
+ - [to_json](#to_json)
40
+ - [to_yaml](#to_yaml)
41
+ - OSS
42
+ - [Development](#development)
43
+ - [Contributing](#contributing)
44
+ - [License](#license)
45
+ - [Code of Conduct](#code-of-conduct)
46
+
47
+ ## Installation
48
+
49
+ Install the gem and add to the application's Gemfile by executing:
50
+
51
+ ```bash
52
+ bundle add ace-config
53
+ ```
54
+
55
+ If bundler is not being used to manage dependencies, install the gem by executing:
56
+
57
+ ```bash
58
+ gem install ace-config
59
+ ```
60
+
61
+ ## Basic Usage
62
+
63
+ ```ruby
64
+ require 'ace_config'
65
+
66
+ module MyApp
67
+ include AceConfig
68
+ end
69
+
70
+ MyApp.configure :settings do
71
+ config option: 42
72
+ config.int typed_opt_one: 42
73
+ config typed_opt_two: 4.2, type: :float
74
+ end
75
+
76
+ MyApp.settings.option # => 42
77
+ MyApp.settings.typed_opt_one # => 42
78
+ MyApp.settings.typed_opt_two # => 4.2
79
+ ```
80
+
81
+ ### Basic Syntax
82
+ ```ruby
83
+ MyApp.configure :settings do
84
+ config option: 42
85
+ end
86
+ ```
87
+
88
+ ## Namespacing
89
+ ```ruby
90
+ MyApp.configure :app do
91
+ configure :lvl_one do
92
+ config opt: 100
93
+ configure :lvl_two do
94
+ config opt: 200
95
+ configure :lvl_three do
96
+ config opt: 300
97
+ configure :lvl_four do
98
+ config opt: 400
99
+ configure :lvl_five do
100
+ config opt: 500
101
+ # NOTE: as deep as you want
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ MyApp.app.lvl_one.opt # => 100
110
+ MyApp.app.lvl_one.lvl_two.opt # => 200
111
+ MyApp.app.lvl_one.lvl_two.lvl_three.opt # => 300
112
+ MyApp.app.lvl_one.lvl_two.lvl_three.lvl_four.opt # => 400
113
+ MyApp.app.lvl_one.lvl_two.lvl_three.lvl_four.lvl_five.opt # => 500
114
+ ```
115
+
116
+ ### Configure Type Validation
117
+ ```ruby
118
+ MyApp.configure :settings do
119
+ config custom_typed_opt_one: '42', type: :float
120
+ end
121
+ # => AceConfig::SettingTypeError
122
+ ```
123
+
124
+ ## Configuration Container Usage
125
+
126
+ ```ruby
127
+ require 'ace_config'
128
+
129
+ module MyGem
130
+ include AceConfig
131
+ end
132
+ ```
133
+
134
+ ### Declare configurations
135
+ ```ruby
136
+ MyGem.configure :settings do
137
+ config :option
138
+ config.int :typed_opt_one
139
+ config :typed_opt_two, type: Integer
140
+ # NOTE: declare nested namespace with configure <symbol arg>
141
+ configure :nested do
142
+ config :option
143
+ end
144
+ end
145
+ ```
146
+
147
+ ### Define configurations
148
+ ```ruby
149
+ MyGem.settings do
150
+ config option: 1
151
+ config typed_opt_one: 2
152
+ config typed_opt_two: 3
153
+ # NOTE: access namespace via <.dot_access>
154
+ config.nested do
155
+ config option: 4
156
+ end
157
+ end
158
+ ```
159
+
160
+ ### Define with DSL Syntax
161
+ ```ruby
162
+ MyGem.settings do
163
+ option 1
164
+ typed_opt_one 2
165
+ typed_opt_two 3
166
+ # NOTE: access namespace via <block>
167
+ nested do
168
+ option 1
169
+ end
170
+ end
171
+ ```
172
+
173
+ ### Get configurations
174
+ ```ruby
175
+ MyGem.settings.option # => 1
176
+ MyGem.settings.typed_opt_one # => 2
177
+ MyGem.settings.typed_opt_two # => 3
178
+ MyGem.settings.nested.option # => 4
179
+ ```
180
+
181
+ ### Define Configuration Type Validation
182
+ ```ruby
183
+ MyGem.settings do
184
+ config.typed_opt_two: '1'
185
+ end
186
+ # => AceConfig::SettingTypeError
187
+
188
+ MyGem.settings do
189
+ typed_opt_two '1'
190
+ end
191
+ # => AceConfig::SettingTypeError
192
+ ```
193
+
194
+ ## Loading Configuration Data
195
+
196
+ The `AceConfig` module allows you to load configuration data from various sources, including YAML and JSON. Below are the details for each option.
197
+
198
+ - `json` (String)
199
+ - `yaml` (String)
200
+ - `hash` (Hash)
201
+ - `schema` (Hash) (Optional) See: [Type Schema](#type_schema) and [Built-in Types](#built-in-types)
202
+
203
+ ### Loading from a JSON String
204
+
205
+ You can load configuration data from a JSON string by passing the `json` option to the `configure` method.
206
+
207
+ #### Parameters
208
+
209
+ - `json` (String): A JSON string containing the configuration data.
210
+ - `schema` (Hash) (Optional): A hash representing the type schema for the configuration data.
211
+
212
+ #### Error Handling
213
+
214
+ - If the JSON format is invalid, a `LoadDataError` will be raised with the message "Invalid JSON format".
215
+
216
+ #### Example 1
217
+ ```ruby
218
+ MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}').settings
219
+ # => #<MyGem::Setting:0x00007f8c1c0b2a80 @options={:opt_one=>1, :opt_two=>2}>
220
+ ```
221
+
222
+ #### Example 2
223
+ ```ruby
224
+ MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}', schema: { opt_one: :int, opt_two: :str })
225
+ # => AceConfig::SettingTypeError: Expected: <str>. Given: 2 which is <Integer> class.
226
+ ```
227
+
228
+ #### Example 3
229
+ ```ruby
230
+ MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}', schema: { opt_one: :int, opt_two: :int })
231
+
232
+ MyGem.settings do
233
+ opt_one 1
234
+ opt_two "2"
235
+ end
236
+ # => AceConfig::SettingTypeError: Expected: <intstr>. Given: \"2\" which is <String> class.
237
+ ```
238
+
239
+ ### Loading from a YAML File
240
+
241
+ You can also load configuration data from a YAML file by passing the `yaml` option to the `configure` method.
242
+
243
+ #### Parameters
244
+
245
+ - `yaml` (String): A file path to a YAML file containing the configuration data.
246
+ - `schema` (Hash) (Optional): A hash representing the type schema for the configuration data.
247
+
248
+ #### Error Handling
249
+
250
+ - If the specified YAML file is not found, a `LoadDataError` will be raised with the message "YAML file not found".
251
+
252
+ ##### YAML File
253
+ ```yaml
254
+ # settings.yml
255
+
256
+ opt_one: 1
257
+ opt_two: 2
258
+ ```
259
+
260
+ #### Example 1
261
+ ```ruby
262
+ MyGem.configure :settings, yaml: 'settings.yml'
263
+ # => #<MyGem::Setting:0x00006f8c1c0b2a80 @options={:opt_one=>1, :opt_two=>2}>
264
+ ```
265
+
266
+ #### Example 2
267
+ ```ruby
268
+ MyGem.configure :settings, yaml: 'settings.yml', schema: { opt_one: :int, opt_two: :str }
269
+ # => AceConfig::SettingTypeError: Expected: <str>. Given: 2 which is <Integer> class.
270
+ ```
271
+
272
+ #### Example 3
273
+ ```ruby
274
+ MyGem.configure :settings, yaml: 'settings.yml', schema: { opt_one: :int, opt_two: :int }
275
+
276
+ MyGem.settings do
277
+ opt_one 1
278
+ opt_two "2"
279
+ end
280
+ # => AceConfig::SettingTypeError: Expected: <intstr>. Given: \"2\" which is <String> class.
281
+ ```
282
+
283
+ ## Exporting Configuration Data
284
+
285
+ You can dump the configuration data in various formats using the following methods:
286
+
287
+ ### to_h
288
+ ```ruby
289
+ MyGem.configure :settings do
290
+ config opt_one: 1
291
+ config opt_two: 2
292
+ end
293
+
294
+ MyGem.settings.to_json # => '{"opt_one":1,"opt_two":2}'
295
+ ```
296
+
297
+ ### to_json
298
+ ```ruby
299
+ MyGem.configure :settings do
300
+ config opt_one: 1
301
+ config opt_two: 2
302
+ end
303
+
304
+ MyGem.settings.to_json # => '{"opt_one":1,"opt_two":2}'
305
+ ```
306
+
307
+ ### to_yaml
308
+ ```ruby
309
+ MyGem.configure :settings do
310
+ config opt_one: 1
311
+ config opt_two: 2
312
+ end
313
+
314
+ MyGem.settings.to_yaml # => "---\nopt_one: 1\nopt_two: 2\n"
315
+ ```
316
+
317
+ ### type_schema
318
+ ```ruby
319
+ MyGem.configure :settings do
320
+ config.int opt_one: 1
321
+ config.str opt_two: "2"
322
+ end
323
+
324
+ MyGem.settings.type_schema # => {:opt_one=>:int, :opt_two=>:str}
325
+ ```
326
+
327
+ ## Built-in Types
328
+
329
+ ### Base Types
330
+ ```ruby
331
+ :int => Integer
332
+ :str => String
333
+ :sym => Symbol
334
+ :null => NilClass
335
+ :any => Object
336
+ :true_class => TrueClass
337
+ :false_class => FalseClass
338
+ ```
339
+
340
+ ### Data Structures
341
+ ```ruby
342
+ :hash => Hash
343
+ :array => Array
344
+ ```
345
+ ### Numeric
346
+ ```ruby
347
+ :big_decimal => BigDecimal,
348
+ :float => Float,
349
+ :complex => Complex,
350
+ :rational => Rational,
351
+ ```
352
+ ### Time
353
+ ```ruby
354
+ :date => Date,
355
+ :date_time => DateTime,
356
+ :time => Time,
357
+ ```
358
+ ### Composite
359
+ ```ruby
360
+ :bool => [TrueClass, FalseClass],
361
+ :numeric => [Integer, Float, BigDecimal],
362
+ :kernel_num => [Integer, Float, BigDecimal, Complex, Rational],
363
+ :chrono => [Date, DateTime, Time]
364
+ ```
365
+
366
+ ## Development
367
+
368
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
369
+
370
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
371
+
372
+ ## Contributing
373
+
374
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yurigitsu/ace-config. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yurigitsu/ace-config/blob/main/CODE_OF_CONDUCT.md).
375
+
376
+ ## License
377
+
378
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
379
+
380
+ ## Code of Conduct
381
+
382
+ Everyone interacting in the AceConfig project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yurigitsu/ace-config/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require "ace_config/version"
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "ace-config"
10
+ spec.version = "0.1.0.beta.rc1b"
11
+ spec.authors = ["yurigitsu"]
12
+ spec.email = ["yurigi.pro@gmail.com"]
13
+ spec.license = "MIT"
14
+
15
+ spec.summary = "A flexible and easy-to-use configuration handling gem."
16
+ spec.description = "Managing, configurations with type validation, configirations load and export support."
17
+ spec.homepage = "https://github.com/yurigitsu/#{spec.name}"
18
+
19
+ spec.required_ruby_version = ">= 3.0.0"
20
+
21
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "ace-config.gemspec", "lib/**/*"]
22
+ spec.bindir = "bin"
23
+ spec.executables = []
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.metadata["repo_homepage"] = "https://github.com/yurigitsu/"
27
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
28
+
29
+ spec.metadata["homepage_uri"] = spec.homepage
30
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
31
+ spec.metadata["source_code_uri"] = spec.homepage
32
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
33
+
34
+ spec.metadata["rubygems_mfa_required"] = "true"
35
+
36
+ spec.add_dependency "bigdecimal", "~> 3.0"
37
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AceConfig module provides functionality for managing configuration features.
4
+ #
5
+ # @example Using AceConfig in a class
6
+ # class MyApp
7
+ # include AceConfig
8
+ #
9
+ # configure :settings do
10
+ # config api_key: "default_key"
11
+ # config max_retries: 3
12
+ # end
13
+ # end
14
+ #
15
+ # MyApp.settings.api_key # => "default_key"
16
+ # MyApp.settings.max_retries # => 3
17
+ module AceConfig
18
+ # Extends the base class with Configuration module methods
19
+ def self.included(base)
20
+ base.extend(Configuration)
21
+ end
22
+
23
+ # Isolated module handles isolated configurations.
24
+ #
25
+ # @example Using Isolated configurations
26
+ # class ParentApp
27
+ # include AceConfig::Isolated
28
+ #
29
+ # configure :parent_settings do
30
+ # config timeout: 30
31
+ # config tries: 3
32
+ # end
33
+ # end
34
+ #
35
+ # class ChildApp < ParentApp
36
+ # parent_settings do
37
+ # config tries: 4
38
+ # end
39
+ # end
40
+ #
41
+ # ChildApp.parent_settings.timeout # => 30
42
+ # ChildApp.parent_settings.tries # => 4
43
+ # ParentApp.parent_settings.tries # => 3
44
+ module Isolated
45
+ # Extends the base class with Configuration and Configuration::Isolated module methods
46
+ def self.included(base)
47
+ base.extend(Configuration)
48
+ base.extend(Configuration::Isolated)
49
+ end
50
+ end
51
+
52
+ # This module handles configuration trees and loading data from various sources.
53
+ module Configuration
54
+ # Isolated module provides methods for handling isolated configurations.
55
+ module Isolated
56
+ # Configures an isolated config tree and tracks it
57
+ #
58
+ # @param config_tree_name [Symbol] The name of the configuration tree
59
+ # @param opts [Hash] Options for configuration
60
+ # @yield The configuration block
61
+ # @return [self]
62
+ #
63
+ # @example
64
+ # configure :isolated_settings do
65
+ # config api_url: "https://api.example.com"
66
+ # end
67
+ #
68
+ # # Example of accessing the configuration
69
+ # puts MyApp.isolated_settings.api_url # => "https://api.example.com"
70
+ def configure(config_tree_name, opts = {}, &block)
71
+ super
72
+
73
+ @isolated_configs ||= []
74
+ @isolated_configs << config_tree_name
75
+
76
+ self
77
+ end
78
+
79
+ # Inherits isolated configurations to the base class
80
+ #
81
+ # @param base [Class] The inheriting class
82
+ #
83
+ # @example
84
+ # class ChildClass < ParentClass
85
+ # # Automatically inherits isolated configurations
86
+ # end
87
+ #
88
+ # # Example of accessing inherited configurations
89
+ # puts ChildClass.parent_settings.timeout # => 30
90
+ def inherited(base)
91
+ super
92
+
93
+ @isolated_configs.each do |parent_config|
94
+ hash = __send__(parent_config).to_h
95
+ schema = __send__(parent_config).type_schema
96
+
97
+ base.configure parent_config, hash: hash, schema: schema
98
+ end
99
+ end
100
+ end
101
+
102
+ # Creates a class-level method for the configuration tree.
103
+ #
104
+ # This method allows you to define a configuration tree using a block
105
+ # or load configuration data from a hash, JSON, or YAML file.
106
+ #
107
+ # @param config_tree_name [Symbol, String] The name of the configuration method to be defined.
108
+ # @param opts [Hash] Optional options for loading configuration data.
109
+ # @option opts [Hash] :hash A hash containing configuration data.
110
+ # @option opts [String] :json A JSON string containing configuration data.
111
+ # @option opts [String] :yaml A file path to a YAML file containing configuration data.
112
+ # @option opts [Hash] :schema A hash representing the type schema for the configuration.
113
+ # @yield [Setting] A block that builds the configuration tree.
114
+ #
115
+ # @example Configuring with a block
116
+ # configure :app_config do
117
+ # config :username, value: "admin"
118
+ # config :max_connections, type: :int, value: 10
119
+ # end
120
+ #
121
+ # @example Loading from a hash
122
+ # configure :app_config, hash: { username: "admin", max_connections: 10 }
123
+ #
124
+ # @example Loading from a JSON string
125
+ # configure :app_config, json: '{"username": "admin", "max_connections": 10}'
126
+ #
127
+ # @example Loading from a YAML file
128
+ # configure :app_config, yaml: 'config/settings.yml'
129
+ #
130
+ # @example Loading with a schema
131
+ # configure :app_config, hash: { name: "admin", policy: "allow" }, schema: { name: :str, policy: :str }
132
+ def configure(config_tree_name, opts = {}, &block)
133
+ settings = block ? AceConfig::Setting.new(&block) : AceConfig::Setting.new
134
+
135
+ load_configs = load_data(opts) unless opts.empty?
136
+ settings.load_from_hash(load_configs, schema: opts[:schema]) if load_configs
137
+
138
+ define_singleton_method(config_tree_name) do |&tree_block|
139
+ tree_block ? settings.instance_eval(&tree_block) : settings
140
+ end
141
+ end
142
+
143
+ module_function
144
+
145
+ # Loads configuration data from various sources based on the provided options.
146
+ #
147
+ # @param opts [Hash] Optional options for loading configuration data.
148
+ # @option opts [Hash] :hash A hash containing configuration data.
149
+ # @option opts [String] :json A JSON string containing configuration data.
150
+ # @option opts [String] :yaml A file path to a YAML file containing configuration data.
151
+ # @return [Hash] The loaded configuration data.
152
+ # @raise [LoadDataError] If no valid data is found.
153
+ #
154
+ # @example Loading from a hash
155
+ # load_data(hash: { key: "value" })
156
+ #
157
+ # @example Loading from a JSON string
158
+ # load_data(json: '{"key": "value"}')
159
+ #
160
+ # @example Loading from a YAML file
161
+ # load_data(yaml: 'config/settings.yml')
162
+ def load_data(opts = {})
163
+ data = opts[:hash] if opts[:hash]
164
+ data = JSON.parse(opts[:json]) if opts[:json]
165
+ data = YAML.load_file(opts[:yaml]) if opts[:yaml]
166
+ raise AceConfig::LoadDataError, "Invalid load source type" unless data
167
+
168
+ data
169
+ rescue JSON::ParserError
170
+ raise AceConfig::LoadDataError, "Invalid JSON format"
171
+ rescue Errno::ENOENT
172
+ raise AceConfig::LoadDataError, "YAML file not found"
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AceConfig module provides functionality for managing AceConfig features.
4
+ module AceConfig
5
+ # Custom error raised when a setting type does not match the expected type.
6
+ #
7
+ # @example Raising an SettingTypeError
8
+ # raise SettingTypeError.new(:int, "string")
9
+ # # => raises SettingTypeError with message
10
+ # # "Expected: <int>. Given: \"string\" which is <String> class."
11
+ class SettingTypeError < TypeError
12
+ # Initializes a new SettingTypeError.
13
+ #
14
+ # @param type [Symbol] The expected type.
15
+ # @param val [Object] The value that was provided.
16
+ def initialize(type, val)
17
+ super(type_error_msg(type, val))
18
+ end
19
+
20
+ # Generates the error message for the exception.
21
+ #
22
+ # @param type [Symbol] The expected type.
23
+ # @param val [Object] The value that was provided.
24
+ # @return [String] The formatted error message.
25
+ def type_error_msg(type, val)
26
+ "Expected: <#{type}>. Given: #{val.inspect} which is <#{val.class}> class."
27
+ end
28
+ end
29
+
30
+ # Custom error raised when a type definition is missing.
31
+ #
32
+ # @example Raising a TypeCheckerError
33
+ # raise TypeCheckerError.new(:unknown_type)
34
+ # # => raises TypeCheckerError with message "No type Definition for: <unknown_type> type"
35
+ class TypeCheckerError < StandardError
36
+ # Initializes a new TypeCheckerError.
37
+ #
38
+ # @param type [Symbol] The type that is missing a definition.
39
+ def initialize(type)
40
+ super(definition_error_msg(type))
41
+ end
42
+
43
+ # Generates the error message for the exception.
44
+ #
45
+ # @param type [Symbol] The type that is missing a definition.
46
+ # @return [String] The formatted error message.
47
+ def definition_error_msg(type)
48
+ "No type Definition for: <#{type}> type"
49
+ end
50
+ end
51
+
52
+ # Custom error raised when data loading fails.
53
+ class LoadDataError < StandardError; end
54
+ end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "json"
5
+
6
+ # AceConfig module provides functionality for managing AceConfig features.
7
+ module AceConfig
8
+ # Setting class provides a configuration tree structure for managing settings.
9
+ #
10
+ # This class allows for dynamic configuration management, enabling
11
+ # the loading of settings from hashes, YAML, or JSON formats.
12
+ #
13
+ # @example Basic usage
14
+ # settings = Setting.new do
15
+ # config(:key1, value: "example")
16
+ # config(:key2, type: :int)
17
+ # end
18
+ #
19
+ class Setting
20
+ # Dynamically define methods for each type in TypeMap.
21
+ #
22
+ # @!method int(value)
23
+ # Sets an integer configuration value.
24
+ # @param value [Integer] The integer value to set.
25
+ #
26
+ # @!method string(value)
27
+ # Sets a string configuration value.
28
+ # @param value [String] The string value to set.
29
+ #
30
+ # ... (other type methods)
31
+ AceConfig::TypeMap.list_types.each do |type|
32
+ define_method(type.downcase) { |stng| config(stng, type: type) }
33
+ end
34
+
35
+ # Initializes a new Setting instance.
36
+ #
37
+ # @yield [self] Configures the instance upon creation if a block is given.
38
+ def initialize(&block)
39
+ @schema = {}
40
+ @config_tree = {}
41
+
42
+ instance_eval(&block) if block_given?
43
+ end
44
+
45
+ # Loads configuration from a hash with an optional schema.
46
+ #
47
+ # @param data [Hash] The hash containing configuration data.
48
+ # @param schema [Hash] Optional schema for type validation.
49
+ # @raise [NoMethodError] If a method corresponding to a key is not defined.
50
+ # @raise [SettingTypeError] If a value doesn't match the specified type in the schema.
51
+ #
52
+ # @example Loading from a hash with type validation
53
+ # settings.load_from_hash({ name: "admin", max_connections: 10 }, schema: { name: :str, max_connections: :int })
54
+ def load_from_hash(data, schema: {})
55
+ data.each do |key, value|
56
+ key = key.to_sym
57
+ type = schema[key] if schema
58
+
59
+ if value.is_a?(Hash) || value.is_a?(Setting)
60
+ configure(key) { load_from_hash(value, schema: schema[key]) }
61
+ else
62
+ validate_setting!(value, type) if type
63
+
64
+ config(key => value, type: type)
65
+ end
66
+ end
67
+ end
68
+
69
+ # Configures a node of the configuration tree.
70
+ #
71
+ # @param node [Symbol, String] The name of the config node key.
72
+ # @yield [Setting] A block that configures the new node.
73
+ #
74
+ # @example Configuring a new node
75
+ # settings.configure(:database) do
76
+ # config(:host, value: "localhost")
77
+ # config(:port, value: 5432)
78
+ # end
79
+ def configure(node, &block)
80
+ if config_tree[node]
81
+ config_tree[node].instance_eval(&block)
82
+ else
83
+ create_new_node(node, &block)
84
+ end
85
+ end
86
+
87
+ # Configures a setting with a given name and type.
88
+ #
89
+ # @param setting [Symbol, Hash, nil] The name of the setting or a hash of settings.
90
+ # @param type [Symbol, nil] The expected type of the setting.
91
+ # @param opt [Hash] Additional options for configuration.
92
+ # @return [self] The current instance for method chaining.
93
+ # @raise [SettingTypeError] If the value does not match the expected type.
94
+ #
95
+ # @example Configuring a setting
96
+ # settings.config(max_connections: 10, type: :int)
97
+ def config(setting = nil, type: nil, **opt)
98
+ return self if !setting && opt.empty?
99
+
100
+ stngs = setting || opt
101
+ stng = extract_setting_info(stngs)
102
+
103
+ stng_type = type || schema[stng[:name]] || :any
104
+ validate_setting!(stng[:value], stng_type)
105
+
106
+ set_configuration(stng[:name], stng[:value], stng_type)
107
+ end
108
+
109
+ # Returns the type schema of the configuration.
110
+ #
111
+ # @return [Hash] A hash representing the type schema.
112
+ # @example Retrieving the type schema
113
+ # schema = settings.type_schema
114
+ def type_schema
115
+ {}.tap do |hsh|
116
+ config_tree.each do |k, v|
117
+ v.is_a?(AceConfig::Setting) ? (hsh[k] = v.type_schema) : hsh.merge!(schema)
118
+ end
119
+ end
120
+ end
121
+
122
+ # Converts the configuration tree into a hash.
123
+ #
124
+ # @return [Hash] The config tree as a hash.
125
+ # @example Converting to hash
126
+ # hash = settings.to_h
127
+ def to_h
128
+ {}.tap do |hsh|
129
+ config_tree.each do |k, v|
130
+ hsh[k] = v.is_a?(AceConfig::Setting) ? v.to_h : v
131
+ end
132
+ end
133
+ end
134
+
135
+ # Converts the configuration tree into YAML format.
136
+ #
137
+ # @param dump [String, nil] Optional file path to dump the YAML.
138
+ # @return [String, nil] The YAML string or nil if dumped to a file.
139
+ # @example Converting to YAML
140
+ # yaml_string = settings.to_yaml
141
+ # settings.to_yaml(dump: "config.yml") # Dumps to a file
142
+ def to_yaml(dump: nil)
143
+ yaml = to_h.to_yaml
144
+ dump ? File.write(dump, yaml) : yaml
145
+ end
146
+
147
+ # Converts the configuration tree into JSON format.
148
+ #
149
+ # @return [String] The JSON representation of the configuration tree.
150
+ # @example Converting to JSON
151
+ # json_string = settings.to_json
152
+ def to_json(*_args)
153
+ to_h.to_json
154
+ end
155
+
156
+ protected
157
+
158
+ # @return [Hash] The schema of configuration types.
159
+ attr_reader :schema
160
+
161
+ private
162
+
163
+ # @return [Hash] The tree structure of configuration settings.
164
+ attr_reader :config_tree
165
+
166
+ # Creates a new node in the configuration tree.
167
+ #
168
+ # @param node [Symbol] The name of the node to create.
169
+ # @yield [Setting] A block to configure the new setting.
170
+ # @return [Setting] The newly created setting node.
171
+ def create_new_node(node, &block)
172
+ new_node = AceConfig::Setting.new(&block)
173
+ config_tree[node] = new_node
174
+ define_node_methods(node)
175
+ end
176
+
177
+ # Defines singleton methods for the given node.
178
+ #
179
+ # @param node [Symbol] The name of the node to define methods for.
180
+ def define_node_methods(node)
181
+ define_singleton_method(node) do |*_args, &node_block|
182
+ if node_block
183
+ config_tree[node].instance_eval(&node_block)
184
+ else
185
+ config_tree[node]
186
+ end
187
+ end
188
+ end
189
+
190
+ # Extracts the setting information from the provided input.
191
+ #
192
+ # @param stngs [Symbol, Hash] The setting name or a hash containing the setting name and value.
193
+ # @return [Hash] A hash containing the setting name and its corresponding value.
194
+ def extract_setting_info(stngs)
195
+ val = nil
196
+ name = stngs if stngs.is_a?(Symbol)
197
+ name, val = stngs.to_a.first if stngs.is_a?(Hash)
198
+
199
+ { name: name, value: val }
200
+ end
201
+
202
+ # Validates the setting value against the expected type.
203
+ #
204
+ # @param stng_val [Object] The value of the setting to validate.
205
+ # @param stng_type [Symbol] The expected type of the setting.
206
+ # @raise [SettingTypeError] If the setting value does not match the expected type.
207
+ def validate_setting!(stng_val, stng_type)
208
+ is_valid = AceConfig::TypeChecker.call(stng_val, type: stng_type)
209
+ raise AceConfig::SettingTypeError.new(stng_type, stng_val) unless !stng_val || is_valid
210
+ end
211
+
212
+ # Sets the configuration for a given setting name, value, and type.
213
+ #
214
+ # @param stng_name [Symbol] The name of the setting to configure.
215
+ # @param stng_val [Object] The value to assign to the setting.
216
+ # @param stng_type [Symbol] The type of the setting.
217
+ def set_configuration(stng_name, stng_val, stng_type)
218
+ schema[stng_name] = stng_type
219
+ config_tree[stng_name] = stng_val
220
+ return if respond_to?(stng_name)
221
+
222
+ define_singleton_method(stng_name) do |value = nil, type: nil|
223
+ if value
224
+ config(**{ stng_name => value, type: type })
225
+ else
226
+ config_tree[stng_name]
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AceConfig module provides functionality for managing AceConfig features.
4
+ module AceConfig
5
+ # This class is responsible for type checking in the Ace configuration.
6
+ class TypeChecker
7
+ class << self
8
+ # Calls the appropriate validation method based on the type.
9
+ #
10
+ # @param value [Object] The value to validate.
11
+ # @param type [Symbol, Array<Symbol, Class>, Class] The type(s) to validate against.
12
+ # @return [Boolean] True if the value matches the type, false otherwise.
13
+ # @raise [TypeCheckerError] if the type is unsupported or not defined.
14
+ #
15
+ # @example
16
+ # TypeChecker.call(1, type: :int) # => true
17
+ # TypeChecker.call(1, type: :numeric) # => true
18
+ # TypeChecker.call("hello", type: [:str, Integer]) # => true
19
+ # TypeChecker.call(CustomClass.new, type: CustomClass) # => true
20
+ def call(value, type:, **_opts)
21
+ case type
22
+ when Symbol
23
+ base_type(value, fetch_type(type))
24
+ when Array
25
+ one_of(value, type)
26
+ else
27
+ custom_type(value, type)
28
+ end
29
+ end
30
+
31
+ # Validates if value matches the base type.
32
+ #
33
+ # @param value [Object] the value to validate
34
+ # @param type [Class] the type to validate against
35
+ # @return [Boolean] true if the value matches the base type
36
+ # @raise [TypeCheckerError] if the type is unsupported or not defined.
37
+ #
38
+ # @example
39
+ # TypeChecker.base_type(1, Integer) # => true
40
+ # TypeChecker.base_type(1, [:int, :float, :big_decimal]) # => true
41
+ def base_type(value, type)
42
+ type = fetch_type(type) if type.is_a?(Symbol)
43
+
44
+ type.is_a?(Array) ? one_of(value, type) : value.is_a?(type)
45
+ end
46
+
47
+ # Checks if value matches any type in the array.
48
+ #
49
+ # @param value [Object] the value to validate
50
+ # @param array_type [Array<Symbol, Class>] the array of types to validate against
51
+ # @return [Boolean] true if the value matches any type in the array
52
+ #
53
+ # @example
54
+ # TypeChecker.one_of(1, [Integer, :str]) # => true
55
+ # TypeChecker.one_of("hello", [Integer, String, :sym]) # => true
56
+ # TypeChecker.one_of(nil, [:null, Integer, String]) # => true
57
+ # TypeChecker.one_of(1.5, [:int, :float, :big_decimal]) # => true
58
+ def one_of(value, array_type)
59
+ array_type.any? { |type| type.is_a?(Symbol) ? base_type(value, type) : custom_type(value, type) }
60
+ end
61
+
62
+ # Validates if value is of the specified custom type.
63
+ #
64
+ # @param value [Object] the value to validate
65
+ # @param type [Class] the custom type to validate against
66
+ # @return [Boolean] true if the value is of the specified custom type
67
+ #
68
+ # @example
69
+ # TypeChecker.custom_type(1, Integer) # => true
70
+ # TypeChecker.custom_type(CustomClass.new, CustomClass) # => true
71
+ def custom_type(value, type)
72
+ value.is_a?(type)
73
+ end
74
+
75
+ # Fetches the basic type from the type map.
76
+ #
77
+ # @param type [Symbol] the type to fetch
78
+ # @return [Class] the corresponding basic type
79
+ # @raise [TypeCheckerError] if the type does not exist in TYPE_MAP
80
+ #
81
+ # @example
82
+ # TypeChecker.fetch_type(:int) # => Integer
83
+ # TypeChecker.fetch_type(:null) # => NilClass
84
+ # TypeChecker.fetch_type(:bool) # => [:truthy, :falsy]
85
+ def fetch_type(type)
86
+ basic_type = TypeMap.get(type)
87
+ raise AceConfig::TypeCheckerError, type unless basic_type
88
+
89
+ basic_type
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "date"
5
+
6
+ # AceConfig module provides functionality for managing AceConfig features.
7
+ module AceConfig
8
+ # A class that maps type symbols to their corresponding Ruby classes and provides
9
+ # predefined collections of type symbols for various categories.
10
+ #
11
+ # This class allows retrieval of Ruby classes based on type symbols and provides
12
+ # methods to access collections of specific type symbols such as booleans, numerics,
13
+ # and chronological types.
14
+ class TypeMap
15
+ TYPE_MAP = {
16
+ # base types
17
+ int: Integer,
18
+ str: String,
19
+ sym: Symbol,
20
+ null: NilClass,
21
+ true_class: TrueClass,
22
+ false_class: FalseClass,
23
+ # data structures
24
+ hash: Hash,
25
+ array: Array,
26
+ # numeric types
27
+ big_decimal: BigDecimal,
28
+ float: Float,
29
+ complex: Complex,
30
+ rational: Rational,
31
+ # time types
32
+ date: Date,
33
+ date_time: DateTime,
34
+ time: Time,
35
+ # any type
36
+ any: Object,
37
+ # composite types
38
+ bool: %i[true_class false_class],
39
+ numeric: %i[int float big_decimal],
40
+ kernel_num: %i[int float big_decimal complex rational],
41
+ chrono: %i[date date_time time]
42
+ }.freeze
43
+
44
+ # Retrieves the Ruby class associated with a given type symbol.
45
+ #
46
+ # @param type [Symbol] The type symbol to look up. Must be one of the keys in TYPE_MAP.
47
+ # @return [Class, nil] The corresponding Ruby class or nil if the type symbol is not found.
48
+ #
49
+ # @example
50
+ # TypeMap.get(:int) # => Integer
51
+ # TypeMap.get(:str) # => String
52
+ # TypeMap.get(:unknown) # => nil
53
+ def self.get(type)
54
+ TYPE_MAP[type]
55
+ end
56
+
57
+ # Returns an array of all type symbols defined in TYPE_MAP.
58
+ #
59
+ # @return [Array<Symbol>] An array containing all keys from TYPE_MAP.
60
+ #
61
+ # @example
62
+ # TypeMap.list_types # => [:int, :str, :sym, :null, :true_class, :false_class,
63
+ # :hash, :array, :big_decimal, :float, :complex,
64
+ # :rational, :date, :date_time, :time, :any,
65
+ # :bool, :numeric, :kernel_num, :chrono]
66
+ def self.list_types
67
+ TYPE_MAP.keys
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AceConfig
4
+ VERSION = "0.1.0"
5
+ end
data/lib/ace_config.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace_config/version"
4
+ require "ace_config/errors"
5
+ require "ace_config/type_map"
6
+ require "ace_config/setting"
7
+ require "ace_config/type_checker"
8
+ require "ace_config/configuration"
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ace-config
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta.rc1b
5
+ platform: ruby
6
+ authors:
7
+ - yurigitsu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-09-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bigdecimal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Managing, configurations with type validation, configirations load and
28
+ export support.
29
+ email:
30
+ - yurigi.pro@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG.md
36
+ - LICENSE
37
+ - README.md
38
+ - ace-config.gemspec
39
+ - lib/ace_config.rb
40
+ - lib/ace_config/configuration.rb
41
+ - lib/ace_config/errors.rb
42
+ - lib/ace_config/setting.rb
43
+ - lib/ace_config/type_checker.rb
44
+ - lib/ace_config/type_map.rb
45
+ - lib/ace_config/version.rb
46
+ homepage: https://github.com/yurigitsu/ace-config
47
+ licenses:
48
+ - MIT
49
+ metadata:
50
+ repo_homepage: https://github.com/yurigitsu/
51
+ allowed_push_host: https://rubygems.org
52
+ homepage_uri: https://github.com/yurigitsu/ace-config
53
+ changelog_uri: https://github.com/yurigitsu/ace-config/blob/main/CHANGELOG.md
54
+ source_code_uri: https://github.com/yurigitsu/ace-config
55
+ bug_tracker_uri: https://github.com/yurigitsu/ace-config/issues
56
+ rubygems_mfa_required: 'true'
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 3.0.0
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.5.20
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: A flexible and easy-to-use configuration handling gem.
76
+ test_files: []