Skip to content

Commit 130d93a

Browse files
authored
new: Improve renderer with custom values and only fields. (#181)
1 parent f7c912f commit 130d93a

14 files changed

Lines changed: 266 additions & 165 deletions

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ This release changes how formats work. Instead of using the `Format` enum, forma
2222

2323
#### 🚀 Updates
2424

25+
- Added `.jsonc` support.
2526
- Added a `SourceFormat` trait, with built-in formats available at `schematic::config::formats`.
2627
- Added a `ConfigLoader#add_format()` method.
2728
- Added a `extract_file_name` function.
28-
- Added `.jsonc` support.
29+
- Added `TemplateOptions.custom_values` for specifying custom values for specific fields in a
30+
template.
31+
- Added `TemplateOptions.only_fields` to only render specific fields in a template.
2932

3033
#### ⚙️ Internal
3134

book/book.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
[book]
22
authors = ["Miles Johnson"]
33
language = "en"
4-
multilingual = false
54
src = "src"
65
title = "Schematic"
76

crates/schematic/src/schema/renderers/jsonc_template.rs

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,6 @@ impl JsoncTemplateRenderer {
2323
schemas: IndexMap::default(),
2424
}
2525
}
26-
27-
fn format_enum_values(&self, enu: &EnumType) -> String {
28-
let values: Vec<String> = match &enu.variants {
29-
Some(variants) => variants
30-
.iter()
31-
.filter_map(|(_, variant)| {
32-
if variant.hidden {
33-
None
34-
} else if let SchemaType::Literal(lit) = &variant.schema.ty {
35-
Some(lit_to_string(&lit.value))
36-
} else {
37-
None
38-
}
39-
})
40-
.collect(),
41-
None => enu.values.iter().map(lit_to_string).collect(),
42-
};
43-
44-
values.join(" | ")
45-
}
4626
}
4727

4828
impl SchemaRenderer<String> for JsoncTemplateRenderer {
@@ -143,38 +123,14 @@ impl SchemaRenderer<String> for JsoncTemplateRenderer {
143123
let prop = format!(
144124
"\"{}\": {}{}",
145125
name,
146-
self.render_schema(&field.schema)?,
126+
self.render_schema(self.ctx.validate_schema_variant(
127+
self.ctx.get_stack_value().as_ref(),
128+
&field.schema
129+
))?,
147130
if index == last_index { "" } else { "," }
148131
);
149132

150-
let field_output = if let SchemaType::Enum(enu) = &field.schema.ty {
151-
let mut comment = self.ctx.create_field_comment(field);
152-
153-
if self.ctx.options.comments {
154-
let enum_values = self.format_enum_values(enu);
155-
if !enum_values.is_empty() {
156-
let indent = self.ctx.indent();
157-
let prefix = self.ctx.get_comment_prefix();
158-
comment.push_str(&format!("{indent}{prefix}@values {enum_values}\n"));
159-
}
160-
}
161-
162-
let key = self.ctx.get_stack_key();
163-
format!(
164-
"{comment}{}{}{}",
165-
self.ctx.indent(),
166-
if self.ctx.options.comment_fields.contains(&key) {
167-
self.ctx.get_comment_prefix()
168-
} else {
169-
""
170-
},
171-
prop
172-
)
173-
} else {
174-
self.ctx.create_field(field, prop)
175-
};
176-
177-
out.push(field_output);
133+
out.push(self.ctx.create_field(field, prop));
178134
}
179135

180136
self.ctx.pop_stack();

crates/schematic/src/schema/renderers/pkl_template.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ impl SchemaRenderer<String> for PklTemplateRenderer {
132132
"{}{}{}",
133133
name,
134134
if field.schema.is_struct() { " " } else { " = " },
135-
self.render_schema(&field.schema)?,
135+
self.render_schema(self.ctx.validate_schema_variant(
136+
self.ctx.get_stack_value().as_ref(),
137+
&field.schema
138+
))?,
136139
);
137140

138141
out.push(self.ctx.create_field(field, prop));

crates/schematic/src/schema/renderers/template.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use indexmap::IndexMap;
33
use miette::miette;
44
use schematic_types::*;
55
use std::collections::{HashMap, VecDeque};
6+
use std::mem;
67

78
/// Options to control the rendered template.
89
pub struct TemplateOptions {
@@ -15,8 +16,8 @@ pub struct TemplateOptions {
1516
/// Characters to prefix a comment line.
1617
pub comment_prefix: String,
1718

18-
/// Default values for each field within the root struct.
19-
pub default_values: HashMap<String, SchemaType>,
19+
/// Custom values for each field. Supports dot notation.
20+
pub custom_values: HashMap<String, Schema>,
2021

2122
/// List of array and object field names to expand and render a fake item.
2223
pub expand_fields: Vec<String>,
@@ -35,6 +36,9 @@ pub struct TemplateOptions {
3536

3637
/// Insert an extra newline between fields.
3738
pub newline_between_fields: bool,
39+
40+
/// List of field names to only render.
41+
pub only_fields: Vec<String>,
3842
}
3943

4044
impl Default for TemplateOptions {
@@ -43,13 +47,14 @@ impl Default for TemplateOptions {
4347
comments: true,
4448
comment_fields: vec![],
4549
comment_prefix: "# ".into(),
46-
default_values: HashMap::new(),
50+
custom_values: HashMap::new(),
4751
expand_fields: vec![],
4852
footer: String::new(),
4953
header: String::new(),
5054
hide_fields: vec![],
5155
indent_char: " ".into(),
5256
newline_between_fields: true,
57+
only_fields: vec![],
5358
}
5459
}
5560
}
@@ -142,8 +147,17 @@ impl TemplateContext {
142147
});
143148
}
144149

145-
if let Some(env_var) = &field.env_var {
146-
push(format!("@envvar {env_var}"));
150+
if let Some(env_var) = &field.env_var
151+
&& !env_var.is_empty()
152+
{
153+
push(format!("@env {env_var}"));
154+
}
155+
156+
if let SchemaType::Enum(enu) = &field.schema.ty
157+
&& let Ok(enum_values) = render_enum_values(enu)
158+
&& !enum_values.is_empty()
159+
{
160+
push(format!("@values {enum_values}"));
147161
}
148162

149163
if lines.is_empty() {
@@ -189,14 +203,22 @@ impl TemplateContext {
189203
key
190204
}
191205

206+
pub fn get_stack_value(&self) -> Option<Schema> {
207+
let key = self.get_stack_key();
208+
209+
self.options.custom_values.get(&key).cloned()
210+
}
211+
192212
pub fn is_expanded(&self, key: &String) -> bool {
193213
self.options.expand_fields.contains(key)
194214
}
195215

196216
pub fn is_hidden(&self, field: &SchemaField) -> bool {
197217
let key = self.get_stack_key();
198218

199-
field.hidden || self.options.hide_fields.contains(&key)
219+
field.hidden
220+
|| self.options.hide_fields.contains(&key)
221+
|| !self.options.only_fields.is_empty() && !self.options.only_fields.contains(&key)
200222
}
201223

202224
pub fn push_stack(&mut self, name: &str) {
@@ -216,6 +238,27 @@ impl TemplateContext {
216238

217239
initial.to_owned()
218240
}
241+
242+
pub fn validate_schema_variant<'a>(
243+
&self,
244+
custom: Option<&'a Schema>,
245+
fallback: &'a Schema,
246+
) -> &'a Schema {
247+
if let Some(custom) = custom {
248+
if mem::discriminant(&custom.ty) == mem::discriminant(&fallback.ty) {
249+
return custom;
250+
} else {
251+
panic!(
252+
"Received an invalid custom value for `{}`, mismatched schema types.\n\nExpected: {:#?}\n\nReceived: {:#?}",
253+
self.get_stack_key(),
254+
fallback,
255+
custom
256+
);
257+
}
258+
}
259+
260+
fallback
261+
}
219262
}
220263

221264
pub fn render_array(_array: &ArrayType) -> RenderResult {
@@ -240,6 +283,26 @@ pub fn render_enum(enu: &EnumType) -> RenderResult {
240283
render_null()
241284
}
242285

286+
pub fn render_enum_values(enu: &EnumType) -> RenderResult {
287+
let values: Vec<String> = match &enu.variants {
288+
Some(variants) => variants
289+
.iter()
290+
.filter_map(|(_, variant)| {
291+
if variant.hidden {
292+
None
293+
} else if let SchemaType::Literal(lit) = &variant.schema.ty {
294+
Some(lit_to_string(&lit.value))
295+
} else {
296+
None
297+
}
298+
})
299+
.collect(),
300+
None => enu.values.iter().map(lit_to_string).collect(),
301+
};
302+
303+
Ok(values.join(" | "))
304+
}
305+
243306
pub fn render_float(float: &FloatType) -> RenderResult {
244307
if let Some(default) = &float.default {
245308
return Ok(lit_to_string(default));

crates/schematic/src/schema/renderers/toml_template.rs

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,6 @@ impl TomlTemplateRenderer {
3434
}
3535
}
3636

37-
fn format_enum_values(&self, enu: &EnumType) -> String {
38-
let values: Vec<String> = match &enu.variants {
39-
Some(variants) => variants
40-
.iter()
41-
.filter_map(|(_, variant)| {
42-
if variant.hidden {
43-
None
44-
} else if let SchemaType::Literal(lit) = &variant.schema.ty {
45-
Some(lit_to_string(&lit.value))
46-
} else {
47-
None
48-
}
49-
})
50-
.collect(),
51-
None => enu.values.iter().map(lit_to_string).collect(),
52-
};
53-
54-
values.join(" | ")
55-
}
56-
5737
fn extract_sections(&mut self, doc: &mut StructType) {
5838
for (name, field) in &mut doc.fields {
5939
self.ctx.push_stack(name);
@@ -195,36 +175,16 @@ impl SchemaRenderer<String> for TomlTemplateRenderer {
195175
self.ctx.push_stack(name);
196176

197177
if !self.ctx.is_hidden(field) {
198-
let prop = format!("{} = {}", name, self.render_schema(&field.schema)?);
199-
200-
let field_output = if let SchemaType::Enum(enu) = &field.schema.ty {
201-
let mut comment = self.ctx.create_field_comment(field);
202-
203-
if self.ctx.options.comments {
204-
let enum_values = self.format_enum_values(enu);
205-
if !enum_values.is_empty() {
206-
let indent = self.ctx.indent();
207-
let prefix = self.ctx.get_comment_prefix();
208-
comment.push_str(&format!("{indent}{prefix}@values {enum_values}\n"));
209-
}
210-
}
211-
212-
let key = self.ctx.get_stack_key();
213-
format!(
214-
"{comment}{}{}{}",
215-
self.ctx.indent(),
216-
if self.ctx.options.comment_fields.contains(&key) {
217-
self.ctx.get_comment_prefix()
218-
} else {
219-
""
220-
},
221-
prop
222-
)
223-
} else {
224-
self.ctx.create_field(field, prop)
225-
};
226-
227-
out.push(field_output);
178+
let prop = format!(
179+
"{} = {}",
180+
name,
181+
self.render_schema(self.ctx.validate_schema_variant(
182+
self.ctx.get_stack_value().as_ref(),
183+
&field.schema
184+
))?
185+
);
186+
187+
out.push(self.ctx.create_field(field, prop));
228188
}
229189

230190
self.ctx.pop_stack();

crates/schematic/src/schema/renderers/yaml_template.rs

Lines changed: 6 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,6 @@ impl YamlTemplateRenderer {
2121
schemas: IndexMap::default(),
2222
}
2323
}
24-
25-
fn format_enum_values(&self, enu: &EnumType) -> String {
26-
let values: Vec<String> = match &enu.variants {
27-
Some(variants) => variants
28-
.iter()
29-
.filter_map(|(_, variant)| {
30-
if variant.hidden {
31-
None
32-
} else if let SchemaType::Literal(lit) = &variant.schema.ty {
33-
Some(lit_to_string(&lit.value))
34-
} else {
35-
None
36-
}
37-
})
38-
.collect(),
39-
None => enu.values.iter().map(lit_to_string).collect(),
40-
};
41-
42-
values.join(" | ")
43-
}
4424
}
4525

4626
impl SchemaRenderer<String> for YamlTemplateRenderer {
@@ -152,7 +132,11 @@ impl SchemaRenderer<String> for YamlTemplateRenderer {
152132
self.ctx.depth += 1;
153133
}
154134

155-
let value = self.render_schema(&field.schema)?;
135+
let value = self.render_schema(
136+
self.ctx
137+
.validate_schema_variant(self.ctx.get_stack_value().as_ref(), &field.schema),
138+
)?;
139+
156140
let prop = format!(
157141
"{}:{}{}",
158142
name,
@@ -168,34 +152,7 @@ impl SchemaRenderer<String> for YamlTemplateRenderer {
168152
self.ctx.depth -= 1;
169153
}
170154

171-
let field_output = if let SchemaType::Enum(enu) = &field.schema.ty {
172-
let mut comment = self.ctx.create_field_comment(field);
173-
174-
if self.ctx.options.comments {
175-
let enum_values = self.format_enum_values(enu);
176-
if !enum_values.is_empty() {
177-
let indent = self.ctx.indent();
178-
let prefix = self.ctx.get_comment_prefix();
179-
comment.push_str(&format!("{indent}{prefix}@values {enum_values}\n"));
180-
}
181-
}
182-
183-
let key = self.ctx.get_stack_key();
184-
format!(
185-
"{comment}{}{}{}",
186-
self.ctx.indent(),
187-
if self.ctx.options.comment_fields.contains(&key) {
188-
self.ctx.get_comment_prefix()
189-
} else {
190-
""
191-
},
192-
prop
193-
)
194-
} else {
195-
self.ctx.create_field(field, prop)
196-
};
197-
198-
out.push(field_output);
155+
out.push(self.ctx.create_field(field, prop));
199156

200157
self.ctx.pop_stack();
201158
}

0 commit comments

Comments
 (0)