1#![doc = include_str!("../README.md")]
2
3use neco_ast::{
4 StructuredDocument as AstStructuredDocument, StructuredField, StructuredNumber, StructuredValue,
5};
6use neco_json5::{parse as parse_value, Json5Value, ParseError};
7use std::borrow::Cow;
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct Json5Document {
11 value: Json5Value,
12}
13
14#[derive(Debug, Clone, Copy)]
15pub struct Json5Node<'a> {
16 key: Option<&'a str>,
17 value: &'a Json5Value,
18}
19
20pub fn parse(input: &str) -> Result<Json5Document, ParseError> {
21 parse_value(input).map(Json5Document::from_value)
22}
23
24impl Json5Document {
25 pub fn from_value(value: Json5Value) -> Self {
26 Self { value }
27 }
28
29 pub fn as_value(&self) -> &Json5Value {
30 &self.value
31 }
32}
33
34impl<'a> Json5Node<'a> {
35 pub fn from_value(value: &'a Json5Value) -> Self {
36 Self { key: None, value }
37 }
38
39 pub fn as_value(&self) -> &'a Json5Value {
40 self.value
41 }
42}
43
44impl<'a> AstStructuredDocument<'a> for Json5Document {
45 type Node = Json5Node<'a>;
46
47 fn nodes(&'a self) -> Vec<Self::Node> {
48 match &self.value {
49 Json5Value::Map(fields) => fields
50 .iter()
51 .map(|(key, value)| Json5Node {
52 key: Some(key.as_str()),
53 value,
54 })
55 .collect(),
56 _ => vec![Json5Node::from_value(&self.value)],
57 }
58 }
59}
60
61impl<'a> neco_ast::StructuredNode<'a> for Json5Node<'a> {
62 fn kind(&self) -> Cow<'a, str> {
63 Cow::Borrowed(self.key.unwrap_or("root"))
64 }
65
66 fn identifier(&self) -> Option<Cow<'a, str>> {
67 self.key.map(Cow::Borrowed)
68 }
69
70 fn attribute(&self, key: &str) -> Option<StructuredValue<'a>> {
71 match self.value {
72 Json5Value::Map(fields) => fields
73 .iter()
74 .find_map(|(field, value)| (field == key).then(|| value_to_structured(value))),
75 _ => None,
76 }
77 }
78
79 fn children(&self) -> Vec<Self> {
80 match self.value {
81 Json5Value::Map(fields) => fields
82 .iter()
83 .map(|(key, value)| Json5Node {
84 key: Some(key.as_str()),
85 value,
86 })
87 .collect(),
88 Json5Value::List(values) => values
89 .iter()
90 .map(|value| Json5Node {
91 key: Some("item"),
92 value,
93 })
94 .collect(),
95 _ => Vec::new(),
96 }
97 }
98
99 fn value(&self) -> StructuredValue<'a> {
100 value_to_structured(self.value)
101 }
102}
103
104fn value_to_structured<'a>(value: &'a Json5Value) -> StructuredValue<'a> {
105 match value {
106 Json5Value::Null => StructuredValue::Null,
107 Json5Value::Bool(value) => StructuredValue::Bool(*value),
108 Json5Value::Number(value) => StructuredValue::Number(StructuredNumber::from_f64(*value)),
109 Json5Value::String(value) => StructuredValue::String(Cow::Borrowed(value.as_str())),
110 Json5Value::List(values) => {
111 StructuredValue::Sequence(values.iter().map(value_to_structured).collect())
112 }
113 Json5Value::Map(fields) => StructuredValue::Mapping(
114 fields
115 .iter()
116 .map(|(key, value)| StructuredField {
117 key: Cow::Borrowed(key.as_str()),
118 value: value_to_structured(value),
119 })
120 .collect(),
121 ),
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::{parse, value_to_structured, Json5Document, Json5Node};
128 use neco_ast::{StructuredDocument, StructuredNode, StructuredValue};
129 use neco_json5::Json5Value;
130
131 const SAMPLE: &str = "name = neco\nenabled = true\nitems = [one, two]\n";
132
133 #[test]
134 fn parse_document() {
135 assert!(parse(SAMPLE).is_ok());
136 }
137
138 #[test]
139 fn document_nodes_read_map_fields() {
140 let doc = parse(SAMPLE).expect("parse");
141 assert_eq!(doc.nodes().len(), 3);
142 }
143
144 #[test]
145 fn node_kind_uses_field_key() {
146 let doc = parse(SAMPLE).expect("parse");
147 assert_eq!(doc.nodes()[0].kind(), "name");
148 }
149
150 #[test]
151 fn node_identifier_uses_field_key() {
152 let doc = parse(SAMPLE).expect("parse");
153 assert_eq!(doc.nodes()[0].identifier().as_deref(), Some("name"));
154 }
155
156 #[test]
157 fn mapping_attribute_reads_string() {
158 let value = Json5Value::Map(vec![("name".into(), Json5Value::String("neco".into()))]);
159 let node = Json5Node::from_value(&value);
160 assert_eq!(node.attribute_str("name").as_deref(), Some("neco"));
161 }
162
163 #[test]
164 fn mapping_attribute_reads_bool() {
165 let value = Json5Value::Map(vec![("enabled".into(), Json5Value::Bool(true))]);
166 let node = Json5Node::from_value(&value);
167 assert_eq!(node.attribute_bool("enabled"), Some(true));
168 }
169
170 #[test]
171 fn missing_attribute_is_none() {
172 let value = Json5Value::Map(Vec::new());
173 let node = Json5Node::from_value(&value);
174 assert!(node.attribute("missing").is_none());
175 }
176
177 #[test]
178 fn list_children_are_items() {
179 let value = Json5Value::List(vec![Json5Value::String("one".into())]);
180 let node = Json5Node::from_value(&value);
181 assert_eq!(node.children()[0].kind(), "item");
182 }
183
184 #[test]
185 fn value_mapping_preserves_key() {
186 let value = Json5Value::Map(vec![("name".into(), Json5Value::String("neco".into()))]);
187 let structured = value_to_structured(&value);
188 assert_eq!(structured.as_mapping().expect("mapping")[0].key, "name");
189 }
190
191 #[test]
192 fn scalar_value_preserves_string() {
193 let value = Json5Value::String("neco".into());
194 let structured = value_to_structured(&value);
195 assert_eq!(structured.as_str(), Some("neco"));
196 }
197
198 #[test]
199 fn document_from_value_keeps_root_scalar() {
200 let doc = Json5Document::from_value(Json5Value::String("neco".into()));
201 assert_eq!(
202 doc.nodes()[0].value(),
203 StructuredValue::String("neco".into())
204 );
205 }
206}