settingspec 0.2.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (29) hide show
  1. {settingspec-0.2.0 → settingspec-0.3.0}/Cargo.lock +1 -1
  2. {settingspec-0.2.0 → settingspec-0.3.0}/Cargo.toml +1 -1
  3. {settingspec-0.2.0 → settingspec-0.3.0}/PKG-INFO +1 -1
  4. {settingspec-0.2.0 → settingspec-0.3.0}/src/error.rs +6 -0
  5. {settingspec-0.2.0 → settingspec-0.3.0}/src/model.rs +4 -0
  6. settingspec-0.3.0/src/parser.rs +341 -0
  7. {settingspec-0.2.0 → settingspec-0.3.0}/src/profile.rs +4 -1
  8. settingspec-0.2.0/src/parser.rs +0 -186
  9. {settingspec-0.2.0 → settingspec-0.3.0}/LICENSE-APACHE +0 -0
  10. {settingspec-0.2.0 → settingspec-0.3.0}/LICENSE-MIT +0 -0
  11. {settingspec-0.2.0 → settingspec-0.3.0}/README.md +0 -0
  12. {settingspec-0.2.0 → settingspec-0.3.0}/pyproject.toml +0 -0
  13. {settingspec-0.2.0 → settingspec-0.3.0}/src/cli.rs +0 -0
  14. {settingspec-0.2.0 → settingspec-0.3.0}/src/envfile.rs +0 -0
  15. {settingspec-0.2.0 → settingspec-0.3.0}/src/filter.rs +0 -0
  16. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/common.rs +0 -0
  17. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/env.rs +0 -0
  18. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/javascript.rs +0 -0
  19. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/json.rs +0 -0
  20. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/lua.rs +0 -0
  21. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/mod.rs +0 -0
  22. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/python.rs +0 -0
  23. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/toml.rs +0 -0
  24. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/tree.rs +0 -0
  25. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/typescript.rs +0 -0
  26. {settingspec-0.2.0 → settingspec-0.3.0}/src/generator/yaml.rs +0 -0
  27. {settingspec-0.2.0 → settingspec-0.3.0}/src/lib.rs +0 -0
  28. {settingspec-0.2.0 → settingspec-0.3.0}/src/main.rs +0 -0
  29. {settingspec-0.2.0 → settingspec-0.3.0}/src/resolver.rs +0 -0
@@ -564,7 +564,7 @@ dependencies = [
564
564
 
565
565
  [[package]]
566
566
  name = "settingspec"
567
- version = "0.2.0"
567
+ version = "0.3.0"
568
568
  dependencies = [
569
569
  "anyhow",
570
570
  "assert_cmd",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "settingspec"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition = "2024"
5
5
  description = "One file for all environments, all languages, all submodules."
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: settingspec
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -6,6 +6,7 @@ pub enum SettingSpecError {
6
6
  InvalidToml(String),
7
7
  MissingSettingsSection,
8
8
  ReservedProfileInOptions,
9
+ ReservedKeywordConflict(String),
9
10
  UnknownProfileInSettings(String),
10
11
  ActiveProfileNotInOptions(String),
11
12
  NoProfileSelected,
@@ -39,6 +40,11 @@ impl fmt::Display for SettingSpecError {
39
40
  f,
40
41
  "Reserved profile identifier 'default' MUST NOT be in spec.profile.options"
41
42
  ),
43
+ Self::ReservedKeywordConflict(seg) => write!(
44
+ f,
45
+ "Reserved directive keyword '{}' cannot be used as a setting key segment or profile name",
46
+ seg
47
+ ),
42
48
  Self::UnknownProfileInSettings(p) => write!(
43
49
  f,
44
50
  "Unknown profile '{}' used in [settings] (not in spec.profile.options)",
@@ -3,6 +3,10 @@ use serde::{Deserialize, Deserializer, Serialize};
3
3
  use std::collections::BTreeMap;
4
4
  use toml::Value as TomlValue;
5
5
 
6
+ pub fn is_reserved_directive_keyword(name: &str) -> bool {
7
+ matches!(name, "val" | "env" | "null" | "tags")
8
+ }
9
+
6
10
  fn default_profile_key() -> String {
7
11
  "SETTINGSPEC_PROFILE".to_string()
8
12
  }
@@ -0,0 +1,341 @@
1
+ use crate::error::{Result, SettingSpecError};
2
+ use crate::model::*;
3
+ use serde::Deserialize;
4
+ use serde::de::IntoDeserializer;
5
+ use std::collections::BTreeMap;
6
+ use toml::Value as TomlValue;
7
+
8
+ #[derive(Debug, Deserialize)]
9
+ #[serde(deny_unknown_fields)]
10
+ pub struct SettingSpecSchema {
11
+ #[serde(default)]
12
+ pub spec: Option<SpecConfig>,
13
+ pub settings: Option<toml::Table>,
14
+ }
15
+
16
+ #[derive(Debug, Clone, Deserialize)]
17
+ #[serde(deny_unknown_fields)]
18
+ pub struct ProfileDeclarationSchema {
19
+ #[serde(default)]
20
+ pub val: Option<TomlValue>,
21
+ #[serde(default)]
22
+ pub env: Option<String>,
23
+ #[serde(default)]
24
+ pub null: Option<bool>,
25
+ #[serde(default)]
26
+ pub tags: Option<Vec<String>>,
27
+ }
28
+
29
+ pub fn parse_config_str(content: &str) -> Result<SettingSpecDocument> {
30
+ let schema: SettingSpecSchema =
31
+ toml::from_str(content).map_err(|e| SettingSpecError::InvalidToml(e.to_string()))?;
32
+
33
+ let spec = schema.spec.unwrap_or_default();
34
+
35
+ // Validate spec.profile.options
36
+ for opt in &spec.profile.options {
37
+ if opt == "default" {
38
+ return Err(SettingSpecError::ReservedProfileInOptions);
39
+ }
40
+ if is_reserved_directive_keyword(opt) {
41
+ return Err(SettingSpecError::ReservedKeywordConflict(opt.clone()));
42
+ }
43
+ }
44
+
45
+ if let Some(ref def) = spec.profile.default
46
+ && is_reserved_directive_keyword(def)
47
+ {
48
+ return Err(SettingSpecError::ReservedKeywordConflict(def.clone()));
49
+ }
50
+
51
+ for prof in spec.envfile.profiles.keys() {
52
+ if is_reserved_directive_keyword(prof) {
53
+ return Err(SettingSpecError::ReservedKeywordConflict(prof.clone()));
54
+ }
55
+ }
56
+
57
+ // Parse [settings] (REQUIRED)
58
+ let settings_table = schema
59
+ .settings
60
+ .ok_or(SettingSpecError::MissingSettingsSection)?;
61
+
62
+ if settings_table.is_empty() {
63
+ return Err(SettingSpecError::MissingSettingsSection);
64
+ }
65
+
66
+ let mut settings: BTreeMap<String, Setting> = BTreeMap::new();
67
+ let mut current_path = Vec::new();
68
+ extract_settings(
69
+ &settings_table,
70
+ &mut current_path,
71
+ &spec.profile.options,
72
+ &mut settings,
73
+ )?;
74
+
75
+ if settings.is_empty() {
76
+ return Err(SettingSpecError::MissingSettingsSection);
77
+ }
78
+
79
+ // Static validation: Strict Coverage Rule
80
+ if !spec.profile.options.is_empty() {
81
+ for (key, setting) in &settings {
82
+ if !setting.profiles.contains_key("default") {
83
+ for opt in &spec.profile.options {
84
+ if !setting.profiles.contains_key(opt) {
85
+ return Err(SettingSpecError::StrictCoverageFailed(
86
+ key.clone(),
87
+ opt.clone(),
88
+ ));
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ Ok(SettingSpecDocument { spec, settings })
96
+ }
97
+
98
+ fn has_known_directive(table: &toml::map::Map<String, TomlValue>) -> bool {
99
+ table.contains_key("val")
100
+ || table.get("env").is_some_and(|v| !v.is_table())
101
+ || table.get("null").is_some_and(|v| !v.is_table())
102
+ || table.get("tags").is_some_and(|v| !v.is_table())
103
+ }
104
+
105
+ fn is_profile_declaration_table(
106
+ table: &toml::map::Map<String, TomlValue>,
107
+ current_path: &[String],
108
+ profile_options: &[String],
109
+ ) -> bool {
110
+ if current_path.len() < 2 {
111
+ return false;
112
+ }
113
+
114
+ if table.contains_key("default") || table.keys().any(|k| profile_options.contains(k)) {
115
+ return false;
116
+ }
117
+
118
+ let last_segment = current_path.last().unwrap();
119
+
120
+ if last_segment == "default" || profile_options.contains(last_segment) {
121
+ return has_known_directive(table)
122
+ || (!table.is_empty() && !table.values().any(|v| v.is_table()));
123
+ }
124
+
125
+ if let Some(val_entry) = table.get("val") {
126
+ if let Some(sub_table) = val_entry.as_table()
127
+ && has_known_directive(sub_table)
128
+ {
129
+ return false;
130
+ }
131
+ return true;
132
+ }
133
+
134
+ if table.get("env").is_some_and(|v| !v.is_table())
135
+ || table.get("null").is_some_and(|v| !v.is_table())
136
+ || table.get("tags").is_some_and(|v| !v.is_table())
137
+ {
138
+ return true;
139
+ }
140
+
141
+ if !table.is_empty() && !table.values().any(|v| v.is_table()) {
142
+ return true;
143
+ }
144
+
145
+ false
146
+ }
147
+
148
+ fn extract_settings(
149
+ table: &toml::map::Map<String, TomlValue>,
150
+ current_path: &mut Vec<String>,
151
+ profile_options: &[String],
152
+ settings: &mut BTreeMap<String, Setting>,
153
+ ) -> Result<()> {
154
+ if is_profile_declaration_table(table, current_path, profile_options) {
155
+ let profile = current_path.last().unwrap().clone();
156
+ let key_segments = &current_path[..current_path.len() - 1];
157
+ let setting_key = key_segments.join(".");
158
+
159
+ // 1. Validate setting key segments for reserved keywords
160
+ for seg in key_segments {
161
+ if is_reserved_directive_keyword(seg) {
162
+ return Err(SettingSpecError::ReservedKeywordConflict(seg.clone()));
163
+ }
164
+ }
165
+
166
+ // 2. Validate profile name for reserved keywords
167
+ if is_reserved_directive_keyword(&profile) {
168
+ return Err(SettingSpecError::ReservedKeywordConflict(profile));
169
+ }
170
+
171
+ // 3. Validate profile options membership
172
+ if !profile_options.is_empty()
173
+ && profile != "default"
174
+ && !profile_options.contains(&profile)
175
+ {
176
+ return Err(SettingSpecError::UnknownProfileInSettings(profile));
177
+ }
178
+
179
+ // 4. Validate directives: check for unknown directives
180
+ for k in table.keys() {
181
+ if !is_reserved_directive_keyword(k) {
182
+ return Err(SettingSpecError::InvalidDirective(
183
+ setting_key,
184
+ profile,
185
+ k.clone(),
186
+ ));
187
+ }
188
+ }
189
+
190
+ // 5. Deserialize profile declaration schema using Serde
191
+ let decl_schema: ProfileDeclarationSchema =
192
+ ProfileDeclarationSchema::deserialize(table.clone().into_deserializer())
193
+ .map_err(|e| SettingSpecError::InvalidToml(e.to_string()))?;
194
+
195
+ if let Some(null_val) = decl_schema.null
196
+ && !null_val
197
+ {
198
+ return Err(SettingSpecError::InvalidNullValue(setting_key, profile));
199
+ }
200
+
201
+ if decl_schema.null == Some(true) && decl_schema.val.is_some() {
202
+ return Err(SettingSpecError::NullCoexistsWithVal(setting_key, profile));
203
+ }
204
+
205
+ if decl_schema.val.is_none() && decl_schema.env.is_none() && decl_schema.null != Some(true)
206
+ {
207
+ return Err(SettingSpecError::RequiredValueMissing(format!(
208
+ "{}.{}",
209
+ setting_key, profile
210
+ )));
211
+ }
212
+
213
+ let decl = ProfileDeclaration {
214
+ val: table.get("val").cloned(),
215
+ env: decl_schema.env,
216
+ null: decl_schema.null.unwrap_or(false),
217
+ tags: decl_schema.tags.unwrap_or_default(),
218
+ };
219
+
220
+ let entry = settings
221
+ .entry(setting_key.clone())
222
+ .or_insert_with(|| Setting {
223
+ key: setting_key,
224
+ profiles: BTreeMap::new(),
225
+ });
226
+ entry.profiles.insert(profile, decl);
227
+ return Ok(());
228
+ }
229
+
230
+ // Otherwise, descend into child tables
231
+ for (k, v) in table {
232
+ if let Some(sub_table) = v.as_table() {
233
+ current_path.push(k.clone());
234
+ extract_settings(sub_table, current_path, profile_options, settings)?;
235
+ current_path.pop();
236
+ } else {
237
+ let mut full = current_path.clone();
238
+ full.push(k.clone());
239
+ return Err(SettingSpecError::InvalidToml(format!(
240
+ "Invalid setting structure at '{}': expected profile sub-table",
241
+ full.join(".")
242
+ )));
243
+ }
244
+ }
245
+
246
+ Ok(())
247
+ }
248
+
249
+ #[cfg(test)]
250
+ mod tests {
251
+ use super::*;
252
+
253
+ #[test]
254
+ fn test_parse_reserved_directive_keywords_and_default_in_table_values() {
255
+ let toml_str = r#"
256
+ [settings]
257
+ key1.default.val = { val = 1 }
258
+ key2.default.val = { null = true }
259
+ key3.default.val = { tags = [1, 2, 3] }
260
+ key4.default.val = { env = "MY_VAR" }
261
+ key5.default.val = { default = { val = 1 } }
262
+ "#;
263
+
264
+ let doc = parse_config_str(toml_str).expect("Failed to parse config");
265
+
266
+ // key1: key1.default.val = { val = 1 }
267
+ let key1_decl = doc
268
+ .settings
269
+ .get("key1")
270
+ .unwrap()
271
+ .profiles
272
+ .get("default")
273
+ .unwrap();
274
+ assert!(!key1_decl.null);
275
+ assert!(key1_decl.env.is_none());
276
+ assert!(key1_decl.tags.is_empty());
277
+ let val1 = key1_decl.val.as_ref().unwrap().as_table().unwrap();
278
+ assert_eq!(val1.get("val").unwrap().as_integer(), Some(1));
279
+
280
+ // key2: key2.default.val = { null = true }
281
+ let key2_decl = doc
282
+ .settings
283
+ .get("key2")
284
+ .unwrap()
285
+ .profiles
286
+ .get("default")
287
+ .unwrap();
288
+ assert!(!key2_decl.null);
289
+ assert!(key2_decl.env.is_none());
290
+ assert!(key2_decl.tags.is_empty());
291
+ let val2 = key2_decl.val.as_ref().unwrap().as_table().unwrap();
292
+ assert_eq!(val2.get("null").unwrap().as_bool(), Some(true));
293
+
294
+ // key3: key3.default.val = { tags = [1, 2, 3] }
295
+ let key3_decl = doc
296
+ .settings
297
+ .get("key3")
298
+ .unwrap()
299
+ .profiles
300
+ .get("default")
301
+ .unwrap();
302
+ assert!(!key3_decl.null);
303
+ assert!(key3_decl.env.is_none());
304
+ assert!(key3_decl.tags.is_empty());
305
+ let val3 = key3_decl.val.as_ref().unwrap().as_table().unwrap();
306
+ let tags = val3.get("tags").unwrap().as_array().unwrap();
307
+ assert_eq!(tags.len(), 3);
308
+ assert_eq!(tags[0].as_integer(), Some(1));
309
+ assert_eq!(tags[1].as_integer(), Some(2));
310
+ assert_eq!(tags[2].as_integer(), Some(3));
311
+
312
+ // key4: key4.default.val = { env = "MY_VAR" }
313
+ let key4_decl = doc
314
+ .settings
315
+ .get("key4")
316
+ .unwrap()
317
+ .profiles
318
+ .get("default")
319
+ .unwrap();
320
+ assert!(!key4_decl.null);
321
+ assert!(key4_decl.env.is_none());
322
+ assert!(key4_decl.tags.is_empty());
323
+ let val4 = key4_decl.val.as_ref().unwrap().as_table().unwrap();
324
+ assert_eq!(val4.get("env").unwrap().as_str(), Some("MY_VAR"));
325
+
326
+ // key5: key5.default.val = { default = { val = 1 } }
327
+ let key5_decl = doc
328
+ .settings
329
+ .get("key5")
330
+ .unwrap()
331
+ .profiles
332
+ .get("default")
333
+ .unwrap();
334
+ assert!(!key5_decl.null);
335
+ assert!(key5_decl.env.is_none());
336
+ assert!(key5_decl.tags.is_empty());
337
+ let val5 = key5_decl.val.as_ref().unwrap().as_table().unwrap();
338
+ let nested_default = val5.get("default").unwrap().as_table().unwrap();
339
+ assert_eq!(nested_default.get("val").unwrap().as_integer(), Some(1));
340
+ }
341
+ }
@@ -1,9 +1,12 @@
1
1
  use crate::error::{Result, SettingSpecError};
2
- use crate::model::ProfileConfig;
2
+ use crate::model::{ProfileConfig, is_reserved_directive_keyword};
3
3
 
4
4
  /// Validates a trimmed, non-empty profile name against the configured
5
5
  /// options (if any are configured; an empty list means any name is valid).
6
6
  fn validate_profile(name: String, options: &[String]) -> Result<Option<String>> {
7
+ if is_reserved_directive_keyword(&name) {
8
+ return Err(SettingSpecError::ReservedKeywordConflict(name));
9
+ }
7
10
  if !options.is_empty() && !options.contains(&name) {
8
11
  return Err(SettingSpecError::ActiveProfileNotInOptions(name));
9
12
  }
@@ -1,186 +0,0 @@
1
- use crate::error::{Result, SettingSpecError};
2
- use crate::model::*;
3
- use serde::Deserialize;
4
- use serde::de::IntoDeserializer;
5
- use std::collections::BTreeMap;
6
- use toml::Value as TomlValue;
7
-
8
- #[derive(Debug, Deserialize)]
9
- #[serde(deny_unknown_fields)]
10
- pub struct SettingSpecSchema {
11
- #[serde(default)]
12
- pub spec: Option<SpecConfig>,
13
- pub settings: Option<toml::Table>,
14
- }
15
-
16
- #[derive(Debug, Clone, Deserialize)]
17
- #[serde(deny_unknown_fields)]
18
- pub struct ProfileDeclarationSchema {
19
- #[serde(default)]
20
- pub val: Option<TomlValue>,
21
- #[serde(default)]
22
- pub env: Option<String>,
23
- #[serde(default)]
24
- pub null: Option<bool>,
25
- #[serde(default)]
26
- pub tags: Option<Vec<String>>,
27
- }
28
-
29
- pub fn parse_config_str(content: &str) -> Result<SettingSpecDocument> {
30
- let schema: SettingSpecSchema =
31
- toml::from_str(content).map_err(|e| SettingSpecError::InvalidToml(e.to_string()))?;
32
-
33
- let spec = schema.spec.unwrap_or_default();
34
-
35
- // Validate spec.profile.options does not contain "default"
36
- if spec.profile.options.iter().any(|o| o == "default") {
37
- return Err(SettingSpecError::ReservedProfileInOptions);
38
- }
39
-
40
- // Parse [settings] (REQUIRED)
41
- let settings_table = schema
42
- .settings
43
- .ok_or(SettingSpecError::MissingSettingsSection)?;
44
-
45
- if settings_table.is_empty() {
46
- return Err(SettingSpecError::MissingSettingsSection);
47
- }
48
-
49
- let mut settings: BTreeMap<String, Setting> = BTreeMap::new();
50
- let mut current_path = Vec::new();
51
- extract_settings(
52
- &settings_table,
53
- &mut current_path,
54
- &spec.profile.options,
55
- &mut settings,
56
- )?;
57
-
58
- if settings.is_empty() {
59
- return Err(SettingSpecError::MissingSettingsSection);
60
- }
61
-
62
- // Static validation: Strict Coverage Rule
63
- if !spec.profile.options.is_empty() {
64
- for (key, setting) in &settings {
65
- if !setting.profiles.contains_key("default") {
66
- for opt in &spec.profile.options {
67
- if !setting.profiles.contains_key(opt) {
68
- return Err(SettingSpecError::StrictCoverageFailed(
69
- key.clone(),
70
- opt.clone(),
71
- ));
72
- }
73
- }
74
- }
75
- }
76
- }
77
-
78
- Ok(SettingSpecDocument { spec, settings })
79
- }
80
-
81
- fn extract_settings(
82
- table: &toml::map::Map<String, TomlValue>,
83
- current_path: &mut Vec<String>,
84
- profile_options: &[String],
85
- settings: &mut BTreeMap<String, Setting>,
86
- ) -> Result<()> {
87
- let has_directive = table
88
- .keys()
89
- .any(|k| matches!(k.as_str(), "val" | "env" | "null" | "tags"));
90
- let last_is_profile = if let Some(last) = current_path.last() {
91
- last == "default" || profile_options.contains(last)
92
- } else {
93
- false
94
- };
95
-
96
- if has_directive
97
- || (last_is_profile && current_path.len() >= 2 && !table.values().any(|v| v.is_table()))
98
- {
99
- // This is a profile declaration!
100
- if current_path.len() < 2 {
101
- return Err(SettingSpecError::InvalidToml(
102
- "Malformed setting declaration: missing setting key or profile".into(),
103
- ));
104
- }
105
-
106
- let profile = current_path.pop().unwrap();
107
- let setting_key = current_path.join(".");
108
- current_path.push(profile.clone());
109
-
110
- // Validate profile name
111
- if !profile_options.is_empty()
112
- && profile != "default"
113
- && !profile_options.contains(&profile)
114
- {
115
- return Err(SettingSpecError::UnknownProfileInSettings(profile));
116
- }
117
-
118
- // Validate directives: check for unknown directives
119
- for k in table.keys() {
120
- if !matches!(k.as_str(), "val" | "env" | "null" | "tags") {
121
- return Err(SettingSpecError::InvalidDirective(
122
- setting_key,
123
- profile,
124
- k.clone(),
125
- ));
126
- }
127
- }
128
-
129
- // Deserialize profile declaration schema using Serde
130
- let decl_schema: ProfileDeclarationSchema =
131
- ProfileDeclarationSchema::deserialize(table.clone().into_deserializer())
132
- .map_err(|e| SettingSpecError::InvalidToml(e.to_string()))?;
133
-
134
- if let Some(null_val) = decl_schema.null
135
- && !null_val
136
- {
137
- return Err(SettingSpecError::InvalidNullValue(setting_key, profile));
138
- }
139
-
140
- if decl_schema.null == Some(true) && decl_schema.val.is_some() {
141
- return Err(SettingSpecError::NullCoexistsWithVal(setting_key, profile));
142
- }
143
-
144
- if decl_schema.val.is_none() && decl_schema.env.is_none() && decl_schema.null != Some(true)
145
- {
146
- return Err(SettingSpecError::RequiredValueMissing(format!(
147
- "{}.{}",
148
- setting_key, profile
149
- )));
150
- }
151
-
152
- let decl = ProfileDeclaration {
153
- val: table.get("val").cloned(),
154
- env: decl_schema.env,
155
- null: decl_schema.null.unwrap_or(false),
156
- tags: decl_schema.tags.unwrap_or_default(),
157
- };
158
-
159
- let entry = settings
160
- .entry(setting_key.clone())
161
- .or_insert_with(|| Setting {
162
- key: setting_key,
163
- profiles: BTreeMap::new(),
164
- });
165
- entry.profiles.insert(profile, decl);
166
- return Ok(());
167
- }
168
-
169
- // Otherwise, descend into child tables
170
- for (k, v) in table {
171
- if let Some(sub_table) = v.as_table() {
172
- current_path.push(k.clone());
173
- extract_settings(sub_table, current_path, profile_options, settings)?;
174
- current_path.pop();
175
- } else {
176
- let mut full = current_path.clone();
177
- full.push(k.clone());
178
- return Err(SettingSpecError::InvalidToml(format!(
179
- "Invalid setting structure at '{}': expected profile sub-table",
180
- full.join(".")
181
- )));
182
- }
183
- }
184
-
185
- Ok(())
186
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes