Skip to content

Commit 80e9be4

Browse files
committed
feat(dtoken): support jsonc format
Signed-off-by: Jean Mertz <git@jeanmertz.com>
1 parent 1930d85 commit 80e9be4

File tree

4 files changed

+174
-53
lines changed

4 files changed

+174
-53
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/dtoken/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ bevy_reflect = { version = "0.15", default-features = false, optional = true }
4141
# optional data formats
4242
toml-span = { version = "0.4", default-features = false, optional = true }
4343
ason = { version = "1", default-features = false, optional = true }
44+
jsonc-parser = { version = "0.26", default-features = false, optional = true }
4445

4546
[features]
4647
default = ["build", "rustfmt"]
4748
build = ["dep:convert_case", "dep:proc-macro2", "dep:quote"]
4849
toml = ["dep:toml-span"]
4950
ason = ["dep:ason"]
51+
jsonc = ["dep:jsonc-parser"]
5052
bevy = ["dep:bevy_ui", "dep:bevy_color"]
5153
reflect = ["dep:bevy_reflect"]
5254
rustfmt = []

crates/dtoken/src/build.rs

Lines changed: 152 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,32 @@ fn read_file(path: impl AsRef<str>) -> Result<String, BuildError> {
2828

2929
fn parse_content(content: &str) -> Result<HashMap<String, JsonValue>, BuildError> {
3030
#[cfg(all(feature = "ason", feature = "toml"))]
31-
eprintln!("Warning: both `ason` and `toml` features are enabled. Using `json` parser.");
31+
eprintln!(
32+
"Warning: any two of `ason`, `toml` or `jsonc` features are enabled. Using `json` parser."
33+
);
3234

33-
#[cfg(all(feature = "ason", not(feature = "toml")))]
35+
#[cfg(all(feature = "ason", not(any(feature = "toml", feature = "jsonc"))))]
3436
{
3537
let json: ason::ast::AsonNode = ason::parse_from_str(content)?;
3638
return ason_node_to_json_value(json);
3739
}
3840

39-
#[cfg(all(feature = "toml", not(feature = "ason")))]
41+
#[cfg(all(feature = "toml", not(any(feature = "ason", feature = "jsonc"))))]
4042
{
4143
let value = toml_span::parse(content)?.take();
4244
return toml_value_to_json_value(value);
4345
}
4446

47+
#[cfg(all(feature = "jsonc", not(any(feature = "ason", feature = "toml"))))]
48+
{
49+
let opts = jsonc_parser::ParseOptions::default();
50+
let value = jsonc_parser::parse_to_value(content, &opts)?;
51+
jsonc_value_to_json_value(value.ok_or(BuildError::Parse(Error::ExpectedObject))?)
52+
}
53+
4554
#[cfg(any(
46-
not(any(feature = "ason", feature = "toml")),
47-
all(feature = "ason", feature = "toml")
55+
not(any(feature = "ason", feature = "toml", feature = "jsonc")),
56+
all(feature = "ason", feature = "toml", feature = "jsonc")
4857
))]
4958
return content
5059
.parse::<JsonValue>()?
@@ -114,16 +123,16 @@ fn deep_merge(target: &mut HashMap<String, JsonValue>, source: HashMap<String, J
114123
}
115124
}
116125

117-
#[cfg(all(feature = "toml", not(feature = "ason")))]
126+
#[cfg(all(feature = "toml", not(any(feature = "ason", feature = "jsonc"))))]
118127
fn toml_value_to_json_value(
119128
value: toml_span::value::ValueInner<'_>,
120129
) -> Result<HashMap<String, JsonValue>, BuildError> {
121130
use toml_span::value::ValueInner;
122131

123132
match value {
124-
ValueInner::Table(table) => {
133+
ValueInner::Table(v) => {
125134
let mut map = HashMap::new();
126-
for (key, mut value) in table {
135+
for (key, mut value) in v {
127136
map.insert(key.name.to_string(), convert_value(value.take())?);
128137
}
129138
Ok(map)
@@ -132,50 +141,50 @@ fn toml_value_to_json_value(
132141
}
133142
}
134143

135-
#[cfg(all(feature = "toml", not(feature = "ason")))]
144+
#[cfg(all(feature = "toml", not(any(feature = "ason", feature = "jsonc"))))]
136145
fn convert_value(value: toml_span::value::ValueInner<'_>) -> Result<JsonValue, BuildError> {
137146
use toml_span::value::ValueInner;
138147

139148
match value {
140-
ValueInner::String(s) => Ok(JsonValue::String(s.to_string())),
149+
ValueInner::String(v) => Ok(JsonValue::String(v.to_string())),
141150
#[allow(clippy::cast_precision_loss)]
142-
ValueInner::Integer(i) => Ok(JsonValue::Number(i as f64)),
143-
ValueInner::Float(f) => Ok(JsonValue::Number(f)),
144-
ValueInner::Boolean(b) => Ok(JsonValue::Boolean(b)),
145-
ValueInner::Array(arr) => {
146-
let mut json_arr = Vec::new();
147-
for mut item in arr {
148-
json_arr.push(convert_value(item.take())?);
151+
ValueInner::Integer(v) => Ok(JsonValue::Number(v as f64)),
152+
ValueInner::Float(v) => Ok(JsonValue::Number(v)),
153+
ValueInner::Boolean(v) => Ok(JsonValue::Boolean(v)),
154+
ValueInner::Array(v) => {
155+
let mut arr = Vec::new();
156+
for mut item in v {
157+
arr.push(convert_value(item.take())?);
149158
}
150-
Ok(JsonValue::Array(json_arr))
159+
Ok(JsonValue::Array(arr))
151160
}
152-
ValueInner::Table(table) => {
161+
ValueInner::Table(v) => {
153162
let mut map = HashMap::new();
154-
for (key, mut value) in table {
163+
for (key, mut value) in v {
155164
map.insert(key.name.to_string(), convert_value(value.take())?);
156165
}
157166
Ok(JsonValue::Object(map))
158167
}
159168
}
160169
}
161170

162-
#[cfg(all(feature = "ason", not(feature = "toml")))]
171+
#[cfg(all(feature = "ason", not(any(feature = "toml", feature = "jsonc"))))]
163172
pub fn ason_node_to_json_value(
164173
node: ason::ast::AsonNode,
165174
) -> Result<HashMap<String, JsonValue>, BuildError> {
166175
use ason::ast::AsonNode;
167176

168177
match node {
169-
AsonNode::Object(pairs) => {
178+
AsonNode::Object(v) => {
170179
let mut map = HashMap::new();
171-
for pair in pairs {
180+
for pair in v {
172181
map.insert(pair.key, convert_node(*pair.value)?);
173182
}
174183
Ok(map)
175184
}
176-
AsonNode::Map(pairs) => {
185+
AsonNode::Map(v) => {
177186
let mut map = HashMap::new();
178-
for pair in pairs {
187+
for pair in v {
179188
match convert_node(*pair.name)? {
180189
JsonValue::String(key) => {
181190
map.insert(key, convert_node(*pair.value)?);
@@ -189,31 +198,31 @@ pub fn ason_node_to_json_value(
189198
}
190199
}
191200

192-
#[cfg(all(feature = "ason", not(feature = "toml")))]
201+
#[cfg(all(feature = "ason", not(any(feature = "toml", feature = "jsonc"))))]
193202
fn convert_node(node: ason::ast::AsonNode) -> Result<JsonValue, BuildError> {
194203
use ason::ast::AsonNode;
195204

196205
match node {
197-
AsonNode::Number(num) => Ok(JsonValue::Number(convert_number(num))),
198-
AsonNode::Boolean(b) => Ok(JsonValue::Boolean(b)),
199-
AsonNode::String(s) => Ok(JsonValue::String(s)),
200-
AsonNode::List(items) => {
201-
let mut json_arr = Vec::new();
202-
for item in items {
203-
json_arr.push(convert_node(item)?);
206+
AsonNode::Number(v) => Ok(JsonValue::Number(convert_number(v))),
207+
AsonNode::Boolean(v) => Ok(JsonValue::Boolean(v)),
208+
AsonNode::String(v) => Ok(JsonValue::String(v)),
209+
AsonNode::List(v) => {
210+
let mut arr = Vec::new();
211+
for item in v {
212+
arr.push(convert_node(item)?);
204213
}
205-
Ok(JsonValue::Array(json_arr))
214+
Ok(JsonValue::Array(arr))
206215
}
207-
AsonNode::Object(pairs) => {
216+
AsonNode::Object(v) => {
208217
let mut map = HashMap::new();
209-
for pair in pairs {
218+
for pair in v {
210219
map.insert(pair.key, convert_node(*pair.value)?);
211220
}
212221
Ok(JsonValue::Object(map))
213222
}
214-
AsonNode::Map(pairs) => {
223+
AsonNode::Map(v) => {
215224
let mut map = HashMap::new();
216-
for pair in pairs {
225+
for pair in v {
217226
match convert_node(*pair.name)? {
218227
JsonValue::String(key) => {
219228
map.insert(key, convert_node(*pair.value)?);
@@ -228,26 +237,81 @@ fn convert_node(node: ason::ast::AsonNode) -> Result<JsonValue, BuildError> {
228237
}
229238
}
230239

231-
#[cfg(all(feature = "ason", not(feature = "toml")))]
240+
#[cfg(all(feature = "ason", not(any(feature = "toml", feature = "jsonc"))))]
232241
fn convert_number(num: ason::ast::Number) -> f64 {
233242
use ason::ast::Number;
234243

235244
match num {
236-
Number::I8(n) => n as f64,
237-
Number::U8(n) => n as f64,
238-
Number::I16(n) => n as f64,
239-
Number::U16(n) => n as f64,
240-
Number::I32(n) => n as f64,
241-
Number::U32(n) => n as f64,
245+
Number::I8(v) => v as f64,
246+
Number::U8(v) => v as f64,
247+
Number::I16(v) => v as f64,
248+
Number::U16(v) => v as f64,
249+
Number::I32(v) => v as f64,
250+
Number::U32(v) => v as f64,
242251
#[allow(clippy::cast_precision_loss)]
243-
Number::I64(n) => n as f64,
252+
Number::I64(v) => v as f64,
244253
#[allow(clippy::cast_precision_loss)]
245-
Number::U64(n) => n as f64,
246-
Number::F32(n) => n as f64,
247-
Number::F64(n) => n,
254+
Number::U64(v) => v as f64,
255+
Number::F32(v) => v as f64,
256+
Number::F64(v) => v,
248257
}
249258
}
250259

260+
#[cfg(all(feature = "jsonc", not(any(feature = "ason", feature = "toml"))))]
261+
fn jsonc_value_to_json_value(
262+
value: jsonc_parser::JsonValue<'_>,
263+
) -> Result<HashMap<String, JsonValue>, BuildError> {
264+
match value {
265+
jsonc_parser::JsonValue::Object(v) => {
266+
let mut map = HashMap::new();
267+
for (key, value) in v {
268+
map.insert(key, convert_jsonc_value(value)?);
269+
}
270+
Ok(map)
271+
}
272+
273+
_ => Err(BuildError::Parse(Error::ExpectedObject)),
274+
}
275+
}
276+
277+
#[cfg(all(feature = "jsonc", not(any(feature = "ason", feature = "toml"))))]
278+
fn convert_jsonc_value(value: jsonc_parser::JsonValue<'_>) -> Result<JsonValue, BuildError> {
279+
match value {
280+
jsonc_parser::JsonValue::String(v) => Ok(JsonValue::String(v.into_owned())),
281+
jsonc_parser::JsonValue::Number(v) => convert_number(v),
282+
jsonc_parser::JsonValue::Boolean(v) => Ok(JsonValue::Boolean(v)),
283+
jsonc_parser::JsonValue::Object(v) => {
284+
let mut map = HashMap::new();
285+
for (key, value) in v {
286+
map.insert(key, convert_jsonc_value(value)?);
287+
}
288+
Ok(JsonValue::Object(map))
289+
}
290+
jsonc_parser::JsonValue::Array(v) => {
291+
let mut arr = Vec::new();
292+
for item in v {
293+
arr.push(convert_jsonc_value(item)?);
294+
}
295+
Ok(JsonValue::Array(arr))
296+
}
297+
jsonc_parser::JsonValue::Null => Ok(JsonValue::Null),
298+
}
299+
}
300+
301+
#[cfg(all(feature = "jsonc", not(any(feature = "ason", feature = "toml"))))]
302+
fn convert_number(n: &str) -> Result<JsonValue, BuildError> {
303+
if let Ok(num) = n.parse::<i64>() {
304+
#[allow(clippy::cast_precision_loss)]
305+
return Ok(JsonValue::Number(num as f64));
306+
}
307+
308+
if let Ok(num) = n.parse::<f64>() {
309+
return Ok(JsonValue::Number(num));
310+
}
311+
312+
Err(BuildError::Parse(Error::ExpectedNumber))
313+
}
314+
251315
fn generate(tokens: &DesignTokens) -> TokenStream {
252316
Generator::new(tokens).generate()
253317
}
@@ -558,8 +622,8 @@ mod tests {
558622
use super::*;
559623

560624
#[cfg(any(
561-
not(any(feature = "ason", feature = "toml")),
562-
all(feature = "ason", feature = "toml")
625+
not(any(feature = "ason", feature = "toml", feature = "jsonc")),
626+
all(feature = "ason", feature = "toml", feature = "jsonc")
563627
))]
564628
#[test]
565629
fn test_json() {
@@ -590,7 +654,7 @@ mod tests {
590654
}
591655
}
592656

593-
#[cfg(all(feature = "toml", not(feature = "ason")))]
657+
#[cfg(all(feature = "toml", not(any(feature = "ason", feature = "jsonc"))))]
594658
#[test]
595659
fn test_toml() {
596660
let test_cases = [indoc! {r#"
@@ -617,7 +681,7 @@ mod tests {
617681
}
618682
}
619683

620-
#[cfg(all(feature = "ason", not(feature = "toml")))]
684+
#[cfg(all(feature = "ason", not(any(feature = "toml", feature = "jsonc"))))]
621685
#[test]
622686
fn test_ason() {
623687
let test_cases = [indoc! {r#"
@@ -647,6 +711,41 @@ mod tests {
647711
}
648712
}
649713

714+
#[cfg(all(feature = "jsonc", not(any(feature = "ason", feature = "toml"))))]
715+
#[test]
716+
fn test_jsonc() {
717+
let test_cases = [indoc! {r#"
718+
{
719+
"group name": {
720+
"token name": {
721+
"$value": 1234,
722+
"$type": "number",
723+
},
724+
},
725+
// A comment
726+
"alias name": { // Another comment
727+
"$value": "{group name.token name}",
728+
},
729+
}
730+
"#}];
731+
732+
for (i, case) in test_cases.iter().enumerate() {
733+
let map: HashMap<String, JsonValue> = parse_content(case).unwrap();
734+
let tokens = DesignTokens::from_map(&map).unwrap();
735+
736+
let tokens = generate(&tokens);
737+
let abstract_file: File =
738+
syn::parse2(tokens.clone()).unwrap_or_else(|err| panic!("{err}:\n\n{tokens}"));
739+
let code = prettyplease::unparse(&abstract_file);
740+
741+
insta::assert_snapshot!(format!("json case {i}"), code.to_string());
742+
}
743+
}
744+
745+
#[cfg(any(
746+
not(any(feature = "ason", feature = "toml", feature = "jsonc")),
747+
all(feature = "ason", feature = "toml", feature = "jsonc")
748+
))]
650749
#[test]
651750
fn test_merged_content() {
652751
let contents = [

0 commit comments

Comments
 (0)