settingspec 0.5.1__tar.gz → 0.7.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.
- {settingspec-0.5.1 → settingspec-0.7.0}/Cargo.lock +1 -1
- {settingspec-0.5.1 → settingspec-0.7.0}/Cargo.toml +1 -1
- {settingspec-0.5.1 → settingspec-0.7.0}/PKG-INFO +29 -27
- {settingspec-0.5.1 → settingspec-0.7.0}/README.md +29 -27
- {settingspec-0.5.1 → settingspec-0.7.0}/src/cli.rs +17 -2
- {settingspec-0.5.1 → settingspec-0.7.0}/src/error.rs +20 -13
- settingspec-0.7.0/src/generator/c.rs +211 -0
- settingspec-0.7.0/src/generator/cpp.rs +219 -0
- settingspec-0.7.0/src/generator/elm.rs +125 -0
- settingspec-0.7.0/src/generator/go.rs +205 -0
- settingspec-0.7.0/src/generator/haskell.rs +250 -0
- settingspec-0.7.0/src/generator/java.rs +203 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/mod.rs +33 -0
- settingspec-0.7.0/src/generator/ruby.rs +134 -0
- settingspec-0.7.0/src/generator/rust.rs +178 -0
- settingspec-0.7.0/src/generator/scala.rs +177 -0
- settingspec-0.7.0/src/generator/terraform.rs +120 -0
- settingspec-0.7.0/src/generator/zig.rs +167 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/model.rs +11 -7
- settingspec-0.7.0/src/parser.rs +421 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/profile.rs +2 -2
- {settingspec-0.5.1 → settingspec-0.7.0}/src/resolver.rs +1 -13
- settingspec-0.5.1/src/parser.rs +0 -341
- {settingspec-0.5.1 → settingspec-0.7.0}/LICENSE-APACHE +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/LICENSE-MIT +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/pyproject.toml +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/envfile.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/filter.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/common.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/env.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/javascript.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/json.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/lua.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/python.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/toml.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/tree.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/typescript.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/generator/yaml.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/lib.rs +0 -0
- {settingspec-0.5.1 → settingspec-0.7.0}/src/main.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: settingspec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
@@ -58,14 +58,15 @@ profile.options = [ # Optional: Enables strict checking of per-
|
|
|
58
58
|
]
|
|
59
59
|
profile.default = "dev" # Optional: Default profile when the switch is not set
|
|
60
60
|
|
|
61
|
-
[settings]
|
|
62
|
-
key1.default.val = "val1" # Default value for all profiles
|
|
63
|
-
key1.prod.val = "prod1" # Override the default value when SETTINGSPEC_PROFILE=prod
|
|
61
|
+
[settings] # Declare the settings here with syntax: `<key>._.<profile>.<directive> = <value>`
|
|
62
|
+
key1._.default.val = "val1" # Default value for all profiles
|
|
63
|
+
key1._.prod.val = "prod1" # Override the default value when SETTINGSPEC_PROFILE=prod
|
|
64
64
|
|
|
65
|
-
# Strictly define different values for different profiles
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
key2._ = { # Strictly define different values for different profiles
|
|
66
|
+
dev.val = "dev2",
|
|
67
|
+
stage.val = "stage2",
|
|
68
|
+
prod.val = "prod2",
|
|
69
|
+
}
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
Overrides are intentional and kept in plain sight (see `key1`).
|
|
@@ -74,14 +75,14 @@ By not defining a default value, you can ensure that you never miss adding a val
|
|
|
74
75
|
|
|
75
76
|
### Language independent
|
|
76
77
|
|
|
77
|
-
The command-line tool `settingspec` can export the final settings into well-known formats such as `toml`, `json`, `yaml`, etc., or hard-coded modules such as `.py`, `.js`, `.lua`, etc., write them to disk, print, or pipe them via stdin or even export as environment variables, allowing you to use a single `settingspec.toml` without worrying about the target language.
|
|
78
|
+
The command-line tool `settingspec` can export the final settings into well-known formats such as `toml`, `json`, `yaml`, `tf`, etc., or hard-coded modules such as `.py`, `.js`, `.ts`, `.lua`, `.rs`, `.go`, `.zig`, `.c`, `.cpp`, `.java`, `.elm`, `.rb`, `.scala`, `.hs`, etc., write them to disk, print, or pipe them via stdin or even export as environment variables, allowing you to use a single `settingspec.toml` without worrying about the target language.
|
|
78
79
|
|
|
79
80
|
```toml
|
|
80
81
|
[spec]
|
|
81
|
-
export.file = true
|
|
82
|
-
# export.file = {
|
|
83
|
-
# "settings.
|
|
84
|
-
# "settings.yaml" = {
|
|
82
|
+
export.file = true # Default: Export all settings into settings.toml
|
|
83
|
+
# export.file = { # Optional: Fine-grained control over what to export
|
|
84
|
+
# "settings.py" = true, # Get auto-completion of settings in your editor/IDE for free
|
|
85
|
+
# "settings.yaml" = { # Select which keys to export
|
|
85
86
|
# key1 = true,
|
|
86
87
|
# group1 = true,
|
|
87
88
|
# group2.subgroup = true,
|
|
@@ -97,11 +98,11 @@ export.env = "PREFIX_" # Optional: Export as environment variables prefixed wit
|
|
|
97
98
|
export.stdin = "toml" # Optional: Pass settings to program stdin in specified format
|
|
98
99
|
|
|
99
100
|
[settings]
|
|
100
|
-
key1.default.val = "val1"
|
|
101
|
-
group1.key1.default.val = "group1val1"
|
|
102
|
-
group2.subgroup.key1.default.val = "group2val1"
|
|
103
|
-
group3.subgroup.key1.default.val = "group3val1"
|
|
104
|
-
group3.subgroup.key1.
|
|
101
|
+
key1._.default.val = "val1"
|
|
102
|
+
group1.key1._.default.val = "group1val1"
|
|
103
|
+
group2.subgroup.key1._.default.val = "group2val1"
|
|
104
|
+
group3.subgroup.key1._.default.val = "group3val1"
|
|
105
|
+
group3.subgroup.key1._.tags = ["tag1", "tag2"] # Syntax: `<key>._.tags = [...]`
|
|
105
106
|
```
|
|
106
107
|
|
|
107
108
|
Now export the final settings as declared.
|
|
@@ -125,7 +126,7 @@ settingspec run -- [your program]...
|
|
|
125
126
|
If no export option is specified, the default behavior is to export all settings into `settings.toml`.
|
|
126
127
|
|
|
127
128
|
> [!NOTE]
|
|
128
|
-
> If the target programming language supports null values, you can declare it as `key.default.null = true` in the settings section.
|
|
129
|
+
> If the target programming language supports null values, you can declare it as `key._.default.null = true` in the settings section.
|
|
129
130
|
|
|
130
131
|
### Easy secrets
|
|
131
132
|
|
|
@@ -156,12 +157,12 @@ settingspec.toml
|
|
|
156
157
|
profile.options = ["dev", "stage", "prod"]
|
|
157
158
|
|
|
158
159
|
[settings]
|
|
159
|
-
secret1.default.env = "SECRET1" # Load value from $SECRET1
|
|
160
|
+
secret1._.default.env = "SECRET1" # Load value from $SECRET1
|
|
160
161
|
|
|
161
162
|
# Override the default value from environment variables
|
|
162
|
-
secret2.default.val = "defaultvalue" # Default value if $SECRET2 is not set
|
|
163
|
-
secret2.default.env = "SECRET2" # Load value from $SECRET2 if set
|
|
164
|
-
secret2.prod.env = "PRODSECRET" # Load value from $PRODSECRET if SETTINGSPEC_PROFILE=prod
|
|
163
|
+
secret2._.default.val = "defaultvalue" # Default value if $SECRET2 is not set
|
|
164
|
+
secret2._.default.env = "SECRET2" # Load value from $SECRET2 if set
|
|
165
|
+
secret2._.prod.env = "PRODSECRET" # Load value from $PRODSECRET if SETTINGSPEC_PROFILE=prod
|
|
165
166
|
```
|
|
166
167
|
|
|
167
168
|
With these set up, run:
|
|
@@ -186,7 +187,7 @@ envfile.prod = "-" # Read from stdin, encrypted or not
|
|
|
186
187
|
# decryption.key.path = "~/.ssh/" # Default
|
|
187
188
|
|
|
188
189
|
[settings]
|
|
189
|
-
secret1.default.env = "SECRET1" # Load value from $SECRET1
|
|
190
|
+
secret1._.default.env = "SECRET1" # Load value from $SECRET1
|
|
190
191
|
```
|
|
191
192
|
|
|
192
193
|
#### Cottage or agebox
|
|
@@ -196,13 +197,14 @@ Too simple? Use [Cottage](https://cottage-cli.pages.dev) or [agebox](https://git
|
|
|
196
197
|
```toml
|
|
197
198
|
[spec]
|
|
198
199
|
envfile.default = ".env.cott.age"
|
|
200
|
+
# envfile.default = ".env.agebox"
|
|
199
201
|
|
|
200
|
-
# decryption.key.env = "AGEBOX_PRIVATE_KEYS"
|
|
201
|
-
# decryption.key.env = "COTTAGE_IDENTITY"
|
|
202
202
|
decryption.key.path = "~/.config/cottage/identity:~/.ssh"
|
|
203
|
+
decryption.key.env = "COTTAGE_IDENTITY"
|
|
204
|
+
# decryption.key.env = "AGEBOX_PRIVATE_KEYS"
|
|
203
205
|
|
|
204
206
|
[settings]
|
|
205
|
-
secret.default.env = "SECRET"
|
|
207
|
+
secret._.default.env = "SECRET"
|
|
206
208
|
```
|
|
207
209
|
|
|
208
210
|
## Learn more
|
|
@@ -41,14 +41,15 @@ profile.options = [ # Optional: Enables strict checking of per-
|
|
|
41
41
|
]
|
|
42
42
|
profile.default = "dev" # Optional: Default profile when the switch is not set
|
|
43
43
|
|
|
44
|
-
[settings]
|
|
45
|
-
key1.default.val = "val1" # Default value for all profiles
|
|
46
|
-
key1.prod.val = "prod1" # Override the default value when SETTINGSPEC_PROFILE=prod
|
|
47
|
-
|
|
48
|
-
# Strictly define different values for different profiles
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
[settings] # Declare the settings here with syntax: `<key>._.<profile>.<directive> = <value>`
|
|
45
|
+
key1._.default.val = "val1" # Default value for all profiles
|
|
46
|
+
key1._.prod.val = "prod1" # Override the default value when SETTINGSPEC_PROFILE=prod
|
|
47
|
+
|
|
48
|
+
key2._ = { # Strictly define different values for different profiles
|
|
49
|
+
dev.val = "dev2",
|
|
50
|
+
stage.val = "stage2",
|
|
51
|
+
prod.val = "prod2",
|
|
52
|
+
}
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
Overrides are intentional and kept in plain sight (see `key1`).
|
|
@@ -57,14 +58,14 @@ By not defining a default value, you can ensure that you never miss adding a val
|
|
|
57
58
|
|
|
58
59
|
### Language independent
|
|
59
60
|
|
|
60
|
-
The command-line tool `settingspec` can export the final settings into well-known formats such as `toml`, `json`, `yaml`, etc., or hard-coded modules such as `.py`, `.js`, `.lua`, etc., write them to disk, print, or pipe them via stdin or even export as environment variables, allowing you to use a single `settingspec.toml` without worrying about the target language.
|
|
61
|
+
The command-line tool `settingspec` can export the final settings into well-known formats such as `toml`, `json`, `yaml`, `tf`, etc., or hard-coded modules such as `.py`, `.js`, `.ts`, `.lua`, `.rs`, `.go`, `.zig`, `.c`, `.cpp`, `.java`, `.elm`, `.rb`, `.scala`, `.hs`, etc., write them to disk, print, or pipe them via stdin or even export as environment variables, allowing you to use a single `settingspec.toml` without worrying about the target language.
|
|
61
62
|
|
|
62
63
|
```toml
|
|
63
64
|
[spec]
|
|
64
|
-
export.file = true
|
|
65
|
-
# export.file = {
|
|
66
|
-
# "settings.
|
|
67
|
-
# "settings.yaml" = {
|
|
65
|
+
export.file = true # Default: Export all settings into settings.toml
|
|
66
|
+
# export.file = { # Optional: Fine-grained control over what to export
|
|
67
|
+
# "settings.py" = true, # Get auto-completion of settings in your editor/IDE for free
|
|
68
|
+
# "settings.yaml" = { # Select which keys to export
|
|
68
69
|
# key1 = true,
|
|
69
70
|
# group1 = true,
|
|
70
71
|
# group2.subgroup = true,
|
|
@@ -80,11 +81,11 @@ export.env = "PREFIX_" # Optional: Export as environment variables prefixed wit
|
|
|
80
81
|
export.stdin = "toml" # Optional: Pass settings to program stdin in specified format
|
|
81
82
|
|
|
82
83
|
[settings]
|
|
83
|
-
key1.default.val = "val1"
|
|
84
|
-
group1.key1.default.val = "group1val1"
|
|
85
|
-
group2.subgroup.key1.default.val = "group2val1"
|
|
86
|
-
group3.subgroup.key1.default.val = "group3val1"
|
|
87
|
-
group3.subgroup.key1.
|
|
84
|
+
key1._.default.val = "val1"
|
|
85
|
+
group1.key1._.default.val = "group1val1"
|
|
86
|
+
group2.subgroup.key1._.default.val = "group2val1"
|
|
87
|
+
group3.subgroup.key1._.default.val = "group3val1"
|
|
88
|
+
group3.subgroup.key1._.tags = ["tag1", "tag2"] # Syntax: `<key>._.tags = [...]`
|
|
88
89
|
```
|
|
89
90
|
|
|
90
91
|
Now export the final settings as declared.
|
|
@@ -108,7 +109,7 @@ settingspec run -- [your program]...
|
|
|
108
109
|
If no export option is specified, the default behavior is to export all settings into `settings.toml`.
|
|
109
110
|
|
|
110
111
|
> [!NOTE]
|
|
111
|
-
> If the target programming language supports null values, you can declare it as `key.default.null = true` in the settings section.
|
|
112
|
+
> If the target programming language supports null values, you can declare it as `key._.default.null = true` in the settings section.
|
|
112
113
|
|
|
113
114
|
### Easy secrets
|
|
114
115
|
|
|
@@ -139,12 +140,12 @@ settingspec.toml
|
|
|
139
140
|
profile.options = ["dev", "stage", "prod"]
|
|
140
141
|
|
|
141
142
|
[settings]
|
|
142
|
-
secret1.default.env = "SECRET1" # Load value from $SECRET1
|
|
143
|
+
secret1._.default.env = "SECRET1" # Load value from $SECRET1
|
|
143
144
|
|
|
144
145
|
# Override the default value from environment variables
|
|
145
|
-
secret2.default.val = "defaultvalue" # Default value if $SECRET2 is not set
|
|
146
|
-
secret2.default.env = "SECRET2" # Load value from $SECRET2 if set
|
|
147
|
-
secret2.prod.env = "PRODSECRET" # Load value from $PRODSECRET if SETTINGSPEC_PROFILE=prod
|
|
146
|
+
secret2._.default.val = "defaultvalue" # Default value if $SECRET2 is not set
|
|
147
|
+
secret2._.default.env = "SECRET2" # Load value from $SECRET2 if set
|
|
148
|
+
secret2._.prod.env = "PRODSECRET" # Load value from $PRODSECRET if SETTINGSPEC_PROFILE=prod
|
|
148
149
|
```
|
|
149
150
|
|
|
150
151
|
With these set up, run:
|
|
@@ -169,7 +170,7 @@ envfile.prod = "-" # Read from stdin, encrypted or not
|
|
|
169
170
|
# decryption.key.path = "~/.ssh/" # Default
|
|
170
171
|
|
|
171
172
|
[settings]
|
|
172
|
-
secret1.default.env = "SECRET1" # Load value from $SECRET1
|
|
173
|
+
secret1._.default.env = "SECRET1" # Load value from $SECRET1
|
|
173
174
|
```
|
|
174
175
|
|
|
175
176
|
#### Cottage or agebox
|
|
@@ -179,13 +180,14 @@ Too simple? Use [Cottage](https://cottage-cli.pages.dev) or [agebox](https://git
|
|
|
179
180
|
```toml
|
|
180
181
|
[spec]
|
|
181
182
|
envfile.default = ".env.cott.age"
|
|
183
|
+
# envfile.default = ".env.agebox"
|
|
182
184
|
|
|
183
|
-
# decryption.key.env = "AGEBOX_PRIVATE_KEYS"
|
|
184
|
-
# decryption.key.env = "COTTAGE_IDENTITY"
|
|
185
185
|
decryption.key.path = "~/.config/cottage/identity:~/.ssh"
|
|
186
|
+
decryption.key.env = "COTTAGE_IDENTITY"
|
|
187
|
+
# decryption.key.env = "AGEBOX_PRIVATE_KEYS"
|
|
186
188
|
|
|
187
189
|
[settings]
|
|
188
|
-
secret.default.env = "SECRET"
|
|
190
|
+
secret._.default.env = "SECRET"
|
|
189
191
|
```
|
|
190
192
|
|
|
191
193
|
## Learn more
|
|
@@ -57,6 +57,9 @@ pub enum Commands {
|
|
|
57
57
|
},
|
|
58
58
|
/// Watches settingspec.toml and regenerates exports whenever it changes
|
|
59
59
|
Watch,
|
|
60
|
+
|
|
61
|
+
/// Deletes all generated export files
|
|
62
|
+
Clean,
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
pub const INIT_CONFIG: &str = r#"[spec]
|
|
@@ -64,8 +67,8 @@ profile.options = ["local", "prod"]
|
|
|
64
67
|
profile.default = "local" # Switch via $SETTINGSPEC_PROFILE
|
|
65
68
|
|
|
66
69
|
[settings]
|
|
67
|
-
key.local.val = "local"
|
|
68
|
-
key.prod.env = "KEY" # Load from $KEY when active profile is "prod"
|
|
70
|
+
key._.local.val = "local"
|
|
71
|
+
key._.prod.env = "KEY" # Load from $KEY when active profile is "prod"
|
|
69
72
|
"#;
|
|
70
73
|
|
|
71
74
|
pub fn init_config() -> Result<()> {
|
|
@@ -169,6 +172,7 @@ pub fn run_cli() -> Result<()> {
|
|
|
169
172
|
execute_run(&ctx, &command)
|
|
170
173
|
}
|
|
171
174
|
Commands::Watch => run_watch(),
|
|
175
|
+
Commands::Clean => clean_exported_files(),
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
@@ -278,6 +282,17 @@ fn perform_export(ctx: &ExecutionContext) -> Result<Vec<PathBuf>> {
|
|
|
278
282
|
Ok(generated_files)
|
|
279
283
|
}
|
|
280
284
|
|
|
285
|
+
fn clean_exported_files() -> Result<()> {
|
|
286
|
+
let ctx = load_and_resolve()?;
|
|
287
|
+
|
|
288
|
+
let _guard = CleanupGuard {
|
|
289
|
+
keep: false,
|
|
290
|
+
paths: ctx.doc.spec.export.file.keys().map(PathBuf::from).collect(),
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
Ok(())
|
|
294
|
+
}
|
|
295
|
+
|
|
281
296
|
fn execute_run(ctx: &ExecutionContext, command_args: &[String]) -> Result<()> {
|
|
282
297
|
if command_args.is_empty() {
|
|
283
298
|
return Err(SettingSpecError::InvalidToml(
|
|
@@ -5,8 +5,9 @@ pub enum SettingSpecError {
|
|
|
5
5
|
ConfigNotFound,
|
|
6
6
|
InvalidToml(String),
|
|
7
7
|
MissingSettingsSection,
|
|
8
|
-
ReservedProfileInOptions,
|
|
8
|
+
ReservedProfileInOptions(String),
|
|
9
9
|
ReservedKeywordConflict(String),
|
|
10
|
+
InvalidDeclaration(String, String),
|
|
10
11
|
UnknownProfileInSettings(String),
|
|
11
12
|
ActiveProfileNotInOptions(String),
|
|
12
13
|
NoProfileSelected,
|
|
@@ -14,7 +15,8 @@ pub enum SettingSpecError {
|
|
|
14
15
|
InvalidDirective(String, String, String), // setting key, profile, directive
|
|
15
16
|
NullCoexistsWithVal(String, String), // setting key, profile
|
|
16
17
|
InvalidNullValue(String, String), // setting key, profile
|
|
17
|
-
InvalidTagsValue(String
|
|
18
|
+
InvalidTagsValue(String), // setting key
|
|
19
|
+
DuplicateTags(String), // setting key
|
|
18
20
|
InvalidEnvValue(String, String), // setting key, profile
|
|
19
21
|
MissingRequiredEnvVar(String, String), // setting key, env var name
|
|
20
22
|
RequiredValueMissing(String), // setting key
|
|
@@ -37,15 +39,17 @@ impl fmt::Display for SettingSpecError {
|
|
|
37
39
|
f,
|
|
38
40
|
"Missing required '[settings]' section in settingspec.toml"
|
|
39
41
|
),
|
|
40
|
-
Self::ReservedProfileInOptions => write!(
|
|
42
|
+
Self::ReservedProfileInOptions(name) => write!(
|
|
41
43
|
f,
|
|
42
|
-
"Reserved profile identifier '
|
|
43
|
-
|
|
44
|
-
Self::ReservedKeywordConflict(seg) => write!(
|
|
45
|
-
f,
|
|
46
|
-
"Reserved directive keyword '{}' cannot be used as a setting key segment or profile name",
|
|
47
|
-
seg
|
|
44
|
+
"Reserved profile identifier '{}' MUST NOT be in spec.profile.options",
|
|
45
|
+
name
|
|
48
46
|
),
|
|
47
|
+
Self::ReservedKeywordConflict(seg) => {
|
|
48
|
+
write!(f, "Reserved profile name '{}' cannot be used", seg)
|
|
49
|
+
}
|
|
50
|
+
Self::InvalidDeclaration(decl, reason) => {
|
|
51
|
+
write!(f, "Invalid setting declaration '{}': {}", decl, reason)
|
|
52
|
+
}
|
|
49
53
|
Self::UnknownProfileInSettings(p) => write!(
|
|
50
54
|
f,
|
|
51
55
|
"Unknown profile '{}' used in [settings] (not in spec.profile.options)",
|
|
@@ -80,14 +84,17 @@ impl fmt::Display for SettingSpecError {
|
|
|
80
84
|
"Directive 'null' must be true for setting '{}' in profile '{}'",
|
|
81
85
|
k, p
|
|
82
86
|
),
|
|
83
|
-
Self::InvalidTagsValue(k
|
|
87
|
+
Self::InvalidTagsValue(k) => write!(
|
|
84
88
|
f,
|
|
85
|
-
"Directive 'tags'
|
|
86
|
-
k
|
|
89
|
+
"Directive 'tags' for setting '{}': expected a sequence of strings",
|
|
90
|
+
k
|
|
87
91
|
),
|
|
92
|
+
Self::DuplicateTags(k) => {
|
|
93
|
+
write!(f, "Setting '{}' has more than one tags declaration", k)
|
|
94
|
+
}
|
|
88
95
|
Self::InvalidEnvValue(k, p) => write!(
|
|
89
96
|
f,
|
|
90
|
-
"Directive 'env'
|
|
97
|
+
"Directive 'env' for setting '{}' in profile '{}': expected a string",
|
|
91
98
|
k, p
|
|
92
99
|
),
|
|
93
100
|
Self::MissingRequiredEnvVar(k, env) => write!(
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
use super::common::{format_scalar_literal, is_valid_identifier};
|
|
2
|
+
use super::tree::{ConfigNode, build_config_tree};
|
|
3
|
+
use crate::error::Result;
|
|
4
|
+
use crate::model::ResolvedSetting;
|
|
5
|
+
use indexmap::IndexMap;
|
|
6
|
+
use toml::Value as TomlValue;
|
|
7
|
+
|
|
8
|
+
pub fn generate_c(settings: &[ResolvedSetting]) -> Result<String> {
|
|
9
|
+
let tree = build_config_tree(settings, false);
|
|
10
|
+
let mut out = String::from(
|
|
11
|
+
"/* Generated by SettingSpec */\n\n#ifndef SETTINGS_H\n#define SETTINGS_H\n\n#include <stdbool.h>\n#include <stddef.h>\n\n",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
let mut struct_defs = String::new();
|
|
15
|
+
let root_type = generate_c_structs(&tree, "Settings", &mut struct_defs);
|
|
16
|
+
|
|
17
|
+
out.push_str(&struct_defs);
|
|
18
|
+
|
|
19
|
+
let mut init_vals = String::new();
|
|
20
|
+
format_c_init(&tree, 1, &mut init_vals);
|
|
21
|
+
|
|
22
|
+
out.push_str(&format!(
|
|
23
|
+
"static const {} settings = {{\n{}}};\n\n#endif /* SETTINGS_H */\n",
|
|
24
|
+
root_type, init_vals
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
Ok(out)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn generate_c_structs(
|
|
31
|
+
map: &IndexMap<String, ConfigNode>,
|
|
32
|
+
struct_name: &str,
|
|
33
|
+
out: &mut String,
|
|
34
|
+
) -> String {
|
|
35
|
+
let mut fields = Vec::new();
|
|
36
|
+
|
|
37
|
+
for (k, node) in map {
|
|
38
|
+
let field_name = sanitize_c_identifier(k);
|
|
39
|
+
match node {
|
|
40
|
+
ConfigNode::Null => {
|
|
41
|
+
fields.push(format!(" const void* {};", field_name));
|
|
42
|
+
}
|
|
43
|
+
ConfigNode::Value(v) => match v {
|
|
44
|
+
TomlValue::String(_) | TomlValue::Datetime(_) => {
|
|
45
|
+
fields.push(format!(" const char* {};", field_name));
|
|
46
|
+
}
|
|
47
|
+
TomlValue::Integer(_) => {
|
|
48
|
+
fields.push(format!(" long long {};", field_name));
|
|
49
|
+
}
|
|
50
|
+
TomlValue::Float(_) => {
|
|
51
|
+
fields.push(format!(" double {};", field_name));
|
|
52
|
+
}
|
|
53
|
+
TomlValue::Boolean(_) => {
|
|
54
|
+
fields.push(format!(" bool {};", field_name));
|
|
55
|
+
}
|
|
56
|
+
TomlValue::Array(arr) => {
|
|
57
|
+
let (type_str, len) = c_array_type(arr);
|
|
58
|
+
fields.push(format!(" {} {}[{}];", type_str, field_name, len.max(1)));
|
|
59
|
+
fields.push(format!(" size_t {}_len;", field_name));
|
|
60
|
+
}
|
|
61
|
+
TomlValue::Table(tbl) => {
|
|
62
|
+
let sub_name = format!("{}{}", struct_name, capitalize(k));
|
|
63
|
+
let mut sub_map = IndexMap::new();
|
|
64
|
+
for (sub_k, sub_v) in tbl {
|
|
65
|
+
sub_map.insert(sub_k.clone(), ConfigNode::Value(sub_v.clone()));
|
|
66
|
+
}
|
|
67
|
+
let sub_type = generate_c_structs(&sub_map, &sub_name, out);
|
|
68
|
+
fields.push(format!(" {} {};", sub_type, field_name));
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
ConfigNode::Map(sub_map) => {
|
|
72
|
+
let sub_name = format!("{}{}", struct_name, capitalize(k));
|
|
73
|
+
let sub_type = generate_c_structs(sub_map, &sub_name, out);
|
|
74
|
+
fields.push(format!(" {} {};", sub_type, field_name));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let struct_def = format!(
|
|
80
|
+
"typedef struct {{\n{}\n}} {};\n\n",
|
|
81
|
+
fields.join("\n"),
|
|
82
|
+
struct_name
|
|
83
|
+
);
|
|
84
|
+
out.push_str(&struct_def);
|
|
85
|
+
struct_name.to_string()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn format_c_init(map: &IndexMap<String, ConfigNode>, indent_level: usize, out: &mut String) {
|
|
89
|
+
let indent = " ".repeat(indent_level);
|
|
90
|
+
|
|
91
|
+
for (k, node) in map {
|
|
92
|
+
let field_name = sanitize_c_identifier(k);
|
|
93
|
+
match node {
|
|
94
|
+
ConfigNode::Null => {
|
|
95
|
+
out.push_str(&format!("{}.{} = NULL,\n", indent, field_name));
|
|
96
|
+
}
|
|
97
|
+
ConfigNode::Value(v) => match v {
|
|
98
|
+
TomlValue::Boolean(b) => {
|
|
99
|
+
out.push_str(&format!("{}.{} = {},\n", indent, field_name, b));
|
|
100
|
+
}
|
|
101
|
+
TomlValue::Array(arr) => {
|
|
102
|
+
let items: Vec<String> = arr
|
|
103
|
+
.iter()
|
|
104
|
+
.map(|item| match item {
|
|
105
|
+
TomlValue::Boolean(b) => b.to_string(),
|
|
106
|
+
_ => format_scalar_literal(item).unwrap_or_else(|| "\"\"".to_string()),
|
|
107
|
+
})
|
|
108
|
+
.collect();
|
|
109
|
+
out.push_str(&format!(
|
|
110
|
+
"{}.{} = {{ {} }},\n{}.{}_len = {},\n",
|
|
111
|
+
indent,
|
|
112
|
+
field_name,
|
|
113
|
+
items.join(", "),
|
|
114
|
+
indent,
|
|
115
|
+
field_name,
|
|
116
|
+
arr.len()
|
|
117
|
+
));
|
|
118
|
+
}
|
|
119
|
+
TomlValue::Table(tbl) => {
|
|
120
|
+
out.push_str(&format!("{}.{} = {{\n", indent, field_name));
|
|
121
|
+
let mut sub_map = IndexMap::new();
|
|
122
|
+
for (sub_k, sub_v) in tbl {
|
|
123
|
+
sub_map.insert(sub_k.clone(), ConfigNode::Value(sub_v.clone()));
|
|
124
|
+
}
|
|
125
|
+
format_c_init(&sub_map, indent_level + 1, out);
|
|
126
|
+
out.push_str(&format!("{}}},\n", indent));
|
|
127
|
+
}
|
|
128
|
+
_ => {
|
|
129
|
+
if let Some(lit) = format_scalar_literal(v) {
|
|
130
|
+
out.push_str(&format!("{}.{} = {},\n", indent, field_name, lit));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
ConfigNode::Map(sub_map) => {
|
|
135
|
+
out.push_str(&format!("{}.{} = {{\n", indent, field_name));
|
|
136
|
+
format_c_init(sub_map, indent_level + 1, out);
|
|
137
|
+
out.push_str(&format!("{}}},\n", indent));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn c_array_type(arr: &[TomlValue]) -> (&'static str, usize) {
|
|
144
|
+
if arr.is_empty() {
|
|
145
|
+
return ("const char*", 0);
|
|
146
|
+
}
|
|
147
|
+
let elem_type = match &arr[0] {
|
|
148
|
+
TomlValue::Integer(_) => "long long",
|
|
149
|
+
TomlValue::Float(_) => "double",
|
|
150
|
+
TomlValue::Boolean(_) => "bool",
|
|
151
|
+
_ => "const char*",
|
|
152
|
+
};
|
|
153
|
+
(elem_type, arr.len())
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn sanitize_c_identifier(name: &str) -> String {
|
|
157
|
+
let is_keyword = matches!(
|
|
158
|
+
name,
|
|
159
|
+
"auto"
|
|
160
|
+
| "break"
|
|
161
|
+
| "case"
|
|
162
|
+
| "char"
|
|
163
|
+
| "const"
|
|
164
|
+
| "continue"
|
|
165
|
+
| "default"
|
|
166
|
+
| "do"
|
|
167
|
+
| "double"
|
|
168
|
+
| "else"
|
|
169
|
+
| "enum"
|
|
170
|
+
| "extern"
|
|
171
|
+
| "float"
|
|
172
|
+
| "for"
|
|
173
|
+
| "goto"
|
|
174
|
+
| "if"
|
|
175
|
+
| "inline"
|
|
176
|
+
| "int"
|
|
177
|
+
| "long"
|
|
178
|
+
| "register"
|
|
179
|
+
| "restrict"
|
|
180
|
+
| "return"
|
|
181
|
+
| "short"
|
|
182
|
+
| "signed"
|
|
183
|
+
| "sizeof"
|
|
184
|
+
| "static"
|
|
185
|
+
| "struct"
|
|
186
|
+
| "switch"
|
|
187
|
+
| "typedef"
|
|
188
|
+
| "union"
|
|
189
|
+
| "unsigned"
|
|
190
|
+
| "void"
|
|
191
|
+
| "volatile"
|
|
192
|
+
| "while"
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if is_keyword || !is_valid_identifier(name) {
|
|
196
|
+
format!(
|
|
197
|
+
"_{}",
|
|
198
|
+
name.replace(|c: char| !c.is_ascii_alphanumeric(), "_")
|
|
199
|
+
)
|
|
200
|
+
} else {
|
|
201
|
+
name.to_string()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn capitalize(s: &str) -> String {
|
|
206
|
+
let mut c = s.chars();
|
|
207
|
+
match c.next() {
|
|
208
|
+
None => String::new(),
|
|
209
|
+
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
|
210
|
+
}
|
|
211
|
+
}
|