haematite-cli 0.21.10

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Braces outside jq quotes select fields or ordered first-stage collection roots.

use haematite_query::stage::shape::{Field, MAX_DEPTH, Selection};

use super::{Input, Refused, Root, Source, Span, Spanned, Stage, first, source, spans};

/// Read one later projection, leaving the following pipeline separator unread.
pub(super) fn stage(text: &str, start: usize) -> Result<Spanned<Stage>, Refused> {
    let mut reader = Reader {
        text,
        rest: text,
        start,
    };
    reader.trim();
    let beginning = reader.position();
    let selection = reader.selection(0)?;
    Ok(Spanned {
        value: Stage::Shape(selection),
        span: Span {
            start: beginning,
            end: reader.position(),
        },
    })
}

/// Keep one root's existing lowering; several roots each own their projection.
pub(super) fn source(
    text: &str,
    start: usize,
) -> Result<(Source, Span, Option<Selection>), Refused> {
    let mut reader = Reader {
        text,
        rest: text,
        start,
    };
    reader.trim();
    let beginning = reader.position();
    let mut roots = reader.roots()?;
    let span = Span {
        start: beginning,
        end: reader.position(),
    };
    if roots.len() > 1 {
        return Ok((Source::Shaped(roots), span, None));
    }
    let root = roots
        .pop()
        .ok_or_else(|| reader.refused("a shape names a collection root"))?;
    Ok((
        Source::Collection {
            name: root.name,
            arguments: root.arguments,
        },
        span,
        Some(root.selection),
    ))
}

struct Reader<'a> {
    text: &'a str,
    rest: &'a str,
    start: usize,
}

impl Reader<'_> {
    const fn position(&self) -> usize {
        self.start + self.text.len() - self.rest.len()
    }

    fn trim(&mut self) {
        self.rest = self.rest.trim_start();
    }

    fn refused(&self, message: impl Into<String>) -> Refused {
        Refused::Parse(message.into(), spans::stage(self.text, self.start))
    }

    fn require(&mut self, token: char, message: &str) -> Result<(), Refused> {
        self.trim();
        let rest = self
            .rest
            .strip_prefix(token)
            .ok_or_else(|| self.refused(message))?;
        self.rest = rest;
        Ok(())
    }

    fn roots(&mut self) -> Result<Vec<Root>, Refused> {
        self.require('{', "a shape starts with `{`")?;
        let mut roots: Vec<Root> = Vec::new();
        loop {
            self.trim();
            if let Some(rest) = self.rest.strip_prefix('}') {
                self.rest = rest;
                if roots.is_empty() {
                    return Err(self.refused("a shape names at least one collection root"));
                }
                return Ok(roots);
            }
            let start = self.position();
            let mut input = Input {
                text: self.text,
                rest: self.rest,
            };
            let parsed = source::collection(&mut input).map_err(|error| self.located(error))?;
            let Source::Collection { name, arguments } = parsed else {
                return Err(self.refused("a collection root has a nested selection set"));
            };
            self.rest = input.rest;
            let selection = self.selection(1)?;
            let span = Span {
                start,
                end: self.position(),
            };
            if roots.iter().any(|root| root.name == name) {
                return Err(Refused::Parse(
                    format!("a shape repeats the root {name:?}"),
                    span,
                ));
            }
            roots.push(Root {
                name,
                arguments,
                selection,
                span,
            });
        }
    }

    fn located(&self, error: Refused) -> Refused {
        let span = error.span();
        let span = Span {
            start: self.start + span.start,
            end: self.start + span.end,
        };
        match error {
            Refused::Parse(message, _) => Refused::Parse(message, span),
            Refused::WholeRecords(message, _) => Refused::WholeRecords(message, span),
            Refused::NotYet(message, _) => Refused::NotYet(message, span),
            Refused::Unserved(message, _) => Refused::Unserved(message, span),
            Refused::Percentile(message, _) => Refused::Percentile(message, span),
            Refused::Flag(message, _) => Refused::Flag(message, span),
            Refused::Window(code, message, _) => Refused::Window(code, message, span),
        }
    }

    fn selection(&mut self, depth: usize) -> Result<Selection, Refused> {
        let fields = self.fields(depth)?;
        Selection::new(fields).map_err(|error| self.refused(error.refusal.message))
    }

    fn fields(&mut self, depth: usize) -> Result<Vec<Field>, Refused> {
        if depth >= MAX_DEPTH {
            return Err(self.refused("a shape exceeds 64 selection levels"));
        }
        self.require('{', "a shape starts with `{`")?;
        let mut fields = Vec::new();
        loop {
            self.trim();
            if let Some(rest) = self.rest.strip_prefix('}') {
                self.rest = rest;
                if fields.is_empty() {
                    return Err(self.refused("a shape selects at least one field"));
                }
                return Ok(fields);
            }
            if self.rest.is_empty() {
                return Err(self.refused("a shape is not closed with `}`"));
            }
            let length = self
                .rest
                .chars()
                .take_while(|character| first(*character))
                .map(char::len_utf8)
                .sum();
            let (name, rest) = self.rest.split_at(length);
            if name.is_empty() {
                return Err(self.refused("a shape field has a bare name"));
            }
            let name = name.to_owned();
            self.rest = rest;
            self.trim();
            let selection = if self.rest.starts_with('{') {
                Some(self.selection(depth + 1)?)
            } else {
                None
            };
            fields.push(Field { name, selection });
        }
    }
}