Skip to main content

styx_tree/
lib.rs

1#![doc = include_str!("../README.md")]
2//! Document tree representation for Styx configuration files.
3//!
4//! This crate provides a high-level API for working with Styx documents,
5//! including parsing, accessing values by path, and serialization.
6
7mod builder;
8mod diagnostic;
9mod value;
10
11pub use builder::{BuildError, TreeBuilder};
12pub use diagnostic::ParseError;
13pub use styx_parse::{ParseErrorKind, ScalarKind, Span};
14pub use value::{Entry, Object, Payload, Scalar, Sequence, Tag, Value};
15
16/// Parse a Styx document into a tree.
17pub fn parse(source: &str) -> Result<Value, BuildError> {
18    let mut parser = styx_parse::Parser::new(source);
19    let mut builder = TreeBuilder::new();
20    while let Some(event) = parser.next_event() {
21        builder.event(event);
22    }
23    builder.finish()
24}
25
26/// A Styx document (root is always an implicit object).
27#[derive(Debug, Clone, PartialEq)]
28pub struct Document {
29    /// The root object.
30    pub root: Object,
31    /// Leading doc comments (before first entry).
32    pub leading_comments: Vec<String>,
33}
34
35impl Document {
36    /// Parse a Styx document.
37    pub fn parse(source: &str) -> Result<Self, BuildError> {
38        let value = parse(source)?;
39        match value.payload {
40            Some(Payload::Object(root)) => Ok(Document {
41                root,
42                leading_comments: Vec::new(),
43            }),
44            _ => Err(BuildError::UnexpectedEvent(
45                "expected object at root".to_string(),
46            )),
47        }
48    }
49
50    /// Get a value by path.
51    pub fn get(&self, path: &str) -> Option<&Value> {
52        if path.is_empty() {
53            return None;
54        }
55
56        let (segment, rest) = split_path(path);
57        let value = self.root.get(segment)?;
58        if rest.is_empty() {
59            Some(value)
60        } else {
61            value.get(rest)
62        }
63    }
64}
65
66fn split_path(path: &str) -> (&str, &str) {
67    if path.starts_with('[')
68        && let Some(end) = path.find(']')
69    {
70        let segment = &path[..=end];
71        let rest = &path[end + 1..];
72        let rest = rest.strip_prefix('.').unwrap_or(rest);
73        return (segment, rest);
74    }
75
76    let dot_pos = path.find('.');
77    let bracket_pos = path.find('[');
78
79    match (dot_pos, bracket_pos) {
80        (Some(d), Some(b)) if b < d => (&path[..b], &path[b..]),
81        (Some(d), _) => (&path[..d], &path[d + 1..]),
82        (None, Some(b)) => (&path[..b], &path[b..]),
83        (None, None) => (path, ""),
84    }
85}
86
87#[cfg(test)]
88mod tests;