optify-config 1.4.2

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.
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "optify_ruby"
3
+ version = "0.9.0"
4
+ edition = "2021"
5
+
6
+ description = "optify bindings for Ruby"
7
+ homepage = "https://github.com/juharris/optify/tree/main/ruby"
8
+ license = "MIT"
9
+ readme = "README.md"
10
+ repository = "https://github.com/juharris/optify"
11
+
12
+ categories = ["config"]
13
+ keywords = ["configuration", "options", "ruby"]
14
+
15
+ exclude = [
16
+ "tests/*",
17
+ ]
18
+
19
+ [lib]
20
+ crate-type = ["cdylib"]
21
+
22
+ [dependencies]
23
+ magnus = "0.7.1"
24
+ optify = { path = "../../../../rust/optify", version = "0.9.0" }
25
+ rb-sys = { version = "*", default-features = false, features = ["ruby-static"] }
26
+ serde_json = "1.0.140"
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ require 'mkmf'
5
+ require 'rb_sys/mkmf'
6
+
7
+ create_rust_makefile('optify_ruby/optify_ruby') do |r|
8
+ r.profile = ENV.fetch('RB_SYS_CARGO_PROFILE', :dev).to_sym
9
+ puts "RB_SYS_CARGO_PROFILE: #{r.profile}"
10
+ end
@@ -0,0 +1,445 @@
1
+ use magnus::{function, method, prelude::*, wrap, Object, Ruby};
2
+ use optify::builder::OptionsProviderBuilder;
3
+ use optify::builder::OptionsRegistryBuilder;
4
+ use optify::builder::OptionsWatcherBuilder;
5
+ use optify::convert_to_str_slice;
6
+ use optify::provider::GetOptionsPreferences;
7
+ use optify::provider::OptionsProvider;
8
+ use optify::provider::OptionsRegistry;
9
+ use optify::provider::OptionsWatcher;
10
+ use optify::schema::metadata::OptionsMetadata;
11
+ use std::cell::RefCell;
12
+
13
+ #[derive(Clone)]
14
+ #[wrap(class = "Optify::GetOptionsPreferences")]
15
+ struct MutGetOptionsPreferences(RefCell<GetOptionsPreferences>);
16
+
17
+ impl MutGetOptionsPreferences {
18
+ fn new() -> Self {
19
+ Self(RefCell::new(GetOptionsPreferences {
20
+ overrides_json: None,
21
+ skip_feature_name_conversion: false,
22
+ }))
23
+ }
24
+
25
+ // Overrides Section
26
+ fn has_overrides(&self) -> bool {
27
+ self.0.borrow().overrides_json.is_some()
28
+ }
29
+
30
+ fn set_overrides_json(&self, overrides: Option<String>) {
31
+ self.0.borrow_mut().overrides_json = overrides;
32
+ }
33
+
34
+ fn get_overrides_json(&self) -> Option<String> {
35
+ self.0.borrow().overrides_json.clone()
36
+ }
37
+
38
+ // Skip Feature Name Conversion Section
39
+ fn set_skip_feature_name_conversion(&self, value: bool) {
40
+ self.0.borrow_mut().skip_feature_name_conversion = value;
41
+ }
42
+
43
+ fn skip_feature_name_conversion(&self) -> bool {
44
+ self.0.borrow().skip_feature_name_conversion
45
+ }
46
+ }
47
+
48
+ #[wrap(class = "Optify::OptionsProvider")]
49
+ struct WrappedOptionsProvider(RefCell<OptionsProvider>);
50
+
51
+ fn convert_metadata(metadata: &OptionsMetadata) -> String {
52
+ serde_json::to_string(metadata).unwrap()
53
+ }
54
+
55
+ impl WrappedOptionsProvider {
56
+ // These methods cannot accept `str`s because of how magnus works.
57
+ // Return the JSON as a string so that it can be deserialized easily into a specific immutable class in Ruby.
58
+ fn get_all_options_json(
59
+ ruby: &Ruby,
60
+ rb_self: &Self,
61
+ feature_names: Vec<String>,
62
+ preferences: &MutGetOptionsPreferences,
63
+ ) -> Result<String, magnus::Error> {
64
+ let _preferences = convert_preferences(preferences);
65
+ let features = convert_to_str_slice!(feature_names);
66
+ match rb_self
67
+ .0
68
+ .borrow()
69
+ .get_all_options(&features, &None, &_preferences)
70
+ {
71
+ Ok(options) => Ok(options.to_string()),
72
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
73
+ }
74
+ }
75
+
76
+ fn get_canonical_feature_name(
77
+ ruby: &Ruby,
78
+ rb_self: &Self,
79
+ feature_name: String,
80
+ ) -> Result<String, magnus::Error> {
81
+ rb_self
82
+ .0
83
+ .borrow()
84
+ .get_canonical_feature_name(&feature_name)
85
+ .map_err(|e| magnus::Error::new(ruby.exception_exception(), e))
86
+ }
87
+
88
+ fn get_canonical_feature_names(
89
+ ruby: &Ruby,
90
+ rb_self: &Self,
91
+ feature_names: Vec<String>,
92
+ ) -> Result<Vec<String>, magnus::Error> {
93
+ let features = convert_to_str_slice!(feature_names);
94
+ rb_self
95
+ .0
96
+ .borrow()
97
+ .get_canonical_feature_names(&features)
98
+ .map_err(|e| magnus::Error::new(ruby.exception_exception(), e))
99
+ }
100
+
101
+ fn get_feature_metadata_json(&self, canonical_feature_name: String) -> Option<String> {
102
+ self.0
103
+ .borrow()
104
+ .get_feature_metadata(&canonical_feature_name)
105
+ .map(|metadata| convert_metadata(&metadata))
106
+ }
107
+
108
+ fn get_features(&self) -> Vec<String> {
109
+ self.0.borrow().get_features()
110
+ }
111
+
112
+ // Return a string because it wasn't clear how to return a type defined in Rust despite looking at docs and trying a few examples.
113
+ fn get_features_with_metadata_json(&self) -> String {
114
+ serde_json::to_string(&self.0.borrow().get_features_with_metadata()).unwrap()
115
+ }
116
+
117
+ // Return a string because it wasn't clear how to return a type defined in Rust despite looking at docs and trying a few examples.
118
+ fn get_options_json(
119
+ ruby: &Ruby,
120
+ rb_self: &Self,
121
+ key: String,
122
+ feature_names: Vec<String>,
123
+ ) -> Result<String, magnus::Error> {
124
+ let features = convert_to_str_slice!(feature_names);
125
+ match rb_self
126
+ .0
127
+ .borrow()
128
+ .get_options_with_preferences(&key, &features, &None, &None)
129
+ {
130
+ Ok(options) => Ok(options.to_string()),
131
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
132
+ }
133
+ }
134
+
135
+ fn get_options_json_with_preferences(
136
+ ruby: &Ruby,
137
+ rb_self: &Self,
138
+ key: String,
139
+ feature_names: Vec<String>,
140
+ preferences: &MutGetOptionsPreferences,
141
+ ) -> Result<String, magnus::Error> {
142
+ let _preferences = convert_preferences(preferences);
143
+ let features = convert_to_str_slice!(feature_names);
144
+ match rb_self
145
+ .0
146
+ .borrow()
147
+ .get_options_with_preferences(&key, &features, &None, &_preferences)
148
+ {
149
+ Ok(options) => Ok(options.to_string()),
150
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
151
+ }
152
+ }
153
+ }
154
+
155
+ fn convert_preferences(preferences: &MutGetOptionsPreferences) -> Option<GetOptionsPreferences> {
156
+ Some(optify::provider::GetOptionsPreferences {
157
+ overrides_json: preferences.get_overrides_json(),
158
+ skip_feature_name_conversion: preferences.skip_feature_name_conversion(),
159
+ })
160
+ }
161
+
162
+ #[derive(Clone)]
163
+ #[wrap(class = "Optify::OptionsProviderBuilder")]
164
+ struct WrappedOptionsProviderBuilder(RefCell<OptionsProviderBuilder>);
165
+
166
+ impl WrappedOptionsProviderBuilder {
167
+ fn new() -> Self {
168
+ Self(RefCell::new(OptionsProviderBuilder::new()))
169
+ }
170
+
171
+ fn add_directory(
172
+ ruby: &Ruby,
173
+ rb_self: &Self,
174
+ directory: String,
175
+ ) -> Result<WrappedOptionsProviderBuilder, magnus::Error> {
176
+ let path = std::path::Path::new(&directory);
177
+ match rb_self.0.borrow_mut().add_directory(path) {
178
+ Ok(builder) => Ok(WrappedOptionsProviderBuilder(RefCell::new(builder.clone()))),
179
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
180
+ }
181
+ }
182
+
183
+ fn build(ruby: &Ruby, rb_self: &Self) -> Result<WrappedOptionsProvider, magnus::Error> {
184
+ match rb_self.0.borrow_mut().build() {
185
+ Ok(provider) => Ok(WrappedOptionsProvider(RefCell::new(provider))),
186
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
187
+ }
188
+ }
189
+ }
190
+
191
+ #[wrap(class = "Optify::OptionsWatcher")]
192
+ struct WrappedOptionsWatcher(RefCell<OptionsWatcher>);
193
+
194
+ impl WrappedOptionsWatcher {
195
+ fn get_all_options_json(
196
+ ruby: &Ruby,
197
+ rb_self: &Self,
198
+ feature_names: Vec<String>,
199
+ preferences: &MutGetOptionsPreferences,
200
+ ) -> Result<String, magnus::Error> {
201
+ let _preferences = convert_preferences(preferences);
202
+ let features = convert_to_str_slice!(feature_names);
203
+ match rb_self
204
+ .0
205
+ .borrow()
206
+ .get_all_options(&features, &None, &_preferences)
207
+ {
208
+ Ok(options) => Ok(options.to_string()),
209
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
210
+ }
211
+ }
212
+
213
+ fn get_canonical_feature_name(
214
+ ruby: &Ruby,
215
+ rb_self: &Self,
216
+ feature_name: String,
217
+ ) -> Result<String, magnus::Error> {
218
+ rb_self
219
+ .0
220
+ .borrow()
221
+ .get_canonical_feature_name(&feature_name)
222
+ .map_err(|e| magnus::Error::new(ruby.exception_exception(), e))
223
+ }
224
+
225
+ fn get_canonical_feature_names(
226
+ ruby: &Ruby,
227
+ rb_self: &Self,
228
+ feature_names: Vec<String>,
229
+ ) -> Result<Vec<String>, magnus::Error> {
230
+ let features = convert_to_str_slice!(feature_names);
231
+ rb_self
232
+ .0
233
+ .borrow()
234
+ .get_canonical_feature_names(&features)
235
+ .map_err(|e| magnus::Error::new(ruby.exception_exception(), e))
236
+ }
237
+
238
+ fn get_feature_metadata_json(&self, canonical_feature_name: String) -> Option<String> {
239
+ self.0
240
+ .borrow()
241
+ .get_feature_metadata(&canonical_feature_name)
242
+ .map(|metadata| convert_metadata(&metadata))
243
+ }
244
+
245
+ fn get_features(&self) -> Vec<String> {
246
+ self.0.borrow().get_features()
247
+ }
248
+
249
+ fn get_features_with_metadata_json(&self) -> String {
250
+ serde_json::to_string(&self.0.borrow().get_features_with_metadata()).unwrap()
251
+ }
252
+
253
+ fn get_options_json(
254
+ ruby: &Ruby,
255
+ rb_self: &Self,
256
+ key: String,
257
+ feature_names: Vec<String>,
258
+ ) -> Result<String, magnus::Error> {
259
+ let features = convert_to_str_slice!(feature_names);
260
+ match rb_self
261
+ .0
262
+ .borrow()
263
+ .get_options_with_preferences(&key, &features, &None, &None)
264
+ {
265
+ Ok(options) => Ok(options.to_string()),
266
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
267
+ }
268
+ }
269
+
270
+ fn get_options_json_with_preferences(
271
+ ruby: &Ruby,
272
+ rb_self: &Self,
273
+ key: String,
274
+ feature_names: Vec<String>,
275
+ preferences: &MutGetOptionsPreferences,
276
+ ) -> Result<String, magnus::Error> {
277
+ let _preferences = convert_preferences(preferences);
278
+ let features = convert_to_str_slice!(feature_names);
279
+ match rb_self
280
+ .0
281
+ .borrow()
282
+ .get_options_with_preferences(&key, &features, &None, &_preferences)
283
+ {
284
+ Ok(options) => Ok(options.to_string()),
285
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
286
+ }
287
+ }
288
+
289
+ fn last_modified(&self) -> std::time::SystemTime {
290
+ self.0.borrow().last_modified()
291
+ }
292
+ }
293
+
294
+ #[derive(Clone)]
295
+ #[wrap(class = "Optify::OptionsWatcherBuilder")]
296
+ struct WrappedOptionsWatcherBuilder(RefCell<OptionsWatcherBuilder>);
297
+
298
+ impl WrappedOptionsWatcherBuilder {
299
+ fn new() -> Self {
300
+ Self(RefCell::new(OptionsWatcherBuilder::new()))
301
+ }
302
+
303
+ fn add_directory(
304
+ ruby: &Ruby,
305
+ rb_self: &Self,
306
+ directory: String,
307
+ ) -> Result<WrappedOptionsWatcherBuilder, magnus::Error> {
308
+ let path = std::path::Path::new(&directory);
309
+ match rb_self.0.borrow_mut().add_directory(path) {
310
+ Ok(builder) => Ok(WrappedOptionsWatcherBuilder(RefCell::new(builder.clone()))),
311
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
312
+ }
313
+ }
314
+
315
+ fn build(ruby: &Ruby, rb_self: &Self) -> Result<WrappedOptionsWatcher, magnus::Error> {
316
+ match rb_self.0.borrow_mut().build() {
317
+ Ok(provider) => Ok(WrappedOptionsWatcher(RefCell::new(provider))),
318
+ Err(e) => Err(magnus::Error::new(ruby.exception_exception(), e)),
319
+ }
320
+ }
321
+ }
322
+
323
+ #[magnus::init]
324
+ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
325
+ let module = ruby.define_module("Optify")?;
326
+
327
+ let builder_class = module.define_class("OptionsProviderBuilder", ruby.class_object())?;
328
+
329
+ builder_class
330
+ .define_singleton_method("new", function!(WrappedOptionsProviderBuilder::new, 0))?;
331
+ builder_class.define_method(
332
+ "add_directory",
333
+ method!(WrappedOptionsProviderBuilder::add_directory, 1),
334
+ )?;
335
+ builder_class.define_method("build", method!(WrappedOptionsProviderBuilder::build, 0))?;
336
+
337
+ let provider_class = module.define_class("OptionsProvider", ruby.class_object())?;
338
+ provider_class.define_method("features", method!(WrappedOptionsProvider::get_features, 0))?;
339
+ provider_class.define_method(
340
+ "get_all_options_json",
341
+ method!(WrappedOptionsProvider::get_all_options_json, 2),
342
+ )?;
343
+ provider_class.define_method(
344
+ "get_canonical_feature_name",
345
+ method!(WrappedOptionsProvider::get_canonical_feature_name, 1),
346
+ )?;
347
+ provider_class.define_method(
348
+ "get_options_json",
349
+ method!(WrappedOptionsProvider::get_options_json, 2),
350
+ )?;
351
+ provider_class.define_method(
352
+ "get_options_json_with_preferences",
353
+ method!(WrappedOptionsProvider::get_options_json_with_preferences, 3),
354
+ )?;
355
+
356
+ // Private methods for internal use.
357
+ provider_class.define_private_method(
358
+ "_get_canonical_feature_names",
359
+ method!(WrappedOptionsProvider::get_canonical_feature_names, 1),
360
+ )?;
361
+ provider_class.define_private_method(
362
+ "features_with_metadata_json",
363
+ method!(WrappedOptionsProvider::get_features_with_metadata_json, 0),
364
+ )?;
365
+ provider_class.define_private_method(
366
+ "get_feature_metadata_json",
367
+ method!(WrappedOptionsProvider::get_feature_metadata_json, 1),
368
+ )?;
369
+
370
+ let get_options_preferences_class =
371
+ module.define_class("GetOptionsPreferences", ruby.class_object())?;
372
+ get_options_preferences_class
373
+ .define_singleton_method("new", function!(MutGetOptionsPreferences::new, 0))?;
374
+ get_options_preferences_class
375
+ .define_method("dup", method!(MutGetOptionsPreferences::clone, 0))?;
376
+ get_options_preferences_class.define_method(
377
+ "overrides?",
378
+ method!(MutGetOptionsPreferences::has_overrides, 0),
379
+ )?;
380
+ get_options_preferences_class.define_method(
381
+ "overrides_json=",
382
+ method!(MutGetOptionsPreferences::set_overrides_json, 1),
383
+ )?;
384
+ get_options_preferences_class.define_method(
385
+ "skip_feature_name_conversion=",
386
+ method!(
387
+ MutGetOptionsPreferences::set_skip_feature_name_conversion,
388
+ 1
389
+ ),
390
+ )?;
391
+ get_options_preferences_class.define_method(
392
+ "skip_feature_name_conversion",
393
+ method!(MutGetOptionsPreferences::skip_feature_name_conversion, 0),
394
+ )?;
395
+
396
+ let watcher_builder_class =
397
+ module.define_class("OptionsWatcherBuilder", ruby.class_object())?;
398
+ watcher_builder_class
399
+ .define_singleton_method("new", function!(WrappedOptionsWatcherBuilder::new, 0))?;
400
+ watcher_builder_class.define_method(
401
+ "add_directory",
402
+ method!(WrappedOptionsWatcherBuilder::add_directory, 1),
403
+ )?;
404
+ watcher_builder_class
405
+ .define_method("build", method!(WrappedOptionsWatcherBuilder::build, 0))?;
406
+
407
+ let watcher_class = module.define_class("OptionsWatcher", ruby.class_object())?;
408
+ watcher_class.define_method("features", method!(WrappedOptionsWatcher::get_features, 0))?;
409
+ watcher_class.define_method(
410
+ "get_all_options_json",
411
+ method!(WrappedOptionsWatcher::get_all_options_json, 2),
412
+ )?;
413
+ watcher_class.define_method(
414
+ "get_canonical_feature_name",
415
+ method!(WrappedOptionsWatcher::get_canonical_feature_name, 1),
416
+ )?;
417
+ watcher_class.define_method(
418
+ "get_options_json",
419
+ method!(WrappedOptionsWatcher::get_options_json, 2),
420
+ )?;
421
+ watcher_class.define_method(
422
+ "get_options_json_with_preferences",
423
+ method!(WrappedOptionsWatcher::get_options_json_with_preferences, 3),
424
+ )?;
425
+ watcher_class.define_method(
426
+ "last_modified",
427
+ method!(WrappedOptionsWatcher::last_modified, 0),
428
+ )?;
429
+
430
+ // Private methods for internal use.
431
+ watcher_class.define_private_method(
432
+ "features_with_metadata_json",
433
+ method!(WrappedOptionsWatcher::get_features_with_metadata_json, 0),
434
+ )?;
435
+ watcher_class.define_private_method(
436
+ "_get_canonical_feature_names",
437
+ method!(WrappedOptionsWatcher::get_canonical_feature_names, 1),
438
+ )?;
439
+ watcher_class.define_private_method(
440
+ "get_feature_metadata_json",
441
+ method!(WrappedOptionsWatcher::get_feature_metadata_json, 1),
442
+ )?;
443
+
444
+ Ok(())
445
+ }
data/lib/optify.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ # The implementation to use directly Ruby and with types declared.
5
+ require_relative 'optify_ruby/get_options_preferences'
6
+ require_relative 'optify_ruby/implementation'
7
+ require_relative 'optify_ruby/watcher_implementation'
8
+
9
+ # The implementation in Rust which redefines some methods.
10
+ # This yields some warnings, but we should redeclare the methods in Ruby to help with type checking anyway.
11
+ # Warnings about redefining methods are normal and can be ignored
12
+ # because the implementations in Ruby are not implemented and only exist to help with type checking.
13
+ # Ideally we do:
14
+ # `require_relative 'optify_ruby/optify_ruby'`
15
+ # but that doesn't work when building for multiple versions of Ruby.
16
+ # So we have to do this which is similar to something from 'https://github.com/matsadler/halton-rb/blob/main/lib/halton.rb'.
17
+ begin
18
+ ruby_version = T.must(RUBY_VERSION.match(/\d+\.\d+/))[0]
19
+ require_relative "optify_ruby/#{ruby_version}/optify_ruby"
20
+ rescue LoadError
21
+ begin
22
+ require_relative 'optify_ruby/optify_ruby'
23
+ rescue LoadError # Cargo Builder in RubyGems < 3.4.6 doesn't install to dir
24
+ require_relative 'optify_ruby.so'
25
+ end
26
+ end
27
+
28
+ require_relative 'optify_ruby/base_config'
@@ -0,0 +1,99 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ require 'tapioca'
6
+
7
+ module Optify
8
+ # A base class for classes from configuration files.
9
+ # Classes that derive from this can easily be used with `Optify::OptionsProvider.get_options`
10
+ # because they will have an implementation of `from_hash` that works recursively.
11
+ # This class is a work in progress with minimal error handling
12
+ # and doesn't handle certain cases such as nilable types yet.
13
+ # It may be moved to another gem in the future.
14
+ class BaseConfig
15
+ extend T::Sig
16
+ extend T::Helpers
17
+ abstract!
18
+
19
+ # Create a new immutable instance of the class from a hash.
20
+ #
21
+ # This is a class method that so that it can set members with private setters.
22
+ # @param hash The hash to create the instance from.
23
+ # @return The new instance.
24
+ #: (Hash[untyped, untyped] hash) -> instance
25
+ def self.from_hash(hash)
26
+ instance = new
27
+
28
+ hash.each do |key, value|
29
+ sig_return_type = T::Utils.signature_for_method(instance_method(key)).return_type
30
+ value = _convert_value(value, sig_return_type)
31
+ instance.instance_variable_set("@#{key}", value)
32
+ end
33
+
34
+ instance.freeze
35
+ end
36
+
37
+ #: (untyped value, untyped type) -> untyped
38
+ def self._convert_value(value, type)
39
+ if type.is_a?(T::Types::Untyped)
40
+ # No preferred type is given, so return the value as is.
41
+ return value
42
+ end
43
+
44
+ return value.to_sym if type.is_a?(T::Types::Simple) && type.raw_type == Symbol
45
+
46
+ case value
47
+ when Array
48
+ # Handle `T.nilable(T::Array[...])`
49
+ type = type.unwrap_nilable if type.respond_to?(:unwrap_nilable)
50
+ inner_type = type.type
51
+ return value.map { |v| _convert_value(v, inner_type) }.freeze
52
+ when Hash
53
+ # Handle `T.nilable(T::Hash[...])` and `T.any(...)`.
54
+ # We used to use `type = type.unwrap_nilable if type.respond_to?(:unwrap_nilable)`, but it's not needed now that we handle `T.any(...)`
55
+ # because using `.types` works for both cases.
56
+ if type.respond_to?(:types)
57
+ # Find a type that works for the hash.
58
+ type.types.each do |t|
59
+ return _convert_hash(value, t).freeze
60
+ rescue StandardError
61
+ # Ignore and try the next type.
62
+ end
63
+ raise TypeError, "Could not convert hash: #{value} to #{type}."
64
+ end
65
+ return _convert_hash(value, type).freeze
66
+ end
67
+
68
+ # It would be nice to validate that the value is of the correct type here.
69
+ # For example that a string is a string and an Integer is an Integer.
70
+ value
71
+ end
72
+
73
+ #: (Hash[untyped, untyped] hash, untyped type) -> untyped
74
+ def self._convert_hash(hash, type)
75
+ if type.respond_to?(:raw_type)
76
+ # There is an object for the hash.
77
+ # It could be a custom class, a String, or maybe something else.
78
+ type_for_hash = type.raw_type
79
+ return type_for_hash.from_hash(hash) if type_for_hash.respond_to?(:from_hash)
80
+ elsif type.is_a?(T::Types::TypedHash)
81
+ # The hash should be a hash, but the values might be objects to convert.
82
+ type_for_keys = type.keys
83
+
84
+ convert_key = if type_for_keys.is_a?(T::Types::Simple) && type_for_keys.raw_type == Symbol
85
+ lambda(&:to_sym)
86
+ else
87
+ lambda(&:itself)
88
+ end
89
+
90
+ type_for_values = type.values
91
+ return hash.map { |k, v| [convert_key.call(k), _convert_value(v, type_for_values)] }.to_h
92
+ end
93
+
94
+ raise TypeError, "Could not convert hash #{hash} to `#{type}`."
95
+ end
96
+
97
+ private_class_method :_convert_hash, :_convert_value
98
+ end
99
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Optify
5
+ # Preferences for `get_options`.
6
+ class GetOptionsPreferences
7
+ # @param overrides [Hash]
8
+ #: (Hash[untyped, untyped] overrides) -> void
9
+ def overrides=(overrides)
10
+ self.overrides_json = overrides.to_json
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'json'
5
+
6
+ require 'sorbet-runtime'
7
+
8
+ require_relative './base_config'
9
+ require_relative './options_metadata'
10
+ require_relative './provider_module'
11
+
12
+ # Tools for working with configurations declared in files.
13
+ module Optify
14
+ # Options for caching.
15
+ # Only enabling or disabling caching is supported for now.
16
+ class CacheOptions < BaseConfig
17
+ end
18
+
19
+ # Provides configurations based on keys and enabled feature names.
20
+ class OptionsProvider
21
+ include ProviderModule
22
+
23
+ # TODO: Find a better way to proxy the methods with copying the parameters.
24
+
25
+ #: -> Hash[String, OptionsMetadata]
26
+ def features_with_metadata
27
+ _features_with_metadata
28
+ end
29
+
30
+ #: [Config] (String key, Array[String] feature_names, Class[Config] config_class, ?CacheOptions? cache_options, ?Optify::GetOptionsPreferences? preferences) -> Config
31
+ def get_options(key, feature_names, config_class, cache_options = nil, preferences = nil)
32
+ _get_options(key, feature_names, config_class, cache_options, preferences)
33
+ end
34
+
35
+ # (Optional) Eagerly initializes the cache.
36
+ # @return [OptionsProvider] `self`.
37
+ #: -> OptionsProvider
38
+ def init
39
+ _init
40
+ self
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ require_relative './base_config'
7
+
8
+ module Optify
9
+ # Information about a feature.
10
+ class OptionsMetadata < BaseConfig
11
+ sig { returns(T.nilable(T::Array[String])) }
12
+ attr_reader :aliases
13
+
14
+ sig { returns(T.untyped) }
15
+ attr_reader :details
16
+
17
+ sig { returns(String) }
18
+ attr_reader :name
19
+
20
+ sig { returns(T.nilable(String)) }
21
+ attr_reader :owners
22
+ end
23
+ end