Unified · Split
| @@ -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 | }; |
| 6 | use crate::values::{normalize_bytes, normalize_cow, normalize_vec}; | 6 | use crate::values::{normalize_bytes, normalize_cow, normalize_vec}; |
| 7 | use std::borrow::{Borrow, Cow}; | ||
| 8 | use std::collections::{HashMap, VecDeque}; | 7 | use std::collections::{HashMap, VecDeque}; |
| 9 | use std::convert::TryFrom; | 8 | use std::convert::TryFrom; |
| 10 | use std::fmt::Display; | 9 | use std::fmt::Display; |
| 11 | use std::ops::{Deref, DerefMut}; | 10 | use std::ops::{Deref, DerefMut}; |
| 11 | use 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 | ||
| 41 | impl std::error::Error for GitConfigError<'_> {} | 44 | impl 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)] | ||
| 55 | struct 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)] |
| 59 | pub struct MutableSection<'borrow, 'event> { | 48 | pub struct MutableSection<'borrow, 'event> { |
| @@ -113,6 +102,50 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 53 unmodified lines | |||
| 113 | None | 102 | 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 unless | 149 | /// 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 the | 168 | /// 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.whitespace | 172 | 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 instead | 263 | 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 borrowed | 269 | }); |
| 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, and | 289 | /// 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 whole | 311 | // 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 | ||
| 319 | impl<'event> From<Vec<Event<'event>>> for OwnedSection<'event> { | 419 | 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 | |||
| 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)] | ||
| 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. | ||
| 325 | #[derive(PartialEq, Eq, Clone, Debug)] | 443 | #[derive(PartialEq, Eq, Clone, Debug)] |
| 326 | enum LookupTreeNode<'a> { | 444 | enum 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 from | 449 | /// High level `git-config` reader and writer. |
| 332 | /// [`GitConfig`]. | ||
| 333 | /// | 450 | /// |
| 334 | /// This holds a mutable reference to the underlying data structure of | 451 | /// Internally, this uses various acceleration data structures to improve |
| 335 | /// [`GitConfig`], and thus guarantees through Rust's borrower checker that | 452 | /// performance of the typical usage behavior of many lookups and relatively |
| 336 | /// multiple mutable references to [`GitConfig`] cannot be owned at the same | 453 | /// fewer insertions. |
| 337 | /// time. | 454 | /// |
| 338 | pub 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)] | ||
| 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>, | ||
| 343 | } | 513 | } |
| 344 | 514 | ||
| 345 | impl MutableValue<'_, '_, '_> { | 515 | impl<'event> GitConfig<'event> { |
| 346 | /// Returns the actual value. This is computed each time this is called, so | 516 | /// 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 | /// # Errors | 552 | /// # 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.section | 557 | /// |
| 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 that | 561 | &'event self, |
| 359 | /// the Value event(s) are replaced with a single new event containing the | 562 | 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 |
| 389 | struct 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)] | ||
| 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 { | ||
| 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 that | 843 | /// [`GitConfig`], and thus guarantees through Rust's borrower checker that |
| 399 | /// multiple mutable references to [`GitConfig`] cannot be owned at the same | 844 | /// 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)] |
| 402 | pub struct MutableMultiValue<'borrow, 'lookup, 'event> { | 847 | pub 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)] | ||
| 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(§ion_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. Generally | 1144 | /// 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. | ||
| 987 | impl<'event> GitConfig<'event> { | 1145 | impl<'event> GitConfig<'event> { |
| 988 | /// Returns an uninterpreted value given a section, an optional subsection | 1146 | /// 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.sections | 1288 | self.sections |
| 1131 | .get(§ion_id) | 1289 | .get(§ion_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 | ||