1#![doc = include_str!("../README.md")]
2mod builder;
8mod diagnostic;
9mod value;
10
11pub use builder::{BuildError, TreeBuilder};
12pub use diagnostic::ParseError;
13pub use styx_parse::{ParseErrorKind, ScalarKind, Separator, Span};
14pub use value::{Entry, Object, Payload, Scalar, Sequence, Tag, Value};
15
16pub fn parse(source: &str) -> Result<Value, BuildError> {
18 let parser = styx_parse::Parser::new(source);
19 let mut builder = TreeBuilder::new();
20 parser.parse(&mut builder);
21 builder.finish()
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct Document {
27 pub root: Object,
29 pub leading_comments: Vec<String>,
31}
32
33impl Document {
34 pub fn parse(source: &str) -> Result<Self, BuildError> {
36 let value = parse(source)?;
37 match value.payload {
38 Some(Payload::Object(root)) => Ok(Document {
39 root,
40 leading_comments: Vec::new(),
41 }),
42 _ => Err(BuildError::UnexpectedEvent(
43 "expected object at root".to_string(),
44 )),
45 }
46 }
47
48 pub fn get(&self, path: &str) -> Option<&Value> {
50 if path.is_empty() {
51 return None;
52 }
53
54 let (segment, rest) = split_path(path);
55 let value = self.root.get(segment)?;
56 if rest.is_empty() {
57 Some(value)
58 } else {
59 value.get(rest)
60 }
61 }
62}
63
64fn split_path(path: &str) -> (&str, &str) {
65 if path.starts_with('[')
66 && let Some(end) = path.find(']')
67 {
68 let segment = &path[..=end];
69 let rest = &path[end + 1..];
70 let rest = rest.strip_prefix('.').unwrap_or(rest);
71 return (segment, rest);
72 }
73
74 let dot_pos = path.find('.');
75 let bracket_pos = path.find('[');
76
77 match (dot_pos, bracket_pos) {
78 (Some(d), Some(b)) if b < d => (&path[..b], &path[b..]),
79 (Some(d), _) => (&path[..d], &path[d + 1..]),
80 (None, Some(b)) => (&path[..b], &path[b..]),
81 (None, None) => (path, ""),
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_parse_simple() {
91 let doc = Document::parse("name Alice\nage 30").unwrap();
92 assert_eq!(doc.get("name").and_then(|v| v.as_str()), Some("Alice"));
93 assert_eq!(doc.get("age").and_then(|v| v.as_str()), Some("30"));
94 }
95
96 #[test]
97 fn test_parse_empty() {
98 let doc = Document::parse("").unwrap();
99 assert!(doc.root.is_empty());
100 }
101
102 #[test]
103 fn test_convenience_parse() {
104 let value = parse("greeting hello").unwrap();
105 assert_eq!(
106 value.get("greeting").and_then(|v| v.as_str()),
107 Some("hello")
108 );
109 }
110
111 #[test]
112 fn test_schema_tree_structure() {
113 let source = r#"schema {
121 @ @object{
122 name @string
123 }
124}"#;
125 let value = parse(source).unwrap();
126
127 let obj = value.as_object().expect("root should be object");
129 assert_eq!(obj.len(), 1);
130
131 let schema = obj.get("schema").expect("should have schema key");
133 let schema_obj = schema.as_object().expect("schema should be object");
134
135 assert_eq!(schema_obj.len(), 1);
137 let entry = &schema_obj.entries[0];
138
139 assert!(
141 entry.key.is_unit(),
142 "key should be unit, got {:?}",
143 entry.key
144 );
145
146 assert_eq!(
148 entry.value.tag_name(),
149 Some("object"),
150 "value should have tag 'object'"
151 );
152
153 let payload = entry
155 .value
156 .payload
157 .as_ref()
158 .expect("@object should have payload");
159 let payload_obj = match payload {
160 value::Payload::Object(obj) => obj,
161 _ => panic!("payload should be object, got {:?}", payload),
162 };
163 assert_eq!(payload_obj.len(), 1);
164
165 let name_entry = &payload_obj.entries[0];
167 assert_eq!(name_entry.key.as_str(), Some("name"));
168
169 assert_eq!(
171 name_entry.value.tag_name(),
172 Some("string"),
173 "@string should have tag 'string'"
174 );
175 assert!(
176 name_entry.value.payload.is_none(),
177 "@string should have no payload"
178 );
179 }
180}