weavatrix-rust 2.16.0

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use weavatrix_graph::{SourcePosition, SourceSpan};

#[derive(Debug, Clone)]
pub(crate) enum Node {
    Scalar(Scalar),
    Sequence {
        items: Vec<Node>,
        start: usize,
        end: usize,
    },
    Mapping {
        entries: Vec<(Scalar, Node)>,
        start: usize,
        end: usize,
    },
}

#[derive(Debug, Clone)]
pub(crate) struct Scalar {
    pub decoded: String,
    pub raw_start: usize,
    pub raw_end: usize,
}

impl Node {
    pub(crate) fn get(&self, key: &str) -> Option<&Node> {
        self.entries()?
            .iter()
            .find(|(name, _)| name.decoded == key)
            .map(|(_, value)| value)
    }

    pub(crate) fn entries(&self) -> Option<&[(Scalar, Node)]> {
        match self {
            Self::Mapping { entries, .. } => Some(entries),
            _ => None,
        }
    }

    pub(crate) fn items(&self) -> Option<&[Node]> {
        match self {
            Self::Sequence { items, .. } => Some(items),
            _ => None,
        }
    }

    pub(crate) fn as_str(&self) -> Option<&str> {
        match self {
            Self::Scalar(scalar) => Some(scalar.decoded.as_str()),
            _ => None,
        }
    }

    pub(crate) fn as_bool(&self) -> Option<bool> {
        match self.as_str()? {
            "true" | "True" | "yes" => Some(true),
            "false" | "False" | "no" => Some(false),
            _ => None,
        }
    }

    pub(crate) fn range(&self) -> (usize, usize) {
        match self {
            Self::Scalar(scalar) => (scalar.raw_start, scalar.raw_end),
            Self::Sequence { start, end, .. } | Self::Mapping { start, end, .. } => (*start, *end),
        }
    }

    #[allow(dead_code)]
    pub(crate) fn strings(&self) -> Vec<&Scalar> {
        let mut out = Vec::new();
        collect_strings(self, &mut out);
        out
    }
}

#[allow(dead_code)]
fn collect_strings<'a>(node: &'a Node, out: &mut Vec<&'a Scalar>) {
    match node {
        Node::Scalar(scalar) => out.push(scalar),
        Node::Sequence { items, .. } => {
            for item in items {
                collect_strings(item, out);
            }
        }
        Node::Mapping { entries, .. } => {
            for (key, value) in entries {
                out.push(key);
                collect_strings(value, out);
            }
        }
    }
}

pub(crate) fn span_for(path: &str, raw: &str, start: usize, end: usize) -> SourceSpan {
    let start = start.min(raw.len());
    let end = end.max(start + usize::from(start == end)).min(raw.len());
    SourceSpan {
        file: path.to_owned(),
        start: position(raw, start),
        end: position(raw, end),
    }
}

fn position(raw: &str, offset: usize) -> SourcePosition {
    let prefix = &raw[..offset.min(raw.len())];
    let line =
        u32::try_from(prefix.bytes().filter(|byte| *byte == b'\n').count() + 1).unwrap_or(u32::MAX);
    let column = u32::try_from(
        prefix
            .rsplit_once('\n')
            .map_or(prefix, |(_, last)| last)
            .chars()
            .count()
            + 1,
    )
    .unwrap_or(u32::MAX);
    SourcePosition { line, column }
}