completely refactor config
This commit is contained in:
parent
19fc0ebaaf
commit
1c770a9560
4 changed files with 265 additions and 249 deletions
410
src/config.rs
410
src/config.rs
|
@ -1,223 +1,243 @@
|
||||||
use crate::parser::{parse_from_str, Event, ParsedSectionHeader, Parser};
|
use std::collections::HashMap;
|
||||||
use crate::values::Value;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{collections::HashMap, convert::TryFrom, io::Read};
|
|
||||||
|
|
||||||
type SectionConfig<'a> = HashMap<&'a str, Value<'a>>;
|
use crate::parser::{parse_from_str, Event, Parser, ParserError};
|
||||||
|
|
||||||
/// This struct provides a high level wrapper to access `git-config` file. This
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
|
||||||
/// struct exists primarily for reading a config rather than modifying it, as
|
struct SectionId(usize);
|
||||||
/// it discards comments and unnecessary whitespace.
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Default)]
|
|
||||||
pub struct GitConfig<'a>(HashMap<&'a str, HashMap<&'a str, SectionConfig<'a>>>);
|
|
||||||
|
|
||||||
const EMPTY_MARKER: &str = "@"; // Guaranteed to not be a {sub,}section or name.
|
enum LookupTreeNode<'a> {
|
||||||
|
Terminal(Vec<SectionId>),
|
||||||
|
NonTerminal(HashMap<&'a str, Vec<SectionId>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// High level `git-config` reader and writer.
|
||||||
|
pub struct GitConfig<'a> {
|
||||||
|
front_matter_events: Vec<Event<'a>>,
|
||||||
|
section_lookup_tree: HashMap<&'a str, Vec<LookupTreeNode<'a>>>,
|
||||||
|
sections: HashMap<SectionId, Vec<Event<'a>>>,
|
||||||
|
section_header_separators: HashMap<SectionId, Option<&'a str>>,
|
||||||
|
section_id_counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> GitConfig<'a> {
|
impl<'a> GitConfig<'a> {
|
||||||
/// Attempts to construct a instance given a [`Parser`] instance.
|
/// Convenience constructor. Attempts to parse the provided string into a
|
||||||
///
|
/// [`GitConfig`].
|
||||||
/// This is _not_ a zero-copy operation. Due to how partial values may be
|
pub fn from_str(str: &'a str) -> Result<Self, ParserError> {
|
||||||
/// provided, we necessarily need to copy and store these values until we
|
Ok(Self::from_parser(parse_from_str(str)?))
|
||||||
/// are done.
|
|
||||||
pub fn try_from_parser_with_options(
|
|
||||||
parser: Parser<'a>,
|
|
||||||
options: ConfigOptions,
|
|
||||||
) -> Result<Self, ()> {
|
|
||||||
Self::try_from_event_iter_with_options(parser.into_iter(), options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_event_iter_with_options(
|
pub fn from_parser(parser: Parser<'a>) -> Self {
|
||||||
iter: impl Iterator<Item = Event<'a>>,
|
// Monotonically increasing
|
||||||
options: ConfigOptions,
|
let mut section_id_counter: usize = 0;
|
||||||
) -> Result<Self, ()> {
|
|
||||||
let mut sections: HashMap<&'a str, HashMap<&'a str, SectionConfig<'a>>> = HashMap::new();
|
|
||||||
let mut current_section_name = EMPTY_MARKER;
|
|
||||||
let mut current_subsection_name = EMPTY_MARKER;
|
|
||||||
let mut ignore_until_next_section = false;
|
|
||||||
let mut current_key = EMPTY_MARKER;
|
|
||||||
let mut value_scratch = String::new();
|
|
||||||
|
|
||||||
for event in iter {
|
// Fields for the struct
|
||||||
|
let mut front_matter_events: Vec<Event<'a>> = vec![];
|
||||||
|
let mut sections: HashMap<SectionId, Vec<Event<'a>>> = HashMap::new();
|
||||||
|
let mut section_lookup_tree: HashMap<&str, Vec<LookupTreeNode>> = HashMap::new();
|
||||||
|
let mut section_header_separators = HashMap::new();
|
||||||
|
|
||||||
|
// Current section that we're building
|
||||||
|
let mut current_section_name: Option<&str> = None;
|
||||||
|
let mut current_subsection_name: Option<&str> = None;
|
||||||
|
let mut maybe_section: Option<Vec<Event<'a>>> = None;
|
||||||
|
|
||||||
|
for event in parser.into_iter() {
|
||||||
match event {
|
match event {
|
||||||
Event::Comment(_) => (),
|
e @ Event::Comment(_) => match maybe_section {
|
||||||
Event::SectionHeader(ParsedSectionHeader {
|
Some(ref mut section) => section.push(e),
|
||||||
name,
|
None => front_matter_events.push(e),
|
||||||
separator: _,
|
},
|
||||||
subsection_name,
|
Event::SectionHeader(header) => {
|
||||||
}) => {
|
// Push current section to struct
|
||||||
current_section_name = name;
|
let new_section_id = SectionId(section_id_counter);
|
||||||
match (sections.get_mut(name), options.on_duplicate_section) {
|
if let Some(section) = maybe_section.take() {
|
||||||
(Some(_), OnDuplicateBehavior::Error) => todo!(),
|
sections.insert(new_section_id, section);
|
||||||
(Some(section), OnDuplicateBehavior::Overwrite) => {
|
let lookup = section_lookup_tree
|
||||||
section.clear();
|
.entry(current_section_name.unwrap())
|
||||||
}
|
.or_default();
|
||||||
(Some(_), OnDuplicateBehavior::KeepExisting) => {
|
|
||||||
ignore_until_next_section = true;
|
let mut found_node = false;
|
||||||
}
|
if let Some(subsection_name) = current_subsection_name {
|
||||||
(None, _) => {
|
for node in lookup.iter_mut() {
|
||||||
sections.insert(name, HashMap::default());
|
if let LookupTreeNode::NonTerminal(subsection) = node {
|
||||||
|
found_node = true;
|
||||||
|
subsection
|
||||||
|
.entry(subsection_name)
|
||||||
|
.or_default()
|
||||||
|
.push(new_section_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_node {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(subsection_name, vec![new_section_id]);
|
||||||
|
lookup.push(LookupTreeNode::NonTerminal(map));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for node in lookup.iter_mut() {
|
||||||
|
if let LookupTreeNode::Terminal(vec) = node {
|
||||||
|
found_node = true;
|
||||||
|
vec.push(new_section_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_node {
|
||||||
|
lookup.push(LookupTreeNode::Terminal(vec![new_section_id]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section_id_counter += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
match subsection_name {
|
// Initialize new section
|
||||||
Some(v) => current_subsection_name = v,
|
let (name, subname) = (header.name, header.subsection_name);
|
||||||
None => {
|
maybe_section = Some(vec![]);
|
||||||
current_subsection_name = EMPTY_MARKER;
|
current_section_name = Some(name);
|
||||||
continue;
|
current_subsection_name = subname;
|
||||||
}
|
// We need to store the new, current id counter, so don't
|
||||||
};
|
// use new_section_id here and use the already incremented
|
||||||
|
// section id value.
|
||||||
// subsection parsing
|
section_header_separators
|
||||||
|
.insert(SectionId(section_id_counter), header.separator);
|
||||||
match (
|
|
||||||
sections
|
|
||||||
.get_mut(current_section_name)
|
|
||||||
.unwrap() // Guaranteed to exist at this point
|
|
||||||
.get_mut(current_subsection_name),
|
|
||||||
options.on_duplicate_section,
|
|
||||||
) {
|
|
||||||
(Some(_), OnDuplicateBehavior::Error) => todo!(),
|
|
||||||
(Some(section), OnDuplicateBehavior::Overwrite) => section.clear(),
|
|
||||||
(Some(_), OnDuplicateBehavior::KeepExisting) => {
|
|
||||||
ignore_until_next_section = true;
|
|
||||||
}
|
|
||||||
(None, _) => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ if ignore_until_next_section => (),
|
e @ Event::Key(_) => maybe_section
|
||||||
Event::Key(key) => {
|
.as_mut()
|
||||||
current_key = key;
|
.expect("Got a section-only event before a section")
|
||||||
}
|
.push(e),
|
||||||
Event::Value(v) => {
|
e @ Event::Value(_) => maybe_section
|
||||||
Self::insert_value(
|
.as_mut()
|
||||||
&mut sections,
|
.expect("Got a section-only event before a section")
|
||||||
current_section_name,
|
.push(e),
|
||||||
current_subsection_name,
|
e @ Event::Newline(_) => match maybe_section {
|
||||||
current_key,
|
Some(ref mut section) => section.push(e),
|
||||||
v,
|
None => front_matter_events.push(e),
|
||||||
options.on_duplicate_name,
|
},
|
||||||
)?;
|
e @ Event::ValueNotDone(_) => maybe_section
|
||||||
}
|
.as_mut()
|
||||||
Event::Newline(_) => (),
|
.expect("Got a section-only event before a section")
|
||||||
Event::ValueNotDone(v) => value_scratch.push_str(v),
|
.push(e),
|
||||||
Event::ValueDone(v) => {
|
e @ Event::ValueDone(_) => maybe_section
|
||||||
let mut completed_value = String::new();
|
.as_mut()
|
||||||
value_scratch.push_str(v);
|
.expect("Got a section-only event before a section")
|
||||||
std::mem::swap(&mut completed_value, &mut value_scratch);
|
.push(e),
|
||||||
Self::insert_value(
|
e @ Event::Whitespace(_) => match maybe_section {
|
||||||
&mut sections,
|
Some(ref mut section) => section.push(e),
|
||||||
current_section_name,
|
None => front_matter_events.push(e),
|
||||||
current_subsection_name,
|
},
|
||||||
current_key,
|
|
||||||
Value::from_string(completed_value),
|
|
||||||
options.on_duplicate_name,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Event::Whitespace(_) => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(sections))
|
Self {
|
||||||
|
front_matter_events,
|
||||||
|
section_lookup_tree,
|
||||||
|
sections,
|
||||||
|
section_header_separators,
|
||||||
|
section_id_counter,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_value(
|
pub fn get_raw_single_value<'b>(
|
||||||
map: &mut HashMap<&'a str, HashMap<&'a str, SectionConfig<'a>>>,
|
&self,
|
||||||
section: &str,
|
section_name: &'b str,
|
||||||
subsection: &str,
|
subsection_name: Option<&'b str>,
|
||||||
key: &'a str,
|
key: &'b str,
|
||||||
value: Value<'a>,
|
) -> Result<&'a str, GitConfigError<'b>> {
|
||||||
on_dup: OnDuplicateBehavior,
|
// Note: cannot wrap around the raw_multi_value method because we need
|
||||||
) -> Result<(), ()> {
|
// to guarantee that the highest section id is used (so that we follow
|
||||||
let config = map.get_mut(section).unwrap().get_mut(subsection).unwrap();
|
// the "last one wins" resolution strategy by `git-config`).
|
||||||
|
let section_id = self
|
||||||
|
.get_section_id_by_name_and_subname(section_name, subsection_name)
|
||||||
|
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))?;
|
||||||
|
|
||||||
if config.contains_key(key) {
|
// section_id is guaranteed to exist in self.sections, else we have a
|
||||||
match on_dup {
|
// violated invariant.
|
||||||
OnDuplicateBehavior::Error => return Err(()),
|
let events = self.sections.get(§ion_id).unwrap();
|
||||||
OnDuplicateBehavior::Overwrite => {
|
let mut found_key = false;
|
||||||
config.insert(key, value);
|
for event in events {
|
||||||
}
|
match event {
|
||||||
OnDuplicateBehavior::KeepExisting => (),
|
Event::Key(event_key) if *event_key == key => found_key = true,
|
||||||
|
Event::Value(v) if found_key => return Ok(v),
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(GitConfigError::KeyDoesNotExist(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_section_id_by_name_and_subname<'b>(
|
||||||
|
&'a self,
|
||||||
|
section_name: &'b str,
|
||||||
|
subsection_name: Option<&'b str>,
|
||||||
|
) -> Option<SectionId> {
|
||||||
|
self.get_section_ids_by_name_and_subname(section_name, subsection_name)
|
||||||
|
.map(|vec| vec.into_iter().max())
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_raw_multi_value<'b>(
|
||||||
|
&'a self,
|
||||||
|
section_name: &'b str,
|
||||||
|
subsection_name: Option<&'b str>,
|
||||||
|
key: &'b str,
|
||||||
|
) -> Result<Vec<&'a str>, GitConfigError<'b>> {
|
||||||
|
let values = self
|
||||||
|
.get_section_ids_by_name_and_subname(section_name, subsection_name)
|
||||||
|
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))?
|
||||||
|
.iter()
|
||||||
|
.map(|section_id| {
|
||||||
|
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 => return Ok(*v),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(GitConfigError::KeyDoesNotExist(key))
|
||||||
|
})
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if values.is_empty() {
|
||||||
|
Err(GitConfigError::KeyDoesNotExist(key))
|
||||||
} else {
|
} else {
|
||||||
config.insert(key, value);
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_section(&self, section_name: &str) -> Option<&SectionConfig<'_>> {
|
fn get_section_ids_by_name_and_subname<'b>(
|
||||||
self.get_subsection(section_name, EMPTY_MARKER)
|
&'a self,
|
||||||
}
|
section_name: &'b str,
|
||||||
|
subsection_name: Option<&'b str>,
|
||||||
pub fn get_section_value(&self, section_name: &str, key: &str) -> Option<&Value<'_>> {
|
) -> Option<Vec<SectionId>> {
|
||||||
self.get_section(section_name)
|
let section_ids = self.section_lookup_tree.get(section_name)?;
|
||||||
.map(|section| section.get(key))
|
if let Some(subsect_name) = subsection_name {
|
||||||
.flatten()
|
let mut maybe_ids = None;
|
||||||
}
|
for node in section_ids {
|
||||||
|
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
|
||||||
pub fn get_subsection(
|
maybe_ids = subsection_lookup.get(subsect_name);
|
||||||
&self,
|
break;
|
||||||
section_name: &str,
|
}
|
||||||
subsection_name: &str,
|
}
|
||||||
) -> Option<&SectionConfig<'_>> {
|
maybe_ids.map(|vec| vec.clone())
|
||||||
self.0
|
} else {
|
||||||
.get(section_name)
|
let mut maybe_ids = None;
|
||||||
.map(|subsections| subsections.get(subsection_name))
|
for node in section_ids {
|
||||||
.flatten()
|
if let LookupTreeNode::Terminal(subsection_lookup) = node {
|
||||||
}
|
maybe_ids = subsection_lookup.iter().max();
|
||||||
|
break;
|
||||||
pub fn get_subsection_value(
|
}
|
||||||
&self,
|
}
|
||||||
section_name: &str,
|
maybe_ids.map(|v| vec![*v])
|
||||||
subsection_name: &str,
|
}
|
||||||
key: &str,
|
|
||||||
) -> Option<&Value<'_>> {
|
|
||||||
self.get_subsection(section_name, subsection_name)
|
|
||||||
.map(|section| section.get(key))
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<Parser<'a>> for GitConfig<'a> {
|
pub enum GitConfigError<'a> {
|
||||||
type Error = ();
|
SectionDoesNotExist(&'a str),
|
||||||
|
SubSectionDoesNotExist(Option<&'a str>),
|
||||||
fn try_from(parser: Parser<'a>) -> Result<Self, Self::Error> {
|
KeyDoesNotExist(&'a str),
|
||||||
Self::try_from_parser_with_options(parser, ConfigOptions::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
|
||||||
pub struct ConfigOptions {
|
|
||||||
on_duplicate_section: OnDuplicateBehavior,
|
|
||||||
on_duplicate_name: OnDuplicateBehavior,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigOptions {
|
|
||||||
pub fn on_duplicate_section(&mut self, behavior: OnDuplicateBehavior) -> &mut Self {
|
|
||||||
self.on_duplicate_section = behavior;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_duplicate_name(&mut self, behavior: OnDuplicateBehavior) -> &mut Self {
|
|
||||||
self.on_duplicate_name = behavior;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`GitConfig`]'s valid possible actions when encountering a duplicate section
|
|
||||||
/// or key name within a section.
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
|
||||||
pub enum OnDuplicateBehavior {
|
|
||||||
/// Fail the operation, returning an error instead. This is the strictest
|
|
||||||
/// behavior, and is the default.
|
|
||||||
Error,
|
|
||||||
/// Discard any data we had before on the
|
|
||||||
Overwrite,
|
|
||||||
KeepExisting,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for OnDuplicateBehavior {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
//! which can be converted into an [`Event`] iterator. The [`Parser`] also has
|
//! which can be converted into an [`Event`] iterator. The [`Parser`] also has
|
||||||
//! additional methods for accessing leading comments or events by section.
|
//! additional methods for accessing leading comments or events by section.
|
||||||
|
|
||||||
use crate::values::{Boolean, TrueVariant, Value};
|
|
||||||
use nom::bytes::complete::{escaped, tag, take_till, take_while};
|
use nom::bytes::complete::{escaped, tag, take_till, take_while};
|
||||||
use nom::character::complete::{char, none_of, one_of};
|
use nom::character::complete::{char, none_of, one_of};
|
||||||
use nom::character::{is_newline, is_space};
|
use nom::character::{is_newline, is_space};
|
||||||
|
@ -22,21 +21,32 @@ use std::iter::FusedIterator;
|
||||||
/// Syntactic events that occurs in the config.
|
/// Syntactic events that occurs in the config.
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
pub enum Event<'a> {
|
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>),
|
Comment(ParsedComment<'a>),
|
||||||
|
/// A section header containing the section name and a subsection, if it
|
||||||
|
/// exists.
|
||||||
SectionHeader(ParsedSectionHeader<'a>),
|
SectionHeader(ParsedSectionHeader<'a>),
|
||||||
|
/// A name to a value in a section.
|
||||||
Key(&'a str),
|
Key(&'a str),
|
||||||
///
|
/// A completed value. This may be any string, including the empty string,
|
||||||
Value(Value<'a>),
|
/// 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(&'a str),
|
||||||
/// Represents any token used to signify a new line character. On Unix
|
/// Represents any token used to signify a new line character. On Unix
|
||||||
/// platforms, this is typically just `\n`, but can be any valid newline
|
/// platforms, this is typically just `\n`, but can be any valid newline
|
||||||
/// sequence.
|
/// sequence.
|
||||||
Newline(&'a str),
|
Newline(&'a str),
|
||||||
/// Any value that isn't completed. This occurs when the value is continued
|
/// 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
|
/// onto the next line. A Newline event is guaranteed after, followed by
|
||||||
/// either another ValueNotDone or a ValueDone.
|
/// either a ValueDone, a Whitespace, or another ValueNotDone.
|
||||||
ValueNotDone(&'a str),
|
ValueNotDone(&'a str),
|
||||||
/// The last line of a value which was continued onto another line.
|
/// The last line of a value which was continued onto another line.
|
||||||
ValueDone(&'a str),
|
ValueDone(&'a str),
|
||||||
|
/// A continuous section of insignificant whitespace. Values with internal
|
||||||
|
/// spaces will not be separated by this event.
|
||||||
Whitespace(&'a str),
|
Whitespace(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,12 +363,7 @@ fn config_value<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> {
|
||||||
Ok((i, values))
|
Ok((i, values))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok((
|
Ok((i, vec![Event::Value("")]))
|
||||||
i,
|
|
||||||
vec![Event::Value(Value::Boolean(Boolean::True(
|
|
||||||
TrueVariant::Implicit,
|
|
||||||
)))],
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,7 +454,7 @@ fn value_impl<'a>(i: &'a str) -> IResult<&'a str, Vec<Event<'a>>> {
|
||||||
if partial_value_found {
|
if partial_value_found {
|
||||||
events.push(Event::ValueDone(remainder_value));
|
events.push(Event::ValueDone(remainder_value));
|
||||||
} else {
|
} else {
|
||||||
events.push(Event::Value(Value::from_str(remainder_value)));
|
events.push(Event::Value(remainder_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((i, events))
|
Ok((i, events))
|
||||||
|
@ -643,7 +648,7 @@ mod parse {
|
||||||
fn no_comment() {
|
fn no_comment() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello").unwrap(),
|
value_impl("hello").unwrap(),
|
||||||
fully_consumed(vec![Event::Value(Value::from_str("hello"))])
|
fully_consumed(vec![Event::Value("hello")])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,7 +656,7 @@ mod parse {
|
||||||
fn no_comment_newline() {
|
fn no_comment_newline() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello\na").unwrap(),
|
value_impl("hello\na").unwrap(),
|
||||||
("\na", vec![Event::Value(Value::from_str("hello"))])
|
("\na", vec![Event::Value("hello")])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,7 +664,7 @@ mod parse {
|
||||||
fn semicolon_comment_not_consumed() {
|
fn semicolon_comment_not_consumed() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello;world").unwrap(),
|
value_impl("hello;world").unwrap(),
|
||||||
(";world", vec![Event::Value(Value::from_str("hello")),])
|
(";world", vec![Event::Value("hello"),])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,7 +672,7 @@ mod parse {
|
||||||
fn octothorpe_comment_not_consumed() {
|
fn octothorpe_comment_not_consumed() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello#world").unwrap(),
|
value_impl("hello#world").unwrap(),
|
||||||
("#world", vec![Event::Value(Value::from_str("hello")),])
|
("#world", vec![Event::Value("hello"),])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,10 +680,7 @@ mod parse {
|
||||||
fn values_with_extraneous_whitespace_without_comment() {
|
fn values_with_extraneous_whitespace_without_comment() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello ").unwrap(),
|
value_impl("hello ").unwrap(),
|
||||||
(
|
(" ", vec![Event::Value("hello")])
|
||||||
" ",
|
|
||||||
vec![Event::Value(Value::from_str("hello"))]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -686,17 +688,11 @@ mod parse {
|
||||||
fn values_with_extraneous_whitespace_before_comment() {
|
fn values_with_extraneous_whitespace_before_comment() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello #world").unwrap(),
|
value_impl("hello #world").unwrap(),
|
||||||
(
|
(" #world", vec![Event::Value("hello"),])
|
||||||
" #world",
|
|
||||||
vec![Event::Value(Value::from_str("hello")),]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl("hello ;world").unwrap(),
|
value_impl("hello ;world").unwrap(),
|
||||||
(
|
(" ;world", vec![Event::Value("hello"),])
|
||||||
" ;world",
|
|
||||||
vec![Event::Value(Value::from_str("hello")),]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,10 +700,7 @@ mod parse {
|
||||||
fn trans_escaped_comment_marker_not_consumed() {
|
fn trans_escaped_comment_marker_not_consumed() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl(r##"hello"#"world; a"##).unwrap(),
|
value_impl(r##"hello"#"world; a"##).unwrap(),
|
||||||
(
|
("; a", vec![Event::Value(r##"hello"#"world"##)])
|
||||||
"; a",
|
|
||||||
vec![Event::Value(Value::from_str(r##"hello"#"world"##)),]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,10 +708,7 @@ mod parse {
|
||||||
fn complex_test() {
|
fn complex_test() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value_impl(r#"value";";ahhhh"#).unwrap(),
|
value_impl(r#"value";";ahhhh"#).unwrap(),
|
||||||
(
|
(";ahhhh", vec![Event::Value(r#"value";""#)])
|
||||||
";ahhhh",
|
|
||||||
vec![Event::Value(Value::from_str(r#"value";""#)),]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -807,17 +797,17 @@ mod parse {
|
||||||
Event::Key("a"),
|
Event::Key("a"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Value(Value::from_str("b")),
|
Event::Value("b"),
|
||||||
Event::Newline("\n"),
|
Event::Newline("\n"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Key("c"),
|
Event::Key("c"),
|
||||||
Event::Value(Value::Boolean(Boolean::True(TrueVariant::Implicit))),
|
Event::Value(""),
|
||||||
Event::Newline("\n"),
|
Event::Newline("\n"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Key("d"),
|
Event::Key("d"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Value(Value::from_str("\"lol\""))
|
Event::Value("\"lol\"")
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -829,11 +819,7 @@ mod parse {
|
||||||
section("[hello] c").unwrap(),
|
section("[hello] c").unwrap(),
|
||||||
fully_consumed(ParsedSection {
|
fully_consumed(ParsedSection {
|
||||||
section_header: gen_section_header("hello", None),
|
section_header: gen_section_header("hello", None),
|
||||||
events: vec![
|
events: vec![Event::Whitespace(" "), Event::Key("c"), Event::Value("")]
|
||||||
Event::Whitespace(" "),
|
|
||||||
Event::Key("c"),
|
|
||||||
Event::Value(Value::Boolean(Boolean::True(TrueVariant::Implicit)))
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -860,7 +846,7 @@ mod parse {
|
||||||
Event::Key("a"),
|
Event::Key("a"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Value(Value::from_str("b")),
|
Event::Value("b"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Comment(ParsedComment {
|
Event::Comment(ParsedComment {
|
||||||
comment_tag: '#',
|
comment_tag: '#',
|
||||||
|
@ -883,7 +869,7 @@ mod parse {
|
||||||
Event::Key("c"),
|
Event::Key("c"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Value(Value::from_str("d")),
|
Event::Value("d"),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -947,7 +933,7 @@ mod parse {
|
||||||
events: vec![
|
events: vec![
|
||||||
Event::Key("hello"),
|
Event::Key("hello"),
|
||||||
Event::Whitespace(" "),
|
Event::Whitespace(" "),
|
||||||
Event::Value(Value::Boolean(Boolean::True(TrueVariant::Implicit))),
|
Event::Value(""),
|
||||||
Event::Comment(ParsedComment {
|
Event::Comment(ParsedComment {
|
||||||
comment_tag: '#',
|
comment_tag: '#',
|
||||||
comment: "world",
|
comment: "world",
|
||||||
|
|
|
@ -12,7 +12,18 @@ pub enum Value<'a> {
|
||||||
|
|
||||||
impl<'a> Value<'a> {
|
impl<'a> Value<'a> {
|
||||||
pub fn from_str(s: &'a str) -> Self {
|
pub fn from_str(s: &'a str) -> Self {
|
||||||
// if s.
|
if let Ok(bool) = Boolean::from_str(s) {
|
||||||
|
return Self::Boolean(bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if let Ok(int) = Integer::from_str(s) {
|
||||||
|
// return Self::Integer(int);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Ok(color) = Color::from_str(s) {
|
||||||
|
// return Self::Color(color);
|
||||||
|
// }
|
||||||
|
|
||||||
Self::Other(Cow::Borrowed(s))
|
Self::Other(Cow::Borrowed(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +163,11 @@ pub struct Integer {
|
||||||
suffix: Option<IntegerSuffix>,
|
suffix: Option<IntegerSuffix>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Integer {}
|
impl Integer {
|
||||||
|
pub fn from_str(s: &str) -> Result<Self, ()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Integer {
|
impl Display for Integer {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
use serde_git_config::parser::{parse_from_str, Event, ParsedSectionHeader};
|
use serde_git_config::parser::{parse_from_str, Event, ParsedSectionHeader};
|
||||||
use serde_git_config::values::Value;
|
|
||||||
|
|
||||||
fn fully_consumed<T>(t: T) -> (&'static str, T) {
|
|
||||||
("", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_section_header(
|
fn gen_section_header(
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -30,7 +25,7 @@ fn name(name: &'static str) -> Event<'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value(value: &'static str) -> Event<'static> {
|
fn value(value: &'static str) -> Event<'static> {
|
||||||
Event::Value(Value::from_str(value))
|
Event::Value(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newline() -> Event<'static> {
|
fn newline() -> Event<'static> {
|
||||||
|
|
Reference in a new issue