History

more work on sections

Edward Shen committed 5 years ago

Commit 84e959d1 parent 41da7ed1 file · +542 −384Browse files

Unified · Split

git-config/src/file.rs+542−384
@@ -4,11 +4,14 @@ use crate::parser::{ · 3 unmodified lines
44 parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName,
55 };
66 use crate::values::{normalize_bytes, normalize_cow, normalize_vec};
7−use std::borrow::{Borrow, Cow};
87 use std::collections::{HashMap, VecDeque};
98 use std::convert::TryFrom;
109 use std::fmt::Display;
1110 use std::ops::{Deref, DerefMut};
11+use std::{
12+ borrow::{Borrow, Cow},
13+ ops::Range,
14+};
1215
1316 /// All possible error types that may occur from interacting with [`GitConfig`].
1417 #[derive(PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Debug)]
@@ -40,20 +43,6 @@ impl Display for GitConfigError<'_> { · 25 unmodified lines
4043
4144 impl std::error::Error for GitConfigError<'_> {}
4245
43−/// The section ID is a monotonically increasing ID used to refer to sections.
44−/// This value does not imply any ordering between sections, as new sections
45−/// with higher section IDs may be in between lower ID sections.
46−///
47−/// We need to use a section id because `git-config` permits sections with
48−/// identical names. As a result, we can't simply use the section name as a key
49−/// in a map.
50−///
51−/// This id guaranteed to be unique, but not guaranteed to be compact. In other
52−/// words, it's possible that a section may have an ID of 3 but the next section
53−/// has an ID of 5.
54−#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
55−struct SectionId(usize);
56−
5746 /// A opaque type that represents a mutable reference to a section.
5847 #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
5948 pub struct MutableSection<'borrow, 'event> {
@@ -113,6 +102,50 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 53 unmodified lines
113102 None
114103 }
115104
105+ /// Sets the last key value pair if it exists, or adds the new value.
106+ /// Returns the previous value if it replaced a value, or None if it adds
107+ /// the value.
108+ pub fn set(&mut self, key: Key<'event>, value: Cow<'event, [u8]>) -> Option<Cow<'event, [u8]>> {
109+ let range = self.get_value_range_by_key(&key);
110+ if range.is_empty() {
111+ self.push(key, value);
112+ return None;
113+ }
114+ let range_start = range.start;
115+ let ret = self.remove_internal(range);
116+ self.section.0.insert(range_start, Event::Value(value));
117+ Some(ret)
118+ }
119+
120+ /// Removes the latest value by key and returns it, if it exists.
121+ pub fn remove(&mut self, key: &Key<'event>) -> Option<Cow<'event, [u8]>> {
122+ let range = self.get_value_range_by_key(key);
123+ if range.is_empty() {
124+ return None;
125+ }
126+ Some(self.remove_internal(range))
127+ }
128+
129+ /// Performs the removal, assuming the range is valid. This is used to
130+ /// avoid duplicating searching for the range in [`Self::set`].
131+ fn remove_internal(&mut self, range: Range<usize>) -> Cow<'event, [u8]> {
132+ self.section
133+ .0
134+ .drain(range)
135+ // TODO: replace with Iterator::reduce once that hits stable
136+ .fold(Cow::<[u8]>::Owned(vec![]), |acc, e| match e {
137+ Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => {
138+ // This is fine because we start out with an owned
139+ // variant, so we never actually clone the
140+ // accumulator.
141+ let mut acc = acc.into_owned();
142+ acc.extend(&*v);
143+ Cow::Owned(acc)
144+ }
145+ _ => acc,
146+ })
147+ }
148+
116149 /// Adds a new line event. Note that you don't need to call this unless
117150 /// you've disabled implicit newlines.
118151 pub fn push_newline(&mut self) {
@@ -134,6 +167,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 15 unmodified lines
134167
135168 /// Returns the number of whitespace this section will insert before the
136169 /// beginning of a key.
170+ #[must_use]
137171 pub const fn whitespace(&self) -> usize {
138172 self.whitespace
139173 }
@@ -221,33 +255,35 @@ impl<'event> OwnedSection<'event> { · 81 unmodified lines
221255 /// Returns None if the key was not found.
222256 #[must_use]
223257 pub fn value(&self, key: &Key) -> Option<Cow<'event, [u8]>> {
224− let mut found_key = false;
225− let mut latest_value = None;
226− let mut partial_value = None;
258+ let range = self.get_value_range_by_key(key);
259+ if range.is_empty() {
260+ return None;
261+ }
227262
228− // todo: iterate backwards instead
229− for event in &self.0 {
230− match event {
231− Event::Key(event_key) if *event_key == *key => found_key = true,
232− Event::Value(v) if found_key => {
233− found_key = false;
234− // Clones the Cow, doesn't copy underlying value if borrowed
235− latest_value = Some(v.clone());
236− partial_value = None;
237− }
238− Event::ValueNotDone(v) if found_key => {
239− latest_value = None;
240− partial_value = Some((*v).to_vec());
241− }
242− Event::ValueDone(v) if found_key => {
243− found_key = false;
244− partial_value.as_mut().unwrap().extend(&**v);
245− }
246− _ => (),
247− }
263+ if range.end - range.start == 1 {
264+ return self.0.get(range.start).map(|e| match e {
265+ Event::Value(v) => v.clone(),
266+ // range only has one element so we know it's a value event, so
267+ // it's impossible to reach this code.
268+ _ => unreachable!(),
269+ });
248270 }
249271
250− latest_value.or_else(|| partial_value.map(normalize_vec))
272+ Some(normalize_cow(self.0[range].iter().fold(
273+ Cow::<[u8]>::Owned(vec![]),
274+ // TODO: replace with Iterator::reduce once that hits stable
275+ |acc, e| match e {
276+ Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => {
277+ // This is fine because we start out with an owned
278+ // variant, so we never actually clone the
279+ // accumulator.
280+ let mut acc = acc.into_owned();
281+ acc.extend(&**v);
282+ Cow::Owned(acc)
283+ }
284+ _ => acc,
285+ },
286+ )))
251287 }
252288
253289 /// Retrieves the last matching value in a section with the given key, and
@@ -273,7 +309,7 @@ impl<'event> OwnedSection<'event> { · 19 unmodified lines
273309 let mut partial_value = None;
274310
275311 // This can iterate forwards because we need to iterate over the whole
276− // section anyways.
312+ // section anyways
277313 for event in &self.0 {
278314 match event {
279315 Event::Key(event_key) if event_key == key => found_key = true,
@@ -314,6 +350,70 @@ impl<'event> OwnedSection<'event> { · 34 unmodified lines
314350 .collect::<Result<Vec<T>, _>>()
315351 .map_err(|_| GitConfigError::FailedConversion)
316352 }
353+
354+ /// Returns an iterator visiting all keys in order.
355+ pub fn keys(&self) -> impl Iterator<Item = &Event<'event>> {
356+ self.0.iter().filter(|e| matches!(e, Event::Key(_)))
357+ }
358+
359+ /// Checks if the section contains the provided key.
360+ #[must_use]
361+ pub fn contains_key(&self, key: &Key) -> bool {
362+ for e in &self.0 {
363+ if let Event::Key(k) = e {
364+ if k == key {
365+ return true;
366+ }
367+ }
368+ }
369+ false
370+ }
371+
372+ /// Returns the number of entries in the section.
373+ #[must_use]
374+ pub fn len(&self) -> usize {
375+ self.0.iter().filter(|e| matches!(e, Event::Key(_))).count()
376+ }
377+
378+ /// Returns if the section is empty.
379+ #[must_use]
380+ pub fn is_empty(&self) -> bool {
381+ self.0.is_empty()
382+ }
383+
384+ /// Returns the the range containing the value events for the section.
385+ /// If the value is not found, then this returns an empty range.
386+ fn get_value_range_by_key(&self, key: &Key<'event>) -> Range<usize> {
387+ let mut values_start = 0;
388+ // value end needs to be offset by one so that the last value's index
389+ // is included in the range
390+ let mut values_end = 0;
391+ for (i, e) in self.0.iter().enumerate().rev() {
392+ match e {
393+ Event::Key(k) => {
394+ if k == key {
395+ break;
396+ }
397+ values_start = 0;
398+ values_end = 0;
399+ }
400+ Event::Value(_) => {
401+ values_end = i + 1;
402+ values_start = i;
403+ }
404+ Event::ValueNotDone(_) | Event::ValueDone(_) => {
405+ if values_end == 0 {
406+ values_end = i + 1;
407+ } else {
408+ values_start = i;
409+ }
410+ }
411+ _ => (),
412+ }
413+ }
414+
415+ values_start..values_end
416+ }
317417 }
318418
319419 impl<'event> From<Vec<Event<'event>>> for OwnedSection<'event> {
@@ -322,71 +422,416 @@ impl<'event> From<Vec<Event<'event>>> for OwnedSection<'event> { · 2 unmodified lines
322422 }
323423 }
324424
425+/// The section ID is a monotonically increasing ID used to refer to sections.
426+/// This value does not imply any ordering between sections, as new sections
427+/// with higher section IDs may be in between lower ID sections.
428+///
429+/// We need to use a section id because `git-config` permits sections with
430+/// identical names. As a result, we can't simply use the section name as a key
431+/// in a map.
432+///
433+/// This id guaranteed to be unique, but not guaranteed to be compact. In other
434+/// words, it's possible that a section may have an ID of 3 but the next section
435+/// has an ID of 5.
436+#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
437+struct SectionId(usize);
438+
439+/// Internal data structure for the section id lookup tree used by
440+/// [`GitConfig`]. Note that order in Vec matters as it represents the order
441+/// of section ids with the matched section and name, and is used for precedence
442+/// management.
325443 #[derive(PartialEq, Eq, Clone, Debug)]
326444 enum LookupTreeNode<'a> {
327445 Terminal(Vec<SectionId>),
328446 NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),
329447 }
330448
331−/// An intermediate representation of a mutable value obtained from
332−/// [`GitConfig`].
449+/// High level `git-config` reader and writer.
333450 ///
334−/// This holds a mutable reference to the underlying data structure of
335−/// [`GitConfig`], and thus guarantees through Rust's borrower checker that
336−/// multiple mutable references to [`GitConfig`] cannot be owned at the same
337−/// time.
338−pub struct MutableValue<'borrow, 'lookup, 'event> {
339− section: MutableSection<'borrow, 'event>,
340− key: Key<'lookup>,
341− index: usize,
342− size: usize,
451+/// Internally, this uses various acceleration data structures to improve
452+/// performance of the typical usage behavior of many lookups and relatively
453+/// fewer insertions.
454+///
455+/// # Multivar behavior
456+///
457+/// `git` is flexible enough to allow users to set a key multiple times in
458+/// any number of identically named sections. When this is the case, the key
459+/// is known as a "multivar". In this case, `get_raw_value` follows the
460+/// "last one wins" approach that `git-config` internally uses for multivar
461+/// resolution.
462+///
463+/// Concretely, the following config has a multivar, `a`, with the values
464+/// of `b`, `c`, and `d`, while `e` is a single variable with the value
465+/// `f g h`.
466+///
467+/// ```text
468+/// [core]
469+/// a = b
470+/// a = c
471+/// [core]
472+/// a = d
473+/// e = f g h
474+/// ```
475+///
476+/// Calling methods that fetch or set only one value (such as [`get_raw_value`])
477+/// key `a` with the above config will fetch `d` or replace `d`, since the last
478+/// valid config key/value pair is `a = d`:
479+///
480+/// ```
481+/// # use git_config::file::GitConfig;
482+/// # use std::borrow::Cow;
483+/// # use std::convert::TryFrom;
484+/// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
485+/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(Cow::Borrowed("d".as_bytes())));
486+/// ```
487+///
488+/// Consider the `multi` variants of the methods instead, if you want to work
489+/// with all values instead.
490+///
491+/// [`get_raw_value`]: Self::get_raw_value
492+#[derive(PartialEq, Eq, Clone, Debug, Default)]
493+pub struct GitConfig<'event> {
494+ /// The list of events that occur before an actual section. Since a
495+ /// `git-config` file prohibits global values, this vec is limited to only
496+ /// comment, newline, and whitespace events.
497+ frontmatter_events: OwnedSection<'event>,
498+ /// Section name and subsection name to section id lookup tree. This is
499+ /// effectively a n-tree (opposed to a binary tree) that can have a height
500+ /// of at most three (including an implicit root node).
501+ section_lookup_tree: HashMap<SectionHeaderName<'event>, Vec<LookupTreeNode<'event>>>,
502+ /// SectionId to section mapping. The value of this HashMap contains actual
503+ /// events.
504+ ///
505+ /// This indirection with the SectionId as the key is critical to flexibly
506+ /// supporting `git-config` sections, as duplicated keys are permitted.
507+ sections: HashMap<SectionId, OwnedSection<'event>>,
508+ section_headers: HashMap<SectionId, ParsedSectionHeader<'event>>,
509+ /// Internal monotonically increasing counter for section ids.
510+ section_id_counter: usize,
511+ /// Section order for output ordering.
512+ section_order: VecDeque<SectionId>,
343513 }
344514
345−impl MutableValue<'_, '_, '_> {
346− /// Returns the actual value. This is computed each time this is called, so
347− /// it's best to reuse this value or own it if an allocation is acceptable.
515+impl<'event> GitConfig<'event> {
516+ /// Constructs an empty `git-config` file.
517+ #[must_use]
518+ pub fn new() -> Self {
519+ Self::default()
520+ }
521+
522+ /// Returns an interpreted value given a section, an optional subsection and
523+ /// key.
524+ ///
525+ /// It's recommended to use one of the values in the [`values`] module as
526+ /// the conversion is already implemented, but this function is flexible and
527+ /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`].
528+ ///
529+ /// Consider [`Self::multi_value`] if you want to get all values of a
530+ /// multivar instead.
531+ ///
532+ /// # Examples
533+ ///
534+ /// ```
535+ /// # use git_config::file::{GitConfig, GitConfigError};
536+ /// # use git_config::values::{Integer, Value, Boolean};
537+ /// # use std::borrow::Cow;
538+ /// # use std::convert::TryFrom;
539+ /// let config = r#"
540+ /// [core]
541+ /// a = 10k
542+ /// c
543+ /// "#;
544+ /// let git_config = GitConfig::try_from(config)?;
545+ /// // You can either use the turbofish to determine the type...
546+ /// let a_value = git_config.value::<Integer>("core", None, "a")?;
547+ /// // ... or explicitly declare the type to avoid the turbofish
548+ /// let c_value: Boolean = git_config.value("core", None, "c")?;
549+ /// # Ok::<(), Box<dyn std::error::Error>>(())
550+ /// ```
348551 ///
349552 /// # Errors
350553 ///
351− /// Returns an error if the lookup failed.
352− #[inline]
353− pub fn get(&self) -> Result<Cow<'_, [u8]>, GitConfigError> {
354− self.section
355− .get(&self.key, self.index, self.index + self.size)
356− }
357−
358− /// Update the value to the provided one. This modifies the value such that
359− /// the Value event(s) are replaced with a single new event containing the
360− /// new value.
361− #[inline]
362− pub fn set_string(&mut self, input: String) {
363− self.set_bytes(input.into_bytes());
364− }
365−
366− /// Update the value to the provided one. This modifies the value such that
367− /// the Value event(s) are replaced with a single new event containing the
368− /// new value.
369− pub fn set_bytes(&mut self, input: Vec<u8>) {
370− if self.size > 0 {
371− self.section.delete(self.index, self.index + self.size);
372− }
373− self.size = 3;
374− self.section
375− .set_value(self.index, Key(Cow::Owned(self.key.to_string())), input);
376− }
377−
378− /// Removes the value. Does nothing when called multiple times in
379− /// succession.
380− pub fn delete(&mut self) {
381− if self.size > 0 {
382− self.section.delete(self.index, self.index + self.size);
383− self.size = 0;
384− }
554+ /// This function will return an error if the key is not in the requested
555+ /// section and subsection, if the section and subsection do not exist, or
556+ /// if there was an issue converting the type into the requested variant.
557+ ///
558+ /// [`values`]: crate::values
559+ /// [`TryFrom`]: std::convert::TryFrom
560+ pub fn value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
561+ &'event self,
562+ section_name: &'lookup str,
563+ subsection_name: Option<&'lookup str>,
564+ key: &'lookup str,
565+ ) -> Result<T, GitConfigError<'lookup>> {
566+ T::try_from(self.get_raw_value(section_name, subsection_name, key)?)
567+ .map_err(|_| GitConfigError::FailedConversion)
385568 }
386−}
387569
388−#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
389−struct EntryData {
570+ /// Returns all interpreted values given a section, an optional subsection
571+ /// and key.
572+ ///
573+ /// It's recommended to use one of the values in the [`values`] module as
574+ /// the conversion is already implemented, but this function is flexible and
575+ /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`].
576+ ///
577+ /// Consider [`Self::value`] if you want to get a single value
578+ /// (following last-one-wins resolution) instead.
579+ ///
580+ /// # Examples
581+ ///
582+ /// ```
583+ /// # use git_config::file::{GitConfig, GitConfigError};
584+ /// # use git_config::values::{Integer, Value, Boolean, TrueVariant};
585+ /// # use std::borrow::Cow;
586+ /// # use std::convert::TryFrom;
587+ /// let config = r#"
588+ /// [core]
589+ /// a = true
590+ /// c = g
591+ /// [core]
592+ /// a
593+ /// a = false
594+ /// "#;
595+ /// let git_config = GitConfig::try_from(config).unwrap();
596+ /// // You can either use the turbofish to determine the type...
597+ /// let a_value = git_config.multi_value::<Boolean>("core", None, "a")?;
598+ /// assert_eq!(
599+ /// a_value,
600+ /// vec![
601+ /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true"))),
602+ /// Boolean::True(TrueVariant::Implicit),
603+ /// Boolean::False(Cow::Borrowed("false")),
604+ /// ]
605+ /// );
606+ /// // ... or explicitly declare the type to avoid the turbofish
607+ /// let c_value: Vec<Value> = git_config.multi_value("core", None, "c")?;
608+ /// assert_eq!(c_value, vec![Value::Other(Cow::Borrowed(b"g"))]);
609+ /// # Ok::<(), GitConfigError>(())
610+ /// ```
611+ ///
612+ /// # Errors
613+ ///
614+ /// This function will return an error if the key is not in the requested
615+ /// section and subsection, if the section and subsection do not exist, or
616+ /// if there was an issue converting the type into the requested variant.
617+ ///
618+ /// [`values`]: crate::values
619+ /// [`TryFrom`]: std::convert::TryFrom
620+ pub fn multi_value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
621+ &'event self,
622+ section_name: &'lookup str,
623+ subsection_name: Option<&'lookup str>,
624+ key: &'lookup str,
625+ ) -> Result<Vec<T>, GitConfigError<'lookup>> {
626+ self.get_raw_multi_value(section_name, subsection_name, key)?
627+ .into_iter()
628+ .map(T::try_from)
629+ .collect::<Result<Vec<_>, _>>()
630+ .map_err(|_| GitConfigError::FailedConversion)
631+ }
632+
633+ /// Returns an immutable section reference.
634+ ///
635+ /// # Errors
636+ ///
637+ /// This function will return an error if the section and optional
638+ /// subsection do not exist.
639+ pub fn section<'lookup>(
640+ &mut self,
641+ section_name: &'lookup str,
642+ subsection_name: Option<&'lookup str>,
643+ ) -> Result<&OwnedSection<'event>, GitConfigError<'lookup>> {
644+ let section_ids =
645+ self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
646+ Ok(self.sections.get(section_ids.last().unwrap()).unwrap())
647+ }
648+
649+ /// Returns an mutable section reference.
650+ ///
651+ /// # Errors
652+ ///
653+ /// This function will return an error if the section and optional
654+ /// subsection do not exist.
655+ pub fn section_mut<'lookup>(
656+ &mut self,
657+ section_name: &'lookup str,
658+ subsection_name: Option<&'lookup str>,
659+ ) -> Result<MutableSection<'_, 'event>, GitConfigError<'lookup>> {
660+ let section_ids =
661+ self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
662+
663+ Ok(MutableSection::new(
664+ self.sections.get_mut(section_ids.last().unwrap()).unwrap(),
665+ ))
666+ }
667+
668+ /// Adds a new section to config. If a subsection name was provided, then
669+ /// the generated header will use the modern subsection syntax. Returns a
670+ /// reference to the new section for immediate editing.
671+ ///
672+ /// # Examples
673+ ///
674+ /// Creating a new empty section:
675+ ///
676+ /// ```
677+ /// # use git_config::file::{GitConfig, GitConfigError};
678+ /// # use std::convert::TryFrom;
679+ /// let mut git_config = GitConfig::new();
680+ /// let _section = git_config.new_section("hello", Some("world".into()));
681+ /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n");
682+ /// ```
683+ ///
684+ /// Creating a new empty section and adding values to it:
685+ ///
686+ /// ```
687+ /// # use git_config::file::{GitConfig, GitConfigError};
688+ /// # use std::convert::TryFrom;
689+ /// let mut git_config = GitConfig::new();
690+ /// let mut section = git_config.new_section("hello", Some("world".into()));
691+ /// section.push("a".into(), "b".as_bytes().into());
692+ /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n");
693+ /// let _section = git_config.new_section("core", None);
694+ /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n");
695+ /// ```
696+ pub fn new_section(
697+ &mut self,
698+ section_name: impl Into<Cow<'event, str>>,
699+ subsection_name: impl Into<Option<Cow<'event, str>>>,
700+ ) -> MutableSection<'_, 'event> {
701+ let subsection_name = subsection_name.into();
702+ let mut section = if subsection_name.is_some() {
703+ self.push_section(
704+ ParsedSectionHeader {
705+ name: SectionHeaderName(section_name.into()),
706+ separator: Some(" ".into()),
707+ subsection_name,
708+ },
709+ OwnedSection::new(),
710+ )
711+ } else {
712+ self.push_section(
713+ ParsedSectionHeader {
714+ name: SectionHeaderName(section_name.into()),
715+ separator: None,
716+ subsection_name: None,
717+ },
718+ OwnedSection::new(),
719+ )
720+ };
721+ section.push_newline();
722+ section
723+ }
724+
725+ /// Removes the section, returning the events it had, if any. If multiple
726+ /// sections have the same name, then the last one is returned. Note that
727+ /// later sections with the same name have precedent over earlier ones.
728+ ///
729+ /// # Examples
730+ ///
731+ /// Creating and removing a section:
732+ ///
733+ /// ```
734+ /// # use git_config::file::{GitConfig, GitConfigError};
735+ /// # use std::convert::TryFrom;
736+ /// let mut git_config = GitConfig::try_from(
737+ /// r#"[hello "world"]
738+ /// some-value = 4
739+ /// "#).unwrap();
740+ ///
741+ /// let events = git_config.remove_section("hello", Some("world".into()));
742+ /// assert_eq!(git_config.to_string(), "");
743+ /// ```
744+ ///
745+ /// Precedence example for removing sections with the same name:
746+ ///
747+ /// ```
748+ /// # use git_config::file::{GitConfig, GitConfigError};
749+ /// # use std::convert::TryFrom;
750+ /// let mut git_config = GitConfig::try_from(
751+ /// r#"[hello "world"]
752+ /// some-value = 4
753+ /// [hello "world"]
754+ /// some-value = 5
755+ /// "#).unwrap();
756+ ///
757+ /// let events = git_config.remove_section("hello", Some("world".into()));
758+ /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n");
759+ /// ```
760+ pub fn remove_section<'lookup>(
761+ &mut self,
762+ section_name: &'lookup str,
763+ subsection_name: impl Into<Option<&'lookup str>>,
764+ ) -> Option<OwnedSection> {
765+ let section_ids =
766+ self.get_section_ids_by_name_and_subname(section_name, subsection_name.into());
767+ let id = section_ids.ok()?.pop()?;
768+ self.section_order
769+ .remove(self.section_order.iter().position(|v| *v == id).unwrap());
770+ self.sections.remove(&id)
771+ }
772+}
773+
774+/// An intermediate representation of a mutable value obtained from
775+/// [`GitConfig`].
776+///
777+/// This holds a mutable reference to the underlying data structure of
778+/// [`GitConfig`], and thus guarantees through Rust's borrower checker that
779+/// multiple mutable references to [`GitConfig`] cannot be owned at the same
780+/// time.
781+#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
782+pub struct MutableValue<'borrow, 'lookup, 'event> {
783+ section: MutableSection<'borrow, 'event>,
784+ key: Key<'lookup>,
785+ index: usize,
786+ size: usize,
787+}
788+
789+impl MutableValue<'_, '_, '_> {
790+ /// Returns the actual value. This is computed each time this is called, so
791+ /// it's best to reuse this value or own it if an allocation is acceptable.
792+ ///
793+ /// # Errors
794+ ///
795+ /// Returns an error if the lookup failed.
796+ #[inline]
797+ pub fn get(&self) -> Result<Cow<'_, [u8]>, GitConfigError> {
798+ self.section
799+ .get(&self.key, self.index, self.index + self.size)
800+ }
801+
802+ /// Update the value to the provided one. This modifies the value such that
803+ /// the Value event(s) are replaced with a single new event containing the
804+ /// new value.
805+ #[inline]
806+ pub fn set_string(&mut self, input: String) {
807+ self.set_bytes(input.into_bytes());
808+ }
809+
810+ /// Update the value to the provided one. This modifies the value such that
811+ /// the Value event(s) are replaced with a single new event containing the
812+ /// new value.
813+ pub fn set_bytes(&mut self, input: Vec<u8>) {
814+ if self.size > 0 {
815+ self.section.delete(self.index, self.index + self.size);
816+ }
817+ self.size = 3;
818+ self.section
819+ .set_value(self.index, Key(Cow::Owned(self.key.to_string())), input);
820+ }
821+
822+ /// Removes the value. Does nothing when called multiple times in
823+ /// succession.
824+ pub fn delete(&mut self) {
825+ if self.size > 0 {
826+ self.section.delete(self.index, self.index + self.size);
827+ self.size = 0;
828+ }
829+ }
830+}
831+
832+/// Internal data structure for [`MutableMultiValue`]
833+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
834+struct EntryData {
390835 section_id: SectionId,
391836 offset_index: usize,
392837 }
@@ -398,7 +843,7 @@ struct EntryData { · 5 unmodified lines
398843 /// [`GitConfig`], and thus guarantees through Rust's borrower checker that
399844 /// multiple mutable references to [`GitConfig`] cannot be owned at the same
400845 /// time.
401−#[derive(Eq, PartialEq, Debug)]
846+#[derive(PartialEq, Eq, Debug)]
402847 pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
403848 section: &'borrow mut HashMap<SectionId, OwnedSection<'event>>,
404849 key: Key<'lookup>,
@@ -693,297 +1138,10 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { · 288 unmodified lines
6931138 }
6941139 }
6951140
696−/// High level `git-config` reader and writer.
697−///
698−/// Internally, this uses various acceleration data structures to improve
699−/// performance of the typical usage behavior of many lookups and relatively
700−/// fewer insertions.
701−///
702−/// # Multivar behavior
703−///
704−/// `git` is flexible enough to allow users to set a key multiple times in
705−/// any number of identically named sections. When this is the case, the key
706−/// is known as a "multivar". In this case, `get_raw_value` follows the
707−/// "last one wins" approach that `git-config` internally uses for multivar
708−/// resolution.
709−///
710−/// Concretely, the following config has a multivar, `a`, with the values
711−/// of `b`, `c`, and `d`, while `e` is a single variable with the value
712−/// `f g h`.
713−///
714−/// ```text
715−/// [core]
716−/// a = b
717−/// a = c
718−/// [core]
719−/// a = d
720−/// e = f g h
721−/// ```
722−///
723−/// Calling methods that fetch or set only one value (such as [`get_raw_value`])
724−/// key `a` with the above config will fetch `d` or replace `d`, since the last
725−/// valid config key/value pair is `a = d`:
726−///
727−/// ```
728−/// # use git_config::file::GitConfig;
729−/// # use std::borrow::Cow;
730−/// # use std::convert::TryFrom;
731−/// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
732−/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(Cow::Borrowed("d".as_bytes())));
733−/// ```
734−///
735−/// Consider the `multi` variants of the methods instead, if you want to work
736−/// with all values instead.
737−///
738−/// [`get_raw_value`]: Self::get_raw_value
739−#[derive(PartialEq, Eq, Clone, Debug, Default)]
740−pub struct GitConfig<'event> {
741− /// The list of events that occur before an actual section. Since a
742− /// `git-config` file prohibits global values, this vec is limited to only
743− /// comment, newline, and whitespace events.
744− frontmatter_events: OwnedSection<'event>,
745− section_lookup_tree: HashMap<SectionHeaderName<'event>, Vec<LookupTreeNode<'event>>>,
746− /// SectionId to section mapping. The value of this HashMap contains actual
747− /// events.
748− ///
749− /// This indirection with the SectionId as the key is critical to flexibly
750− /// supporting `git-config` sections, as duplicated keys are permitted.
751− sections: HashMap<SectionId, OwnedSection<'event>>,
752− section_headers: HashMap<SectionId, ParsedSectionHeader<'event>>,
753− section_id_counter: usize,
754− section_order: VecDeque<SectionId>,
755−}
756−
757−impl<'event> GitConfig<'event> {
758− /// Constructs an empty `git-config` file.
759− #[must_use]
760− pub fn new() -> Self {
761− Self::default()
762− }
763−
764− /// Returns an interpreted value given a section, an optional subsection and
765− /// key.
766− ///
767− /// It's recommended to use one of the values in the [`values`] module as
768− /// the conversion is already implemented, but this function is flexible and
769− /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`].
770− ///
771− /// Consider [`Self::multi_value`] if you want to get all values of a
772− /// multivar instead.
773− ///
774− /// # Examples
775− ///
776− /// ```
777− /// # use git_config::file::{GitConfig, GitConfigError};
778− /// # use git_config::values::{Integer, Value, Boolean};
779− /// # use std::borrow::Cow;
780− /// # use std::convert::TryFrom;
781− /// let config = r#"
782− /// [core]
783− /// a = 10k
784− /// c
785− /// "#;
786− /// let git_config = GitConfig::try_from(config)?;
787− /// // You can either use the turbofish to determine the type...
788− /// let a_value = git_config.value::<Integer>("core", None, "a")?;
789− /// // ... or explicitly declare the type to avoid the turbofish
790− /// let c_value: Boolean = git_config.value("core", None, "c")?;
791− /// # Ok::<(), Box<dyn std::error::Error>>(())
792− /// ```
793− ///
794− /// # Errors
795− ///
796− /// This function will return an error if the key is not in the requested
797− /// section and subsection, if the section and subsection do not exist, or
798− /// if there was an issue converting the type into the requested variant.
799− ///
800− /// [`values`]: crate::values
801− /// [`TryFrom`]: std::convert::TryFrom
802− pub fn value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
803− &'event self,
804− section_name: &'lookup str,
805− subsection_name: Option<&'lookup str>,
806− key: &'lookup str,
807− ) -> Result<T, GitConfigError<'lookup>> {
808− T::try_from(self.get_raw_value(section_name, subsection_name, key)?)
809− .map_err(|_| GitConfigError::FailedConversion)
810− }
811−
812− /// Returns all interpreted values given a section, an optional subsection
813− /// and key.
814− ///
815− /// It's recommended to use one of the values in the [`values`] module as
816− /// the conversion is already implemented, but this function is flexible and
817− /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`].
818− ///
819− /// Consider [`Self::value`] if you want to get a single value
820− /// (following last-one-wins resolution) instead.
821− ///
822− /// # Examples
823− ///
824− /// ```
825− /// # use git_config::file::{GitConfig, GitConfigError};
826− /// # use git_config::values::{Integer, Value, Boolean, TrueVariant};
827− /// # use std::borrow::Cow;
828− /// # use std::convert::TryFrom;
829− /// let config = r#"
830− /// [core]
831− /// a = true
832− /// c = g
833− /// [core]
834− /// a
835− /// a = false
836− /// "#;
837− /// let git_config = GitConfig::try_from(config).unwrap();
838− /// // You can either use the turbofish to determine the type...
839− /// let a_value = git_config.multi_value::<Boolean>("core", None, "a")?;
840− /// assert_eq!(
841− /// a_value,
842− /// vec![
843− /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true"))),
844− /// Boolean::True(TrueVariant::Implicit),
845− /// Boolean::False(Cow::Borrowed("false")),
846− /// ]
847− /// );
848− /// // ... or explicitly declare the type to avoid the turbofish
849− /// let c_value: Vec<Value> = git_config.multi_value("core", None, "c")?;
850− /// assert_eq!(c_value, vec![Value::Other(Cow::Borrowed(b"g"))]);
851− /// # Ok::<(), GitConfigError>(())
852− /// ```
853− ///
854− /// # Errors
855− ///
856− /// This function will return an error if the key is not in the requested
857− /// section and subsection, if the section and subsection do not exist, or
858− /// if there was an issue converting the type into the requested variant.
859− ///
860− /// [`values`]: crate::values
861− /// [`TryFrom`]: std::convert::TryFrom
862− pub fn multi_value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
863− &'event self,
864− section_name: &'lookup str,
865− subsection_name: Option<&'lookup str>,
866− key: &'lookup str,
867− ) -> Result<Vec<T>, GitConfigError<'lookup>> {
868− self.get_raw_multi_value(section_name, subsection_name, key)?
869− .into_iter()
870− .map(T::try_from)
871− .collect::<Result<Vec<_>, _>>()
872− .map_err(|_| GitConfigError::FailedConversion)
873− }
874−
875− /// Returns an immutable section reference.
876− ///
877− /// # Errors
878− ///
879− /// This function will return an error if the section and optional
880− /// subsection do not exist.
881− pub fn section<'lookup>(
882− &mut self,
883− section_name: &'lookup str,
884− subsection_name: Option<&'lookup str>,
885− ) -> Result<&OwnedSection<'event>, GitConfigError<'lookup>> {
886− let section_ids =
887− self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
888− Ok(self.sections.get(section_ids.last().unwrap()).unwrap())
889− }
890−
891− /// Returns an mutable section reference.
892− ///
893− /// # Errors
894− ///
895− /// This function will return an error if the section and optional
896− /// subsection do not exist.
897− pub fn section_mut<'lookup>(
898− &mut self,
899− section_name: &'lookup str,
900− subsection_name: Option<&'lookup str>,
901− ) -> Result<MutableSection<'_, 'event>, GitConfigError<'lookup>> {
902− let section_ids =
903− self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
904−
905− Ok(MutableSection::new(
906− self.sections.get_mut(section_ids.last().unwrap()).unwrap(),
907− ))
908− }
909−
910− /// Adds a new section to config. If a subsection name was provided, then
911− /// the generated header will use the modern subsection syntax. Returns a
912− /// reference to the new section for immediate editing.
913− ///
914− /// # Examples
915− ///
916− /// Creating a new empty section:
917− ///
918− /// ```
919− /// # use git_config::file::{GitConfig, GitConfigError};
920− /// # use std::convert::TryFrom;
921− /// let mut git_config = GitConfig::new();
922− /// let _section = git_config.new_section("hello", Some("world".into()));
923− /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n");
924− /// ```
925− ///
926− /// Creating a new empty section and adding values to it:
927− ///
928− /// ```
929− /// # use git_config::file::{GitConfig, GitConfigError};
930− /// # use std::convert::TryFrom;
931− /// let mut git_config = GitConfig::new();
932− /// let mut section = git_config.new_section("hello", Some("world".into()));
933− /// section.push("a".into(), "b".as_bytes().into());
934− /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n");
935− /// let _section = git_config.new_section("core", None);
936− /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n");
937− /// ```
938− pub fn new_section(
939− &mut self,
940− section_name: impl Into<Cow<'event, str>>,
941− subsection_name: impl Into<Option<Cow<'event, str>>>,
942− ) -> MutableSection<'_, 'event> {
943− let subsection_name = subsection_name.into();
944− let mut section = if subsection_name.is_some() {
945− self.push_section(
946− ParsedSectionHeader {
947− name: SectionHeaderName(section_name.into()),
948− separator: Some(" ".into()),
949− subsection_name,
950− },
951− OwnedSection::new(),
952− )
953− } else {
954− self.push_section(
955− ParsedSectionHeader {
956− name: SectionHeaderName(section_name.into()),
957− separator: None,
958− subsection_name: None,
959− },
960− OwnedSection::new(),
961− )
962− };
963− section.push_newline();
964− section
965− }
966−
967− /// Removes the section, returning the events it had, if any. If multiple
968− /// sections have the same name, then the last one is returned.
969− pub fn remove_section<'lookup>(
970− &mut self,
971− section_name: &'lookup str,
972− subsection_name: impl Into<Option<&'lookup str>>,
973− ) -> Option<OwnedSection> {
974− let section_ids =
975− self.get_section_ids_by_name_and_subname(section_name, subsection_name.into());
976− let section_ids = section_ids.ok()?.pop()?;
977− self.sections.remove(&section_ids)
978− }
979−}
980−
981−/// # Raw value API
1141+/// # Raw value API
9821142 ///
9831143 /// These functions are the raw value API. Instead of returning Rust structures,
984−/// these functions return bytes which may or may not be owned. Generally
985−/// speaking, you shouldn't need to use these functions, but are exposed in case
986−/// the higher level functions are deficient.
1144+/// these functions return bytes which may or may not be owned.
9871145 impl<'event> GitConfig<'event> {
9881146 /// Returns an uninterpreted value given a section, an optional subsection
9891147 /// and key.
@@ -1042,6 +1200,7 @@ impl<'event> GitConfig<'event> { · 52 unmodified lines
10421200 let mut size = 0;
10431201 let mut index = 0;
10441202 let mut found_key = false;
1203+ // todo: iter backwards
10451204 for (i, event) in self.sections.get(section_id).unwrap().0.iter().enumerate() {
10461205 match event {
10471206 Event::Key(event_key) if *event_key == key => {
@@ -1123,14 +1282,13 @@ impl<'event> GitConfig<'event> { · 75 unmodified lines
11231282 subsection_name: Option<&'lookup str>,
11241283 key: &'lookup str,
11251284 ) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {
1126− let key = Key(key.into());
11271285 let mut values = vec![];
11281286 for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {
11291287 values.extend(
11301288 self.sections
11311289 .get(&section_id)
11321290 .unwrap()
1133− .values(&key)
1291+ .values(&Key(key.into()))
11341292 .iter()
11351293 .map(|v| Cow::Owned(v.to_vec())),
11361294 );
@@ -1544,7 +1702,7 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> { · 407 unmodified lines
15441702 new_self.frontmatter_events = section_events;
15451703 }
15461704
1547− dbg!(new_self)
1705+ new_self
15481706 }
15491707 }
15501708