//! 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. Load more
//! 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 {