Compare commits
No commits in common. "eaa0a1766b146649accfa8beb509ae0d166263c7" and "3bd46913d32033d5f4bf80765652728dc2287530" have entirely different histories.
eaa0a1766b
...
3bd46913d3
3 changed files with 37 additions and 182 deletions
207
src/config.rs
207
src/config.rs
|
@ -2,6 +2,15 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::parser::{parse_from_str, Event, Parser, ParserError};
|
use crate::parser::{parse_from_str, Event, Parser, ParserError};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
|
||||||
|
struct SectionId(usize);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum LookupTreeNode<'a> {
|
||||||
|
Terminal(Vec<SectionId>),
|
||||||
|
NonTerminal(HashMap<&'a str, Vec<SectionId>>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum GitConfigError<'a> {
|
pub enum GitConfigError<'a> {
|
||||||
/// The requested section does not exist.
|
/// The requested section does not exist.
|
||||||
|
@ -12,42 +21,15 @@ pub enum GitConfigError<'a> {
|
||||||
KeyDoesNotExist(&'a str),
|
KeyDoesNotExist(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use linked list for section ordering?
|
|
||||||
|
|
||||||
/// High level `git-config` reader and writer.
|
/// High level `git-config` reader and writer.
|
||||||
pub struct GitConfig<'a> {
|
pub struct GitConfig<'a> {
|
||||||
/// The list of events that occur before an actual section. Since a
|
|
||||||
/// `git-config` file prohibits global values, this vec is limited to only
|
|
||||||
/// comment, newline, and whitespace events.
|
|
||||||
front_matter_events: Vec<Event<'a>>,
|
front_matter_events: Vec<Event<'a>>,
|
||||||
section_lookup_tree: HashMap<&'a str, Vec<LookupTreeNode<'a>>>,
|
section_lookup_tree: HashMap<&'a str, Vec<LookupTreeNode<'a>>>,
|
||||||
/// SectionId to section mapping. The value of this HashMap contains actual
|
|
||||||
/// events
|
|
||||||
sections: HashMap<SectionId, Vec<Event<'a>>>,
|
sections: HashMap<SectionId, Vec<Event<'a>>>,
|
||||||
section_header_separators: HashMap<SectionId, Option<&'a str>>,
|
section_header_separators: HashMap<SectionId, Option<&'a str>>,
|
||||||
section_id_counter: usize,
|
section_id_counter: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The section ID is a monotonically increasing ID used to refer to sections,
|
|
||||||
/// and implies ordering between sections in the original `git-config` file,
|
|
||||||
/// such that a section with id 0 always is before a section with id 1.
|
|
||||||
///
|
|
||||||
/// We need to use a section id because `git-config` permits sections with
|
|
||||||
/// identical names. As a result, we can't simply use the section name as a key
|
|
||||||
/// in a map.
|
|
||||||
///
|
|
||||||
/// This id guaranteed to be unique, but not guaranteed to be compact. In other
|
|
||||||
/// words, it's possible that a section may have an ID of 3 but the next section
|
|
||||||
/// has an ID of 5.
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
|
|
||||||
struct SectionId(usize);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
enum LookupTreeNode<'a> {
|
|
||||||
Terminal(Vec<SectionId>),
|
|
||||||
NonTerminal(HashMap<&'a str, Vec<SectionId>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GitConfig<'a> {
|
impl<'a> GitConfig<'a> {
|
||||||
/// Convenience constructor. Attempts to parse the provided string into a
|
/// Convenience constructor. Attempts to parse the provided string into a
|
||||||
/// [`GitConfig`].
|
/// [`GitConfig`].
|
||||||
|
@ -227,73 +209,6 @@ impl<'a> GitConfig<'a> {
|
||||||
latest_value.ok_or(GitConfigError::KeyDoesNotExist(key))
|
latest_value.ok_or(GitConfigError::KeyDoesNotExist(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to an uninterpreted value given a section,
|
|
||||||
/// an optional subsection and key.
|
|
||||||
///
|
|
||||||
/// Note that `git-config` follows a "last-one-wins" rule for single values.
|
|
||||||
/// If multiple sections contain the same key, then the last section's last
|
|
||||||
/// key's value will be returned.
|
|
||||||
///
|
|
||||||
/// Concretely, if you have the following config:
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// [core]
|
|
||||||
/// a = b
|
|
||||||
/// [core]
|
|
||||||
/// a = c
|
|
||||||
/// a = d
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Then this function will return `d`, since the last valid config value is
|
|
||||||
/// `a = d`, so this entry "wins":
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use serde_git_config::config::{GitConfig, GitConfigError};
|
|
||||||
/// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap();
|
|
||||||
/// let mut mut_value = git_config.get_raw_value_mut("core", None, "a")?;
|
|
||||||
/// assert_eq!(mut_value, &mut "d");
|
|
||||||
/// *mut_value = "hello";
|
|
||||||
/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok("hello"));
|
|
||||||
/// # Ok::<(), GitConfigError>(())
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Consider [`Self::get_raw_multi_value`] if you want to get all values for
|
|
||||||
/// a given key.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if the key is not in the requested
|
|
||||||
/// section and subsection, or if the section and subsection do not exist.
|
|
||||||
pub fn get_raw_value_mut<'b>(
|
|
||||||
&mut self,
|
|
||||||
section_name: &'b str,
|
|
||||||
subsection_name: Option<&'b str>,
|
|
||||||
key: &'b str,
|
|
||||||
) -> Result<&mut &'a str, GitConfigError<'b>> {
|
|
||||||
// Note: cannot wrap around the raw_multi_value method because we need
|
|
||||||
// to guarantee that the highest section id is used (so that we follow
|
|
||||||
// the "last one wins" resolution strategy by `git-config`).
|
|
||||||
let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?;
|
|
||||||
|
|
||||||
// section_id is guaranteed to exist in self.sections, else we have a
|
|
||||||
// violated invariant.
|
|
||||||
let events = self.sections.get_mut(§ion_id).unwrap();
|
|
||||||
let mut found_key = false;
|
|
||||||
let mut latest_value = None;
|
|
||||||
for event in events {
|
|
||||||
match event {
|
|
||||||
Event::Key(event_key) if *event_key == key => found_key = true,
|
|
||||||
Event::Value(v) if found_key => {
|
|
||||||
found_key = false;
|
|
||||||
latest_value = Some(v);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
latest_value.ok_or(GitConfigError::KeyDoesNotExist(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_section_id_by_name_and_subname<'b>(
|
fn get_section_id_by_name_and_subname<'b>(
|
||||||
&'a self,
|
&'a self,
|
||||||
section_name: &'b str,
|
section_name: &'b str,
|
||||||
|
@ -340,96 +255,34 @@ impl<'a> GitConfig<'a> {
|
||||||
subsection_name: Option<&'b str>,
|
subsection_name: Option<&'b str>,
|
||||||
key: &'b str,
|
key: &'b str,
|
||||||
) -> Result<Vec<&'a str>, GitConfigError<'b>> {
|
) -> Result<Vec<&'a str>, GitConfigError<'b>> {
|
||||||
let mut values = vec![];
|
let values = self
|
||||||
for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {
|
|
||||||
let mut found_key = false;
|
|
||||||
// section_id is guaranteed to exist in self.sections, else we
|
|
||||||
// have a violated invariant.
|
|
||||||
for event in self.sections.get(section_id).unwrap() {
|
|
||||||
match event {
|
|
||||||
Event::Key(event_key) if *event_key == key => found_key = true,
|
|
||||||
Event::Value(v) if found_key => {
|
|
||||||
values.push(*v);
|
|
||||||
found_key = false;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if values.is_empty() {
|
|
||||||
Err(GitConfigError::KeyDoesNotExist(key))
|
|
||||||
} else {
|
|
||||||
Ok(values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns mutable references to all uninterpreted values given a section,
|
|
||||||
/// an optional subsection and key. If you have the following config:
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// [core]
|
|
||||||
/// a = b
|
|
||||||
/// [core]
|
|
||||||
/// a = c
|
|
||||||
/// a = d
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Attempting to get all values of `a` yields the following:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use serde_git_config::config::{GitConfig, GitConfigError};
|
|
||||||
/// # let mut git_config = GitConfig::from_str("[core]a=b\n[core]\na=c\na=d").unwrap();
|
|
||||||
/// assert_eq!(git_config.get_raw_multi_value("core", None, "a")?, vec!["b", "c", "d"]);
|
|
||||||
/// for value in git_config.get_raw_multi_value_mut("core", None, "a")? {
|
|
||||||
/// *value = "g";
|
|
||||||
///}
|
|
||||||
/// assert_eq!(git_config.get_raw_multi_value("core", None, "a")?, vec!["g", "g", "g"]);
|
|
||||||
/// # Ok::<(), GitConfigError>(())
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Consider [`Self::get_raw_value`] if you want to get the resolved single
|
|
||||||
/// value for a given key, if your key does not support multi-valued values.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if the key is not in any requested
|
|
||||||
/// section and subsection, or if no instance of the section and subsections
|
|
||||||
/// exist.
|
|
||||||
pub fn get_raw_multi_value_mut<'b>(
|
|
||||||
&mut self,
|
|
||||||
section_name: &'b str,
|
|
||||||
subsection_name: Option<&'b str>,
|
|
||||||
key: &'b str,
|
|
||||||
) -> Result<Vec<&mut &'a str>, GitConfigError<'b>> {
|
|
||||||
let section_ids = self
|
|
||||||
.get_section_ids_by_name_and_subname(section_name, subsection_name)?
|
.get_section_ids_by_name_and_subname(section_name, subsection_name)?
|
||||||
.to_vec();
|
.iter()
|
||||||
let mut found_key = false;
|
.map(|section_id| {
|
||||||
let values: Vec<&mut &'a str> = self
|
let mut found_key = false;
|
||||||
.sections
|
let mut events = vec![];
|
||||||
.iter_mut()
|
// section_id is guaranteed to exist in self.sections, else we have a
|
||||||
.filter_map(|(k, v)| {
|
// violated invariant.
|
||||||
if section_ids.contains(k) {
|
for event in self.sections.get(section_id).unwrap() {
|
||||||
let mut values = vec![];
|
match event {
|
||||||
for event in v {
|
Event::Key(event_key) if *event_key == key => found_key = true,
|
||||||
match event {
|
Event::Value(v) if found_key => {
|
||||||
Event::Key(event_key) if *event_key == key => found_key = true,
|
events.push(*v);
|
||||||
Event::Value(v) if found_key => {
|
found_key = false;
|
||||||
values.push(v);
|
|
||||||
found_key = false;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
Some(values)
|
}
|
||||||
|
|
||||||
|
if events.is_empty() {
|
||||||
|
Err(GitConfigError::KeyDoesNotExist(key))
|
||||||
} else {
|
} else {
|
||||||
None
|
Ok(events)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.filter_map(Result::ok)
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
Err(GitConfigError::KeyDoesNotExist(key))
|
Err(GitConfigError::KeyDoesNotExist(key))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
|
|
||||||
// mod de;
|
// mod de;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! This module handles parsing a `git-config` file. Generally speaking, you
|
//! This module handles parsing a `git-config`. Generally speaking, you want to
|
||||||
//! want to use a higher abstraction such as [`GitConfig`] unless you have some
|
//! use a higher abstraction such as [`GitConfig`] unless you have some explicit
|
||||||
//! explicit reason to work with events instead.
|
//! reason to work with events instead.
|
||||||
//!
|
//!
|
||||||
//! The general workflow for interacting with this is to use one of the
|
//! The general workflow for interacting with this is to use one of the
|
||||||
//! `parse_from_*` function variants. These will return a [`Parser`] on success,
|
//! `parse_from_*` function variants. These will return a [`Parser`] on success,
|
||||||
|
@ -503,6 +503,10 @@ fn config_value<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> {
|
fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> {
|
||||||
|
// I wrote this code and don't know how it works.
|
||||||
|
//
|
||||||
|
// Even after sleeping on it I still don't know how it works.
|
||||||
|
|
||||||
let mut events = vec![];
|
let mut events = vec![];
|
||||||
let mut parsed_index: usize = 0;
|
let mut parsed_index: usize = 0;
|
||||||
let mut offset: usize = 0;
|
let mut offset: usize = 0;
|
||||||
|
|
Reference in a new issue