completely refactor config

This commit is contained in:
Edward Shen 2021-02-20 00:33:17 -05:00
parent 19fc0ebaaf
commit 1c770a9560
Signed by: edward
GPG key ID: 19182661E818369F
4 changed files with 265 additions and 249 deletions

View file

@ -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;
}
(None, _) => {
sections.insert(name, HashMap::default());
}
}
match subsection_name { let mut found_node = false;
Some(v) => current_subsection_name = v, if let Some(subsection_name) = current_subsection_name {
None => { for node in lookup.iter_mut() {
current_subsection_name = EMPTY_MARKER; if let LookupTreeNode::NonTerminal(subsection) = node {
continue; found_node = true;
} subsection
}; .entry(subsection_name)
.or_default()
// subsection parsing .push(new_section_id);
break;
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 => (), if !found_node {
Event::Key(key) => { let mut map = HashMap::new();
current_key = key; map.insert(subsection_name, vec![new_section_id]);
} lookup.push(LookupTreeNode::NonTerminal(map));
Event::Value(v) => {
Self::insert_value(
&mut sections,
current_section_name,
current_subsection_name,
current_key,
v,
options.on_duplicate_name,
)?;
}
Event::Newline(_) => (),
Event::ValueNotDone(v) => value_scratch.push_str(v),
Event::ValueDone(v) => {
let mut completed_value = String::new();
value_scratch.push_str(v);
std::mem::swap(&mut completed_value, &mut value_scratch);
Self::insert_value(
&mut sections,
current_section_name,
current_subsection_name,
current_key,
Value::from_string(completed_value),
options.on_duplicate_name,
)?;
}
Event::Whitespace(_) => (),
}
}
Ok(Self(sections))
}
fn insert_value(
map: &mut HashMap<&'a str, HashMap<&'a str, SectionConfig<'a>>>,
section: &str,
subsection: &str,
key: &'a str,
value: Value<'a>,
on_dup: OnDuplicateBehavior,
) -> Result<(), ()> {
let config = map.get_mut(section).unwrap().get_mut(subsection).unwrap();
if config.contains_key(key) {
match on_dup {
OnDuplicateBehavior::Error => return Err(()),
OnDuplicateBehavior::Overwrite => {
config.insert(key, value);
}
OnDuplicateBehavior::KeepExisting => (),
} }
} else { } else {
config.insert(key, value); 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]))
}
} }
Ok(()) section_id_counter += 1;
} }
pub fn get_section(&self, section_name: &str) -> Option<&SectionConfig<'_>> { // Initialize new section
self.get_subsection(section_name, EMPTY_MARKER) let (name, subname) = (header.name, header.subsection_name);
maybe_section = Some(vec![]);
current_section_name = Some(name);
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.
section_header_separators
.insert(SectionId(section_id_counter), header.separator);
}
e @ Event::Key(_) => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::Value(_) => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::Newline(_) => match maybe_section {
Some(ref mut section) => section.push(e),
None => front_matter_events.push(e),
},
e @ Event::ValueNotDone(_) => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::ValueDone(_) => maybe_section
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::Whitespace(_) => match maybe_section {
Some(ref mut section) => section.push(e),
None => front_matter_events.push(e),
},
}
} }
pub fn get_section_value(&self, section_name: &str, key: &str) -> Option<&Value<'_>> { Self {
self.get_section(section_name) front_matter_events,
.map(|section| section.get(key)) section_lookup_tree,
.flatten() sections,
section_header_separators,
section_id_counter,
}
} }
pub fn get_subsection( pub fn get_raw_single_value<'b>(
&self, &self,
section_name: &str, section_name: &'b str,
subsection_name: &str, subsection_name: Option<&'b str>,
) -> Option<&SectionConfig<'_>> { key: &'b str,
self.0 ) -> Result<&'a str, GitConfigError<'b>> {
.get(section_name) // Note: cannot wrap around the raw_multi_value method because we need
.map(|subsections| subsections.get(subsection_name)) // 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)
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))?;
// section_id is guaranteed to exist in self.sections, else we have a
// violated invariant.
let events = self.sections.get(&section_id).unwrap();
let mut found_key = false;
for event in events {
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))
}
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() .flatten()
} }
pub fn get_subsection_value( pub fn get_raw_multi_value<'b>(
&self, &'a self,
section_name: &str, section_name: &'b str,
subsection_name: &str, subsection_name: Option<&'b str>,
key: &str, key: &'b str,
) -> Option<&Value<'_>> { ) -> Result<Vec<&'a str>, GitConfigError<'b>> {
self.get_subsection(section_name, subsection_name) let values = self
.map(|section| section.get(key)) .get_section_ids_by_name_and_subname(section_name, subsection_name)
.flatten() .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),
_ => (),
} }
} }
impl<'a> TryFrom<Parser<'a>> for GitConfig<'a> { Err(GitConfigError::KeyDoesNotExist(key))
type Error = (); })
.filter_map(Result::ok)
.collect::<Vec<_>>();
fn try_from(parser: Parser<'a>) -> Result<Self, Self::Error> { if values.is_empty() {
Self::try_from_parser_with_options(parser, ConfigOptions::default()) Err(GitConfigError::KeyDoesNotExist(key))
} else {
Ok(values)
} }
} }
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] fn get_section_ids_by_name_and_subname<'b>(
pub struct ConfigOptions { &'a self,
on_duplicate_section: OnDuplicateBehavior, section_name: &'b str,
on_duplicate_name: OnDuplicateBehavior, subsection_name: Option<&'b str>,
) -> Option<Vec<SectionId>> {
let section_ids = self.section_lookup_tree.get(section_name)?;
if let Some(subsect_name) = subsection_name {
let mut maybe_ids = None;
for node in section_ids {
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
maybe_ids = subsection_lookup.get(subsect_name);
break;
} }
impl ConfigOptions {
pub fn on_duplicate_section(&mut self, behavior: OnDuplicateBehavior) -> &mut Self {
self.on_duplicate_section = behavior;
self
} }
maybe_ids.map(|vec| vec.clone())
pub fn on_duplicate_name(&mut self, behavior: OnDuplicateBehavior) -> &mut Self { } else {
self.on_duplicate_name = behavior; let mut maybe_ids = None;
self for node in section_ids {
if let LookupTreeNode::Terminal(subsection_lookup) = node {
maybe_ids = subsection_lookup.iter().max();
break;
}
}
maybe_ids.map(|v| vec![*v])
}
} }
} }
/// [`GitConfig`]'s valid possible actions when encountering a duplicate section pub enum GitConfigError<'a> {
/// or key name within a section. SectionDoesNotExist(&'a str),
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] SubSectionDoesNotExist(Option<&'a str>),
pub enum OnDuplicateBehavior { KeyDoesNotExist(&'a str),
/// 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
}
} }

View file

@ -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",

View file

@ -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 {

View file

@ -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> {