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.
- checksums.yaml +7 -0
- data/ext/optify_ruby/Cargo.lock +917 -0
- data/ext/optify_ruby/Cargo.toml +26 -0
- data/ext/optify_ruby/extconf.rb +10 -0
- data/ext/optify_ruby/src/lib.rs +445 -0
- data/lib/optify.rb +28 -0
- data/lib/optify_ruby/base_config.rb +99 -0
- data/lib/optify_ruby/get_options_preferences.rb +13 -0
- data/lib/optify_ruby/implementation.rb +43 -0
- data/lib/optify_ruby/options_metadata.rb +23 -0
- data/lib/optify_ruby/provider_module.rb +120 -0
- data/lib/optify_ruby/watcher_implementation.rb +52 -0
- data/rbi/optify.rbi +226 -0
- data/sig/optify.rbs +157 -0
- metadata +155 -0
@@ -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,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
|