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
4 parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName,4 parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName,
5};5};
6use crate::values::{normalize_bytes, normalize_cow, normalize_vec};6use crate::values::{normalize_bytes, normalize_cow, normalize_vec};
7use std::borrow::{Borrow, Cow};
8use std::collections::{HashMap, VecDeque};7use std::collections::{HashMap, VecDeque};
9use std::convert::TryFrom;8use std::convert::TryFrom;
10use std::fmt::Display;9use std::fmt::Display;
11use std::ops::{Deref, DerefMut};10use std::ops::{Deref, DerefMut};
11use std::{
12 borrow::{Borrow, Cow},
13 ops::Range,
14};
12 15
13/// All possible error types that may occur from interacting with [`GitConfig`].16/// All possible error types that may occur from interacting with [`GitConfig`].
14#[derive(PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Debug)]17#[derive(PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Debug)]
@@ -40,20 +43,6 @@ impl Display for GitConfigError<'_> { · 25 unmodified lines
40 43
41impl std::error::Error for GitConfigError<'_> {}44impl std::error::Error for GitConfigError<'_> {}
42 45
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)]
55struct SectionId(usize);
56
57/// A opaque type that represents a mutable reference to a section.46/// A opaque type that represents a mutable reference to a section.
58#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]47#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
59pub struct MutableSection<'borrow, 'event> {48pub struct MutableSection<'borrow, 'event> {
@@ -113,6 +102,50 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 53 unmodified lines
113 None102 None
114 }103 }
115 104
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
116 /// Adds a new line event. Note that you don't need to call this unless149 /// Adds a new line event. Note that you don't need to call this unless
117 /// you've disabled implicit newlines.150 /// you've disabled implicit newlines.
118 pub fn push_newline(&mut self) {151 pub fn push_newline(&mut self) {
@@ -134,6 +167,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 15 unmodified lines
134 167
135 /// Returns the number of whitespace this section will insert before the168 /// Returns the number of whitespace this section will insert before the
136 /// beginning of a key.169 /// beginning of a key.
170 #[must_use]
137 pub const fn whitespace(&self) -> usize {171 pub const fn whitespace(&self) -> usize {
138 self.whitespace172 self.whitespace
139 }173 }
@@ -221,33 +255,35 @@ impl<'event> OwnedSection<'event> { · 81 unmodified lines
221 /// Returns None if the key was not found.255 /// Returns None if the key was not found.
222 #[must_use]256 #[must_use]
223 pub fn value(&self, key: &Key) -> Option<Cow<'event, [u8]>> {257 pub fn value(&self, key: &Key) -> Option<Cow<'event, [u8]>> {
224 let mut found_key = false;258 let range = self.get_value_range_by_key(key);
225 let mut latest_value = None;259 if range.is_empty() {
226 let mut partial_value = None;260 return None;
261 }
227 262
228 // todo: iterate backwards instead263 if range.end - range.start == 1 {
229 for event in &self.0 {264 return self.0.get(range.start).map(|e| match e {
230 match event {265 Event::Value(v) => v.clone(),
231 Event::Key(event_key) if *event_key == *key => found_key = true,266 // range only has one element so we know it's a value event, so
232 Event::Value(v) if found_key => {267 // it's impossible to reach this code.
233 found_key = false;268 _ => unreachable!(),
234 // Clones the Cow, doesn't copy underlying value if borrowed269 });
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 }
248 }270 }
249 271
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 )))
251 }287 }
252 288
253 /// Retrieves the last matching value in a section with the given key, and289 /// Retrieves the last matching value in a section with the given key, and
@@ -273,7 +309,7 @@ impl<'event> OwnedSection<'event> { · 19 unmodified lines
273 let mut partial_value = None;309 let mut partial_value = None;
274 310
275 // This can iterate forwards because we need to iterate over the whole311 // This can iterate forwards because we need to iterate over the whole
276 // section anyways.312 // section anyways
277 for event in &self.0 {313 for event in &self.0 {
278 match event {314 match event {
279 Event::Key(event_key) if event_key == key => found_key = true,315 Event::Key(event_key) if event_key == key => found_key = true,
@@ -314,6 +350,70 @@ impl<'event> OwnedSection<'event> { · 34 unmodified lines
314 .collect::<Result<Vec<T>, _>>()350 .collect::<Result<Vec<T>, _>>()
315 .map_err(|_| GitConfigError::FailedConversion)351 .map_err(|_| GitConfigError::FailedConversion)
316 }352 }
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 }
317}417}
318 418
319impl<'event> From<Vec<Event<'event>>> for OwnedSection<'event> {419impl<'event> From<Vec<Event<'event>>> for OwnedSection<'event> {
@@ -322,71 +422,416 @@ impl<'event> From<Vec<Event<'event>>> for OwnedSection<'event> { · 2 unmodified lines
322 }422 }
323}423}
324 424
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)]
437struct 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.
325#[derive(PartialEq, Eq, Clone, Debug)]443#[derive(PartialEq, Eq, Clone, Debug)]
326enum LookupTreeNode<'a> {444enum LookupTreeNode<'a> {
327 Terminal(Vec<SectionId>),445 Terminal(Vec<SectionId>),
328 NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),446 NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),
329}447}
330 448
331/// An intermediate representation of a mutable value obtained from449/// High level `git-config` reader and writer.
332/// [`GitConfig`].
333///450///
334/// This holds a mutable reference to the underlying data structure of451/// Internally, this uses various acceleration data structures to improve
335/// [`GitConfig`], and thus guarantees through Rust's borrower checker that452/// performance of the typical usage behavior of many lookups and relatively
336/// multiple mutable references to [`GitConfig`] cannot be owned at the same453/// fewer insertions.
337/// time.454///
338pub struct MutableValue<'borrow, 'lookup, 'event> {455/// # Multivar behavior
339 section: MutableSection<'borrow, 'event>,456///
340 key: Key<'lookup>,457/// `git` is flexible enough to allow users to set a key multiple times in
341 index: usize,458/// any number of identically named sections. When this is the case, the key
342 size: usize,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)]
493pub 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>,
343}513}
344 514
345impl MutableValue<'_, '_, '_> {515impl<'event> GitConfig<'event> {
346 /// Returns the actual value. This is computed each time this is called, so516 /// Constructs an empty `git-config` file.
347 /// it's best to reuse this value or own it if an allocation is acceptable.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 /// ```
348 ///551 ///
349 /// # Errors552 /// # Errors
350 ///553 ///
351 /// Returns an error if the lookup failed.554 /// This function will return an error if the key is not in the requested
352 #[inline]555 /// section and subsection, if the section and subsection do not exist, or
353 pub fn get(&self) -> Result<Cow<'_, [u8]>, GitConfigError> {556 /// if there was an issue converting the type into the requested variant.
354 self.section557 ///
355 .get(&self.key, self.index, self.index + self.size)558 /// [`values`]: crate::values
356 }559 /// [`TryFrom`]: std::convert::TryFrom
357 560 pub fn value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
358 /// Update the value to the provided one. This modifies the value such that561 &'event self,
359 /// the Value event(s) are replaced with a single new event containing the562 section_name: &'lookup str,
360 /// new value.563 subsection_name: Option<&'lookup str>,
361 #[inline]564 key: &'lookup str,
362 pub fn set_string(&mut self, input: String) {565 ) -> Result<T, GitConfigError<'lookup>> {
363 self.set_bytes(input.into_bytes());566 T::try_from(self.get_raw_value(section_name, subsection_name, key)?)
364 }567 .map_err(|_| GitConfigError::FailedConversion)
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 }
385 }568 }
386}
387 569
388#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]570 /// Returns all interpreted values given a section, an optional subsection
389struct EntryData {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)]
782pub struct MutableValue<'borrow, 'lookup, 'event> {
783 section: MutableSection<'borrow, 'event>,
784 key: Key<'lookup>,
785 index: usize,
786 size: usize,
787}
788
789impl 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)]
834struct EntryData {
390 section_id: SectionId,835 section_id: SectionId,
391 offset_index: usize,836 offset_index: usize,
392}837}
@@ -398,7 +843,7 @@ struct EntryData { · 5 unmodified lines
398/// [`GitConfig`], and thus guarantees through Rust's borrower checker that843/// [`GitConfig`], and thus guarantees through Rust's borrower checker that
399/// multiple mutable references to [`GitConfig`] cannot be owned at the same844/// multiple mutable references to [`GitConfig`] cannot be owned at the same
400/// time.845/// time.
401#[derive(Eq, PartialEq, Debug)]846#[derive(PartialEq, Eq, Debug)]
402pub struct MutableMultiValue<'borrow, 'lookup, 'event> {847pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
403 section: &'borrow mut HashMap<SectionId, OwnedSection<'event>>,848 section: &'borrow mut HashMap<SectionId, OwnedSection<'event>>,
404 key: Key<'lookup>,849 key: Key<'lookup>,
@@ -693,297 +1138,10 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> { · 288 unmodified lines
693 }1138 }
694}1139}
695 1140
696/// High level `git-config` reader and writer.1141/// # Raw value API
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)]
740pub 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
757impl<'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
982///1142///
983/// These functions are the raw value API. Instead of returning Rust structures,1143/// These functions are the raw value API. Instead of returning Rust structures,
984/// these functions return bytes which may or may not be owned. Generally1144/// these functions return bytes which may or may not be owned.
985/// speaking, you shouldn't need to use these functions, but are exposed in case
986/// the higher level functions are deficient.
987impl<'event> GitConfig<'event> {1145impl<'event> GitConfig<'event> {
988 /// Returns an uninterpreted value given a section, an optional subsection1146 /// Returns an uninterpreted value given a section, an optional subsection
989 /// and key.1147 /// and key.
@@ -1042,6 +1200,7 @@ impl<'event> GitConfig<'event> { · 52 unmodified lines
1042 let mut size = 0;1200 let mut size = 0;
1043 let mut index = 0;1201 let mut index = 0;
1044 let mut found_key = false;1202 let mut found_key = false;
1203 // todo: iter backwards
1045 for (i, event) in self.sections.get(section_id).unwrap().0.iter().enumerate() {1204 for (i, event) in self.sections.get(section_id).unwrap().0.iter().enumerate() {
1046 match event {1205 match event {
1047 Event::Key(event_key) if *event_key == key => {1206 Event::Key(event_key) if *event_key == key => {
@@ -1123,14 +1282,13 @@ impl<'event> GitConfig<'event> { · 75 unmodified lines
1123 subsection_name: Option<&'lookup str>,1282 subsection_name: Option<&'lookup str>,
1124 key: &'lookup str,1283 key: &'lookup str,
1125 ) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {1284 ) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {
1126 let key = Key(key.into());
1127 let mut values = vec![];1285 let mut values = vec![];
1128 for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {1286 for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {
1129 values.extend(1287 values.extend(
1130 self.sections1288 self.sections
1131 .get(&section_id)1289 .get(&section_id)
1132 .unwrap()1290 .unwrap()
1133 .values(&key)1291 .values(&Key(key.into()))
1134 .iter()1292 .iter()
1135 .map(|v| Cow::Owned(v.to_vec())),1293 .map(|v| Cow::Owned(v.to_vec())),
1136 );1294 );
@@ -1544,7 +1702,7 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> { · 407 unmodified lines
1544 new_self.frontmatter_events = section_events;1702 new_self.frontmatter_events = section_events;
1545 }1703 }
1546 1704
1547 dbg!(new_self)1705 new_self
1548 }1706 }
1549}1707}
1550 1708