Edward Shenfix format1655b565 years agoHistory
1597 lines · 51.7 KB · Rust
RawDownload
//! This module handles parsing a `git-config` file. Generally speaking, you//! want to use a higher abstraction such as [`GitConfig`] unless you have some//! explicit reason to work with events instead.//!//! The general workflow for interacting with this is to use one of the//! `parse_from_*` function variants. These will return a [`Parser`] on success,//! which can be converted into an [`Event`] iterator. The [`Parser`] also has//! additional methods for accessing leading comments or events by section.//!//! [`GitConfig`]: crate::file::GitConfig use nom::branch::alt;use nom::bytes::complete::{escaped, tag, take_till, take_while};use nom::character::complete::{char, none_of, one_of};use nom::character::{is_newline, is_space};use nom::combinator::{map, opt};use nom::error::{Error as NomError, ErrorKind};use nom::multi::{many0, many1};use nom::sequence::delimited;use nom::IResult;use std::convert::TryFrom;use std::fmt::Display;use std::iter::FusedIterator;use std::{borrow::Cow, hash::Hash}; /// Syntactic events that occurs in the config. Despite all these variants/// holding a [`Cow`] instead over a simple reference, the parser will only emit/// borrowed `Cow` variants.////// The `Cow` smart pointer is used here for ease of inserting events in a/// middle of an Event iterator. This is used, for example, in the [`GitConfig`]/// struct when adding values.////// [`Cow`]: std::borrow::Cow/// [`GitConfig`]: crate::file::GitConfig#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]pub enum Event<'a> {    /// A comment with a comment tag and the comment itself. Note that the    /// comment itself may contain additional whitespace and comment markers    /// at the beginning.    Comment(ParsedComment<'a>),    /// A section header containing the section name and a subsection, if it    /// exists.    SectionHeader(ParsedSectionHeader<'a>),    /// A name to a value in a section.    Key(Key<'a>),    /// A completed value. This may be any string, including the empty string,    /// if an implicit boolean value is used. Note that these values may contain    /// spaces and any special character. This value is also unprocessed, so it    /// it may contain double quotes that should be replaced.    Value(Cow<'a, [u8]>),    /// Represents any token used to signify a new line character. On Unix    /// platforms, this is typically just `\n`, but can be any valid newline    /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single    /// newline event.    Newline(Cow<'a, str>),    /// Any value that isn't completed. This occurs when the value is continued    /// onto the next line. A Newline event is guaranteed after, followed by    /// either a ValueDone, a Whitespace, or another ValueNotDone.    ValueNotDone(Cow<'a, [u8]>),    /// The last line of a value which was continued onto another line.    ValueDone(Cow<'a, [u8]>),    /// A continuous section of insignificant whitespace. Values with internal    /// spaces will not be separated by this event.    Whitespace(Cow<'a, str>),    /// This event is emitted when the parser counters a valid `=` character    /// separating the key and value. This event is necessary as it eliminates    /// the ambiguity for whitespace events between a key and value event.    KeyValueSeparator,} impl Event<'_> {    /// Generates a byte representation of the value. This should be used when    /// non-UTF-8 sequences are present or a UTF-8 representation can't be    /// guaranteed.    #[must_use]    pub fn to_vec(&self) -> Vec<u8> {        self.into()    }} impl Display for Event<'_> {    /// Note that this is a best-effort attempt at printing an `Event`. If    /// there are non UTF-8 values in your config, this will _NOT_ render    /// as read.    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => match std::str::from_utf8(e) {                Ok(e) => e.fmt(f),                Err(_) => write!(f, "{:02x?}", e),            },            Self::Comment(e) => e.fmt(f),            Self::SectionHeader(e) => e.fmt(f),            Self::Key(e) => e.fmt(f),            Self::Newline(e) | Self::Whitespace(e) => e.fmt(f),            Self::KeyValueSeparator => write!(f, "="),        }    }} impl Into<Vec<u8>> for Event<'_> {    fn into(self) -> Vec<u8> {        (&self).into()    }} impl Into<Vec<u8>> for &Event<'_> {    fn into(self) -> Vec<u8> {        match self {            Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(),            Event::Comment(e) => e.into(),            Event::SectionHeader(e) => e.into(),            Event::Key(e) => e.0.as_bytes().to_vec(),            Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(),            Event::KeyValueSeparator => vec![b'='],        }    }} /// A parsed section containing the header and the section events.#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]pub struct ParsedSection<'a> {    /// The section name and subsection name, if any.    pub section_header: ParsedSectionHeader<'a>,    /// The syntactic events found in this section.    pub events: Vec<Event<'a>>,} impl Display for ParsedSection<'_> {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        write!(f, "{}", self.section_header)?;        for event in &self.events {            event.fmt(f)?;        }        Ok(())    }} /// A parsed section header, containing a name and optionally a subsection name.////// Note that section headers must be parsed as valid ASCII, and thus all valid/// instances must also necessarily be valid UTF-8, which is why we use a/// [`str`] instead of [`[u8]`].#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]pub struct ParsedSectionHeader<'a> {    /// The name of the header.    pub name: SectionHeaderName<'a>,    /// The separator used to determine if the section contains a subsection.    /// This is either a period `.` or a string of whitespace. Note that    /// reconstruction of subsection format is dependent on this value. If this    /// is all whitespace, then the subsection name needs to be surrounded by    /// quotes to have perfect reconstruction.    pub separator: Option<Cow<'a, str>>,    /// The subsection name without quotes if any exist.    pub subsection_name: Option<Cow<'a, str>>,} macro_rules! generate_case_insensitive {    ($name:ident, $inner_type:ty, $comment:literal) => {        #[doc = $comment]        #[derive(Clone, Eq, Ord, Debug, Default)]        pub struct $name<'a>(pub $inner_type);         impl PartialEq for $name<'_> {            fn eq(&self, other: &Self) -> bool {                self.0.eq_ignore_ascii_case(&other.0)            }        }         impl Display for $name<'_> {            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {                self.0.fmt(f)            }        }         impl PartialOrd for $name<'_> {            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {                self.0                    .to_ascii_lowercase()                    .partial_cmp(&other.0.to_ascii_lowercase())            }        }         impl std::hash::Hash for $name<'_> {            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {                self.0.to_ascii_lowercase().hash(state)            }        }         impl<'a> From<&'a str> for $name<'a> {            fn from(s: &'a str) -> Self {                Self(Cow::Borrowed(s))            }        }         impl<'a> From<Cow<'a, str>> for $name<'a> {            fn from(s: Cow<'a, str>) -> Self {                Self(s)            }        }         impl<'a> std::ops::Deref for $name<'a> {            type Target = $inner_type;             fn deref(&self) -> &Self::Target {                &self.0            }        }         impl<'a> std::ops::DerefMut for $name<'a> {            fn deref_mut(&mut self) -> &mut Self::Target {                &mut self.0            }        }    };} generate_case_insensitive!(    SectionHeaderName,    Cow<'a, str>,    "Wrapper struct for section header names, since section headers are case-insensitive."); generate_case_insensitive!(    Key,    Cow<'a, str>,    "Wrapper struct for key names, since keys are case-insensitive."); impl ParsedSectionHeader<'_> {    /// Generates a byte representation of the value. This should be used when    /// non-UTF-8 sequences are present or a UTF-8 representation can't be    /// guaranteed.    #[must_use]    pub fn to_vec(&self) -> Vec<u8> {        self.into()    }} impl Display for ParsedSectionHeader<'_> {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        write!(f, "[{}", self.name)?;         if let Some(v) = &self.separator {            // Separator must be utf-8            v.fmt(f)?;            let subsection_name = self.subsection_name.as_ref().unwrap();            if v == "." {                subsection_name.fmt(f)?;            } else {                write!(f, "\"{}\"", subsection_name)?;            }        }         write!(f, "]")    }} impl Into<Vec<u8>> for ParsedSectionHeader<'_> {    fn into(self) -> Vec<u8> {        (&self).into()    }} impl Into<Vec<u8>> for &ParsedSectionHeader<'_> {    fn into(self) -> Vec<u8> {        self.to_string().into_bytes()    }} impl<'a> Into<Event<'a>> for ParsedSectionHeader<'a> {    fn into(self) -> Event<'a> {        Event::SectionHeader(self)    }} /// A parsed comment event containing the comment marker and comment.#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]pub struct ParsedComment<'a> {    /// The comment marker used. This is either a semicolon or octothorpe.    pub comment_tag: char,    /// The parsed comment.    pub comment: Cow<'a, [u8]>,} impl Display for ParsedComment<'_> {    /// Note that this is a best-effort attempt at printing an comment. If    /// there are non UTF-8 values in your config, this will _NOT_ render    /// as read.    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        self.comment_tag.fmt(f)?;        if let Ok(s) = std::str::from_utf8(&self.comment) {            s.fmt(f)        } else {            write!(f, "{:02x?}", self.comment)        }    }} impl Into<Vec<u8>> for ParsedComment<'_> {    fn into(self) -> Vec<u8> {        (&self).into()    }} impl Into<Vec<u8>> for &ParsedComment<'_> {    fn into(self) -> Vec<u8> {        let mut values = vec![self.comment_tag as u8];        values.extend(self.comment.iter());        values    }} /// A parser error reports the one-indexed line number where the parsing error/// occurred, as well as the last parser node and the remaining data to be/// parsed.#[derive(PartialEq, Debug)]pub struct Error<'a> {    line_number: usize,    last_attempted_parser: ParserNode,    parsed_until: &'a [u8],} impl Error<'_> {    /// The one-indexed line number where the error occurred. This is determined    /// by the number of newlines that were successfully parsed.    #[must_use]    pub const fn line_number(&self) -> usize {        self.line_number + 1    }     /// The remaining data that was left unparsed.    #[must_use]    pub const fn remaining_data(&self) -> &[u8] {        self.parsed_until    }} impl Display for Error<'_> {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        let data_size = self.parsed_until.len();        let data = std::str::from_utf8(self.parsed_until);        write!(            f,            "Got an unexpected token on line {} while trying to parse a {}: ",            self.line_number + 1,            self.last_attempted_parser,        )?;         match (data, data_size) {            (Ok(data), _) if data_size > 10 => {                write!(f, "'{}' ... ({} characters omitted)", &data[..10], data_size - 10)            }            (Ok(data), _) => write!(f, "'{}'", data),            (Err(_), _) if data_size > 10 => write!(                f,                "'{:02x?}' ... ({} characters omitted)",                &self.parsed_until[..10],                data_size - 10            ),            (Err(_), _) => write!(f, "'{:02x?}'", self.parsed_until),        }    }} impl std::error::Error for Error<'_> {} /// A list of parsers that parsing can fail on. This is used for pretty-printing/// errors#[derive(PartialEq, Debug, Clone, Copy)]enum ParserNode {    SectionHeader,    ConfigName,} impl Display for ParserNode {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            Self::SectionHeader => write!(f, "section header"),            Self::ConfigName => write!(f, "config name"),        }    }} /// A zero-copy `git-config` file parser.////// This is parser exposes low-level syntactic events from a `git-config` file./// Generally speaking, you'll want to use [`GitConfig`] as it wraps/// around the parser to provide a higher-level abstraction to a `git-config`/// file, including querying, modifying, and updating values.////// This parser guarantees that the events emitted are sufficient to/// reconstruct a `git-config` file identical to the source `git-config`.////// # Differences between a `.ini` parser////// While the `git-config` format closely resembles the [`.ini` file format],/// there are subtle differences that make them incompatible. For one, the file/// format is not well defined, and there exists no formal specification to/// adhere to. Thus, attempting to use an `.ini` parser on a `git-config` file/// may successfully parse invalid configuration files.////// For concrete examples, some notable differences are:/// - `git-config` sections permit subsections via either a quoted string/// (`[some-section "subsection"]`) or via the deprecated dot notation/// (`[some-section.subsection]`). Successful parsing these section names is not/// well defined in typical `.ini` parsers. This parser will handle these cases/// perfectly./// - Comment markers are not strictly defined either. This parser will always/// and only handle a semicolon or octothorpe (also known as a hash or number/// sign)./// - Global properties may be allowed in `.ini` parsers, but is strictly/// disallowed by this parser./// - Only `\t`, `\n`, `\b` `\\` are valid escape characters./// - Quoted and semi-quoted values will be parsed (but quotes will be included/// in event outputs). An example of a semi-quoted value is `5"hello world"`,/// which should be interpreted as `5hello world`./// - Line continuations via a `\` character is supported./// - Whitespace handling similarly follows the `git-config` specification as/// closely as possible, where excess whitespace after a non-quoted value are/// trimmed, and line continuations onto a new line with excess spaces are kept./// - Only equal signs (optionally padded by spaces) are valid name/value/// delimiters.////// Note that that things such as case-sensitivity or duplicate sections are/// _not_ handled. This parser is a low level _syntactic_ interpreter (as a/// parser should be), and higher level wrappers around this parser (which may/// or may not be zero-copy) should handle _semantic_ values. This also means/// that string-like values are not interpreted. For example, `hello"world"`/// would be read at a high level as `helloworld` but this parser will return/// the former instead, with the extra quotes. This is because it is not the/// responsibility of the parser to interpret these values, and doing so would/// necessarily require a copy, which this parser avoids.////// # Trait Implementations////// - This struct does _not_ implement [`FromStr`] due to lifetime/// constraints implied on the required `from_str` method. Instead, it provides/// [`From<&'_ str>`].////// # Idioms////// If you do want to use this parser, there are some idioms that may help you/// with interpreting sequences of events.////// ## `Value` events do not immediately follow `Key` events////// Consider the following `git-config` example:////// ```text/// [core]///   autocrlf = input/// ```////// Because this parser guarantees perfect reconstruction, there are many/// non-significant events that occur in addition to the ones you may expect:////// ```/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key};/// # use std::borrow::Cow;/// # let section_header = ParsedSectionHeader {/// #   name: SectionHeaderName(Cow::Borrowed("core")),/// #   separator: None,/// #   subsection_name: None,/// # };/// # let section_data = "[core]\n  autocrlf = input";/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![/// Event::SectionHeader(section_header),/// Event::Newline(Cow::Borrowed("\n")),/// Event::Whitespace(Cow::Borrowed("  ")),/// Event::Key(Key(Cow::Borrowed("autocrlf"))),/// Event::Whitespace(Cow::Borrowed(" ")),/// Event::KeyValueSeparator,/// Event::Whitespace(Cow::Borrowed(" ")),/// Event::Value(Cow::Borrowed(b"input")),/// # ]);/// ```////// Note the two whitespace events between the key and value pair! Those two/// events actually refer to the whitespace between the name and value and the/// equal sign. So if the config instead had `autocrlf=input`, those whitespace/// events would no longer be present.////// ## `KeyValueSeparator` event is not guaranteed to emit////// Consider the following `git-config` example:////// ```text/// [core]///   autocrlf/// ```////// This is a valid config with a `autocrlf` key having an implicit `true`/// value. This means that there is not a `=` separating the key and value,/// which means that the corresponding event won't appear either:////// ```/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key};/// # use std::borrow::Cow;/// # let section_header = ParsedSectionHeader {

Lines 1–500 of 1597.