151 lines · 4.9 KB · Rust
RawDownload
use std::borrow::Cow; use bstr::{BStr, ByteSlice};use gix_error::{ErrorExt, ResultExt, ValidationError}; use crate::{AssignmentRef, Name, NameRef, StateRef}; /// The kind of attribute that was parsed.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub enum Kind {    /// A pattern to match paths against    Pattern(gix_glob::Pattern),    /// The name of the macro to define, always a valid attribute name    Macro(Name),} /// An iterator over attribute assignments, parsed line by line.pub struct Lines<'a> {    lines: bstr::Lines<'a>,    line_no: usize,} /// An iterator over attribute assignments in a single line.pub struct Iter<'a> {    attrs: std::slice::Split<'a, u8, fn(&u8) -> bool>,} impl<'a> Iter<'a> {    /// Create a new instance to parse attribute assignments from `input`.    pub fn new(input: &'a BStr) -> Self {        Iter {            attrs: input.split(is_blank as fn(&u8) -> bool),        }    }     fn parse_attr(&self, attr: &'a [u8]) -> Result<AssignmentRef<'a>, gix_error::ValidationError> {        let mut tokens = attr.splitn(2, |b| *b == b'=');        let attr = tokens.next().expect("attr itself").as_bstr();        let possibly_value = tokens.next();        let (attr, state) = if attr.first() == Some(&b'-') {            (&attr[1..], StateRef::Unset)        } else if attr.first() == Some(&b'!') {            (&attr[1..], StateRef::Unspecified)        } else {            (attr, possibly_value.map_or(StateRef::Set, StateRef::from_bytes))        };        Ok(AssignmentRef::new(check_attr(attr)?, state))    }} fn check_attr(attr: &BStr) -> Result<NameRef<'_>, gix_error::ValidationError> {    NameRef::try_from(attr).and_then(|name| {        (!name.as_str().starts_with("builtin_")).then_some(name).ok_or_else(|| {            gix_error::ValidationError::new_with_input("Attribute name uses the reserved 'builtin_' prefix", attr)        })    })} impl<'a> Iterator for Iter<'a> {    type Item = Result<AssignmentRef<'a>, gix_error::ValidationError>;     fn next(&mut self) -> Option<Self::Item> {        let attr = self.attrs.find(|a| !a.is_empty())?;        self.parse_attr(attr).into()    }} /// Instantiationimpl<'a> Lines<'a> {    /// Create a new instance to parse all attributes in all lines of the input `bytes`.    pub fn new(bytes: &'a [u8]) -> Self {        let bom = unicode_bom::Bom::from(bytes);        Lines {            lines: bytes[bom.len()..].lines(),            line_no: 0,        }    }} impl<'a> Iterator for Lines<'a> {    type Item = Result<(Kind, Iter<'a>, usize), gix_error::Exn<gix_error::ValidationError>>;     fn next(&mut self) -> Option<Self::Item> {        fn skip_blanks(line: &BStr) -> &BStr {            line.find_not_byteset(BLANKS).map_or(line, |pos| &line[pos..])        }        for line in self.lines.by_ref() {            self.line_no += 1;            let line = skip_blanks(line.into());            if line.first() == Some(&b'#') {                continue;            }            match parse_line(line, self.line_no) {                None => continue,                Some(res) => return Some(res),            }        }        None    }} fn parse_line(    line: &BStr,    line_number: usize,) -> Option<Result<(Kind, Iter<'_>, usize), gix_error::Exn<gix_error::ValidationError>>> {    if line.is_empty() {        return None;    }     let unquoted = line        .starts_with(b"\"")        .then(|| gix_quote::ansi_c::undo(line).ok())        .flatten();    let (line, attrs): (Cow<'_, _>, _) = match unquoted {        Some((unquoted, consumed)) => (unquoted, &line[consumed..]),        None => line            .find_byteset(BLANKS)            .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr()))            .unwrap_or((line.into(), [].as_bstr())),    };     let kind_res = match line.strip_prefix(b"[attr]").filter(|name| !name.is_empty()) {        Some(macro_name) => check_attr(macro_name.into())            .or_raise(|| ValidationError::new(format!("Macro in line {line_number} has an invalid name")))            .map(|name| Kind::Macro(name.to_owned())),        None => {            let pattern = gix_glob::Pattern::from_bytes(line.as_ref())?;            if pattern.mode.contains(gix_glob::pattern::Mode::NEGATIVE) {                Err(ValidationError::new_with_input(                    format!(r"Line {line_number} has a negative pattern, for literal characters use \!"),                    line.as_ref(),                )                .raise())            } else {                Ok(Kind::Pattern(pattern))            }        }    };    let kind = match kind_res {        Ok(kind) => kind,        Err(err) => return Some(Err(err)),    };    Ok((kind, Iter::new(attrs), line_number)).into()} fn is_blank(b: &u8) -> bool {    BLANKS.contains(b)} const BLANKS: &[u8] = b" \t\r";