guix-scheme 0.1.0

Read, edit, and generate Guix Scheme files (channels.scm, system config.scm) from Rust
Documentation
//! Generic view over `(record-name (field value) ...)` forms.

use scheme_edit::{list, sym, Item, Node};

/// Read-only view of a record-shaped list node.
pub struct RecordView<'a> {
    node: &'a Node,
}

impl<'a> RecordView<'a> {
    /// `None` unless NODE is a list with a symbol head.
    pub fn new(node: &'a Node) -> Option<Self> {
        node.head_symbol()?;
        Some(Self { node })
    }

    pub fn record_name(&self) -> &'a str {
        // new() guarantees a symbol head.
        self.node.head_symbol().unwrap_or_default()
    }

    /// Value node of the first `(name value)` child.
    pub fn field(&self, name: &str) -> Option<&'a Node> {
        field_value(self.node, name)
    }

    pub fn symbol_field(&self, name: &str) -> Option<&'a str> {
        self.field(name)?.as_symbol()
    }

    pub fn string_field(&self, name: &str) -> Option<String> {
        self.field(name)?.as_string_lit()
    }
}

/// Mutable view: same getters plus field editing.
pub struct RecordViewMut<'a> {
    node: &'a mut Node,
}

impl<'a> RecordViewMut<'a> {
    /// `None` unless NODE is a list with a symbol head.
    pub fn new(node: &'a mut Node) -> Option<Self> {
        node.head_symbol()?;
        Some(Self { node })
    }

    pub fn record_name(&self) -> &str {
        self.node.head_symbol().unwrap_or_default()
    }

    pub fn field(&self, name: &str) -> Option<&Node> {
        field_value(self.node, name)
    }

    pub fn symbol_field(&self, name: &str) -> Option<&str> {
        self.field(name)?.as_symbol()
    }

    pub fn string_field(&self, name: &str) -> Option<String> {
        self.field(name)?.as_string_lit()
    }

    /// Mutable value node of the first `(name value)` child, for
    /// in-place edits inside field expressions.
    pub fn field_mut(&mut self, name: &str) -> Option<&mut Node> {
        let idx = self.node.position_of(|n| n.head_symbol() == Some(name))?;
        self.node.data_child_mut(idx)?.data_child_mut(1)
    }

    /// Replace the value of `(name value)` in place, or append a new
    /// `(name value)` child when the field is absent.
    pub fn set_field(&mut self, name: &str, value: Node) {
        if let Node::List { items, .. } = &mut *self.node {
            for item in items.iter_mut() {
                if let Item::Node(n) = item {
                    if n.head_symbol() == Some(name) {
                        if n.data_len() >= 2 {
                            n.replace_child(1, value);
                        } else {
                            n.push_child(value);
                        }
                        return;
                    }
                }
            }
        }
        self.node.push_child(list(vec![sym(name), value]));
    }

    /// Remove the `(name value)` child, taking attached leading trivia.
    pub fn remove_field(&mut self, name: &str) -> bool {
        let Some(idx) = self.node.position_of(|n| n.head_symbol() == Some(name)) else {
            return false;
        };
        self.node.remove_child(idx, true).is_some()
    }
}

fn field_value<'n>(node: &'n Node, name: &str) -> Option<&'n Node> {
    node.list_nodes()
        .skip(1)
        .find(|n| n.head_symbol() == Some(name))?
        .data_child(1)
}