Unified · Split
| @@ -4,11 +4,14 @@ use crate::parser::{ · 3 unmodified lines | ||
| 4 | 4 | parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName, |
| 5 | 5 | }; |
| 6 | 6 | use crate::values::{normalize_bytes, normalize_cow, normalize_vec}; |
| 7 | −use std::borrow::{Borrow, Cow}; | |
| 8 | 7 | use std::collections::{HashMap, VecDeque}; |
| 9 | 8 | use std::convert::TryFrom; |
| 10 | 9 | use std::fmt::Display; |
| 11 | 10 | use std::ops::{Deref, DerefMut}; |
| 11 | +use std::{ | |
| 12 | + borrow::{Borrow, Cow}, | |
| 13 | + ops::Range, | |
| 14 | +}; | |
| 12 | 15 | |
| 13 | 16 | /// All possible error types that may occur from interacting with [`GitConfig`]. |
| 14 | 17 | #[derive(PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Debug)] |
| @@ -40,20 +43,6 @@ impl Display for GitConfigError<'_> { · 25 unmodified lines | ||
| 40 | 43 | |
| 41 | 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 | 46 | /// A opaque type that represents a mutable reference to a section. |
| 58 | 47 | #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] |
| 59 | 48 | pub struct MutableSection<'borrow, 'event> { |
| @@ -113,6 +102,50 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 53 unmodified lines | ||
| 113 | 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 | 149 | /// Adds a new line event. Note that you don't need to call this unless |
| 117 | 150 | /// you've disabled implicit newlines. |
| 118 | 151 | pub fn push_newline(&mut self) { |
| @@ -134,6 +167,7 @@ impl<'borrow, 'event> MutableSection<'borrow, 'event> { · 15 unmodified lines | ||
| 134 | 167 | |
| 135 | 168 | /// Returns the number of whitespace this section will insert before the |
| 136 | 169 | /// beginning of a key. |
| 170 | + #[must_use] | |
| 137 | 171 | pub const fn whitespace(&self) -> usize { |
| 138 | 172 | self.whitespace |
| 139 | 173 | } |
| @@ -221,33 +255,35 @@ impl<'event> OwnedSection<'event> { · 81 unmodified lines | ||
| 221 | 255 | /// Returns None if the key was not found. |
| 222 | 256 | #[must_use] |
| 223 | 257 | 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 | + } | |
| 227 | 262 | |
| 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 | + }); | |
| 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 | 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 | 309 | let mut partial_value = None; |
| 274 | 310 | |
| 275 | 311 | // This can iterate forwards because we need to iterate over the whole |
| 276 | − // section anyways. | |
| 312 | + // section anyways | |
| 277 | 313 | for event in &self.0 { |
| 278 | 314 | match event { |
| 279 | 315 | Event::Key(event_key) if event_key == key => found_key = true, |
| @@ -314,6 +350,70 @@ impl<'event> OwnedSection<'event> { · 34 unmodified lines | ||
| 314 | 350 | .collect::<Result<Vec<T>, _>>() |
| 315 | 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 | 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 | 443 | #[derive(PartialEq, Eq, Clone, Debug)] |
| 326 | 444 | enum LookupTreeNode<'a> { |
| 327 | 445 | Terminal(Vec<SectionId>), |
| 328 | 446 | NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>), |
| 329 | 447 | } |
| 330 | 448 | |
| 331 | −/// An intermediate representation of a mutable value obtained from | |
| 332 | −/// [`GitConfig`]. | |
| 449 | +/// High level `git-config` reader and writer. | |
| 333 | 450 | /// |
| 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>, | |
| 343 | 513 | } |
| 344 | 514 | |
| 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 | + /// ``` | |
| 348 | 551 | /// |
| 349 | 552 | /// # Errors |
| 350 | 553 | /// |
| 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) | |
| 385 | 568 | } |
| 386 | −} | |
| 387 | 569 | |
| 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 { | |
| 390 | 835 | section_id: SectionId, |
| 391 | 836 | offset_index: usize, |
| 392 | 837 | } |
| @@ -398,7 +843,7 @@ struct EntryData { · 5 unmodified lines | ||
| 398 | 843 | /// [`GitConfig`], and thus guarantees through Rust's borrower checker that |
| 399 | 844 | /// multiple mutable references to [`GitConfig`] cannot be owned at the same |
| 400 | 845 | /// time. |
| 401 | −#[derive(Eq, PartialEq, Debug)] | |
| 846 | +#[derive(PartialEq, Eq, Debug)] | |
| 402 | 847 | pub struct MutableMultiValue<'borrow, 'lookup, 'event> { |
| 403 | 848 | section: &'borrow mut HashMap<SectionId, OwnedSection<'event>>, |
| 404 | 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. | |
| 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 | |
| 1141 | +/// # Raw value API | |
| 982 | 1142 | /// |
| 983 | 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 | |
| 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. | |
| 987 | 1145 | impl<'event> GitConfig<'event> { |
| 988 | 1146 | /// Returns an uninterpreted value given a section, an optional subsection |
| 989 | 1147 | /// and key. |
| @@ -1042,6 +1200,7 @@ impl<'event> GitConfig<'event> { · 52 unmodified lines | ||
| 1042 | 1200 | let mut size = 0; |
| 1043 | 1201 | let mut index = 0; |
| 1044 | 1202 | let mut found_key = false; |
| 1203 | + // todo: iter backwards | |
| 1045 | 1204 | for (i, event) in self.sections.get(section_id).unwrap().0.iter().enumerate() { |
| 1046 | 1205 | match event { |
| 1047 | 1206 | Event::Key(event_key) if *event_key == key => { |
| @@ -1123,14 +1282,13 @@ impl<'event> GitConfig<'event> { · 75 unmodified lines | ||
| 1123 | 1282 | subsection_name: Option<&'lookup str>, |
| 1124 | 1283 | key: &'lookup str, |
| 1125 | 1284 | ) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> { |
| 1126 | − let key = Key(key.into()); | |
| 1127 | 1285 | let mut values = vec![]; |
| 1128 | 1286 | for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? { |
| 1129 | 1287 | values.extend( |
| 1130 | 1288 | self.sections |
| 1131 | 1289 | .get(§ion_id) |
| 1132 | 1290 | .unwrap() |
| 1133 | − .values(&key) | |
| 1291 | + .values(&Key(key.into())) | |
| 1134 | 1292 | .iter() |
| 1135 | 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 | 1702 | new_self.frontmatter_events = section_events; |
| 1545 | 1703 | } |
| 1546 | 1704 | |
| 1547 | − dbg!(new_self) | |
| 1705 | + new_self | |
| 1548 | 1706 | } |
| 1549 | 1707 | } |
| 1550 | 1708 | |