Skip to main content

gix_config/file/access/
read_only.rs

1use bstr::{BStr, BString, ByteSlice};
2use gix_error::ExnResult;
3use gix_features::threading::OwnShared;
4use smallvec::SmallVec;
5
6use crate::{
7    AsBStrOpt, AsKey, File,
8    file::{
9        self, Metadata, SectionId,
10        write::{extract_newline, platform_newline},
11    },
12    lookup,
13    parse::EventRef,
14};
15
16/// Read-only low-level access methods, as it requires generics for converting into
17/// custom values defined in this crate like [`Integer`](crate::Integer) and
18/// [`Color`](crate::Color).
19impl File {
20    /// Returns an interpreted value given a `key`.
21    ///
22    /// It's recommended to use one of the value types provide dby this crate
23    /// as they implement the conversion, but this function is flexible and
24    /// will accept any type that implements [`TryFrom<&BStr>`](TryFrom).
25    ///
26    /// Consider [`Self::values`] if you want to get all values of a multivar instead.
27    ///
28    /// If a `string` is desired, use the [`string()`](Self::string()) method instead.
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// # use gix_config::File;
34    /// # use gix_config::{Integer, Boolean};
35    /// # use std::convert::TryFrom;
36    /// let config = r#"
37    ///     [core]
38    ///         a = 10k
39    ///         c = false
40    /// "#;
41    /// let git_config = gix_config::File::try_from(config).expect("valid config");
42    /// // You can either use the turbofish to determine the type...
43    /// let a_value = git_config.value::<Integer>("core.a").expect("valid value");
44    /// // ... or explicitly declare the type to avoid the turbofish
45    /// let c_value: Boolean = git_config.value("core.c").expect("valid value");
46    /// # Ok::<(), Box<dyn std::error::Error>>(())
47    /// ```
48    pub fn value<T: TryFrom<BString>>(&self, key: impl AsKey) -> Result<T, lookup::Error<T::Error>> {
49        let key = key.as_key();
50        self.value_by(key.section_name, key.subsection_name, key.value_name)
51    }
52
53    /// Returns an interpreted value given a section, an optional subsection and
54    /// value name.
55    ///
56    /// It's recommended to use one of the value types provide dby this crate
57    /// as they implement the conversion, but this function is flexible and
58    /// will accept any type that implements [`TryFrom<&BStr>`](std::convert::TryFrom).
59    ///
60    /// Consider [`Self::values`] if you want to get all values of a multivar instead.
61    ///
62    /// If a `string` is desired, use the [`string()`](Self::string()) method instead.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// # use gix_config::File;
68    /// # use gix_config::{Integer, Boolean};
69    /// # use std::convert::TryFrom;
70    /// let config = r#"
71    ///     [core]
72    ///         a = 10k
73    ///         c = false
74    /// "#;
75    /// let git_config = gix_config::File::try_from(config).expect("valid config");
76    /// // You can either use the turbofish to determine the type...
77    /// let a_value = git_config.value_by::<Integer>("core", None, "a").expect("valid value");
78    /// // ... or explicitly declare the type to avoid the turbofish
79    /// let c_value: Boolean = git_config.value_by("core", None, "c").expect("valid value");
80    /// # Ok::<(), Box<dyn std::error::Error>>(())
81    /// ```
82    pub fn value_by<T: TryFrom<BString>>(
83        &self,
84        section_name: impl AsRef<str>,
85        subsection_name: impl AsBStrOpt,
86        value_name: impl AsRef<str>,
87    ) -> Result<T, lookup::Error<T::Error>> {
88        T::try_from(self.raw_value_by(section_name, subsection_name, value_name)?)
89            .map_err(lookup::Error::FailedConversion)
90    }
91
92    /// Returns an interpreted value and the section containing it given a `key`.
93    ///
94    /// Resolution is identical to [`value()`][Self::value()]: the last explicit value wins, even across multiple
95    /// matching sections.
96    pub fn value_with_section<T: TryFrom<BString>>(
97        &self,
98        key: impl AsKey,
99    ) -> Result<(T, file::SectionRef<'_>), lookup::Error<T::Error>> {
100        let key = key.as_key();
101        self.value_with_section_by(key.section_name, key.subsection_name, key.value_name)
102    }
103
104    /// Returns an interpreted value and the section containing it given its individual key components.
105    ///
106    /// Resolution is identical to [`value_by()`][Self::value_by()]: the last explicit value wins, even across multiple
107    /// matching sections.
108    pub fn value_with_section_by<T: TryFrom<BString>>(
109        &self,
110        section_name: impl AsRef<str>,
111        subsection_name: impl AsBStrOpt,
112        value_name: impl AsRef<str>,
113    ) -> Result<(T, file::SectionRef<'_>), lookup::Error<T::Error>> {
114        let (value, section) = self.raw_value_with_section_by(section_name, subsection_name, value_name)?;
115        T::try_from(value)
116            .map(|value| (value, section))
117            .map_err(lookup::Error::FailedConversion)
118    }
119
120    /// Like [`value()`](File::value()), but returning an `None` if the value wasn't found at `section[.subsection].value_name`
121    pub fn try_value<T: TryFrom<BString>>(&self, key: impl AsKey) -> Result<Option<T>, T::Error> {
122        let key = key.as_key();
123        self.try_value_by(key.section_name, key.subsection_name, key.value_name)
124    }
125
126    /// Like [`value_by()`](File::value_by()), but returning an `None` if the value wasn't found at `section[.subsection].value_name`
127    pub fn try_value_by<T: TryFrom<BString>>(
128        &self,
129        section_name: impl AsRef<str>,
130        subsection_name: impl AsBStrOpt,
131        value_name: impl AsRef<str>,
132    ) -> Result<Option<T>, T::Error> {
133        self.raw_value_by(section_name, subsection_name, value_name)
134            .ok()
135            .map(T::try_from)
136            .transpose()
137    }
138
139    /// Returns all interpreted values given a section, an optional subsection
140    /// and value name.
141    ///
142    /// It's recommended to use one of the value types provide dby this crate
143    /// as they implement the conversion, but this function is flexible and
144    /// will accept any type that implements [`TryFrom<&BStr>`](TryFrom).
145    ///
146    /// Consider [`Self::value`] if you want to get a single value
147    /// (following last-one-wins resolution) instead.
148    ///
149    /// To access plain strings, use the [`strings()`](Self::strings()) method instead.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// # use gix_config::File;
155    /// # use gix_config::{Integer, Boolean};
156    /// # use std::convert::TryFrom;
157    /// # use bstr::ByteSlice;
158    /// let config = r#"
159    ///     [core]
160    ///         a = true
161    ///         c
162    ///     [core]
163    ///         a
164    ///         a = false
165    /// "#;
166    /// let git_config = gix_config::File::try_from(config).expect("valid config");
167    /// // You can either use the turbofish to determine the type...
168    /// let a_value = git_config.values::<Boolean>("core.a").expect("valid values");
169    /// assert_eq!(
170    ///     a_value,
171    ///     vec![
172    ///         Boolean(true),
173    ///         Boolean(false),
174    ///         Boolean(false),
175    ///     ]
176    /// );
177    /// // ... or explicitly declare the type to avoid the turbofish
178    /// let c_value: Vec<Boolean> = git_config.values("core.c")?;
179    /// assert_eq!(c_value, vec![Boolean(false)]);
180    /// # Ok::<(), gix_config::lookup::Error<gix_error::Exn<gix_error::Message>>>(())
181    /// ```
182    ///
183    /// [`value`]: crate::value
184    /// [`TryFrom`]: std::convert::TryFrom
185    pub fn values<T: TryFrom<BString>>(&self, key: impl AsKey) -> Result<Vec<T>, lookup::Error<T::Error>> {
186        self.raw_values(key)?
187            .into_iter()
188            .map(T::try_from)
189            .collect::<Result<Vec<_>, _>>()
190            .map_err(lookup::Error::FailedConversion)
191    }
192
193    /// Returns all interpreted values given a section, an optional subsection
194    /// and value name.
195    ///
196    /// It's recommended to use one of the value types provide dby this crate
197    /// as they implement the conversion, but this function is flexible and
198    /// will accept any type that implements [`TryFrom<&BStr>`](std::convert::TryFrom).
199    ///
200    /// Consider [`Self::value`] if you want to get a single value
201    /// (following last-one-wins resolution) instead.
202    ///
203    /// To access plain strings, use the [`strings()`](Self::strings()) method instead.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// # use gix_config::File;
209    /// # use gix_config::{Integer, Boolean};
210    /// # use std::convert::TryFrom;
211    /// # use bstr::ByteSlice;
212    /// let config = r#"
213    ///     [core]
214    ///         a = true
215    ///         c
216    ///     [core]
217    ///         a
218    ///         a = false
219    /// "#;
220    /// let git_config = gix_config::File::try_from(config).expect("valid config");
221    /// // You can either use the turbofish to determine the type...
222    /// let a_value = git_config.values_by::<Boolean>("core", None, "a").expect("valid values");
223    /// assert_eq!(
224    ///     a_value,
225    ///     vec![
226    ///         Boolean(true),
227    ///         Boolean(false),
228    ///         Boolean(false),
229    ///     ]
230    /// );
231    /// // ... or explicitly declare the type to avoid the turbofish
232    /// let c_value: Vec<Boolean> = git_config.values_by("core", None, "c")?;
233    /// assert_eq!(c_value, vec![Boolean(false)]);
234    /// # Ok::<(), gix_config::lookup::Error<gix_error::Exn<gix_error::Message>>>(())
235    /// ```
236    ///
237    /// [`value`]: crate::value
238    /// [`TryFrom`]: std::convert::TryFrom
239    pub fn values_by<T: TryFrom<BString>>(
240        &self,
241        section_name: impl AsRef<str>,
242        subsection_name: impl AsBStrOpt,
243        value_name: impl AsRef<str>,
244    ) -> Result<Vec<T>, lookup::Error<T::Error>> {
245        self.raw_values_by(section_name, subsection_name, value_name)?
246            .into_iter()
247            .map(T::try_from)
248            .collect::<Result<Vec<_>, _>>()
249            .map_err(lookup::Error::FailedConversion)
250    }
251
252    /// Returns all interpreted values and their containing sections given a `key`, in order of occurrence.
253    pub fn values_with_sections<T: TryFrom<BString>>(
254        &self,
255        key: impl AsKey,
256    ) -> Result<Vec<(T, file::SectionRef<'_>)>, lookup::Error<T::Error>> {
257        let key = key.as_key();
258        self.values_with_sections_by(key.section_name, key.subsection_name, key.value_name)
259    }
260
261    /// Returns all interpreted values and their containing sections given individual key components, in order of
262    /// occurrence.
263    pub fn values_with_sections_by<T: TryFrom<BString>>(
264        &self,
265        section_name: impl AsRef<str>,
266        subsection_name: impl AsBStrOpt,
267        value_name: impl AsRef<str>,
268    ) -> Result<Vec<(T, file::SectionRef<'_>)>, lookup::Error<T::Error>> {
269        self.raw_values_with_sections_by(section_name, subsection_name, value_name)?
270            .into_iter()
271            .map(|(value, section)| T::try_from(value).map(|value| (value, section)))
272            .collect::<Result<Vec<_>, _>>()
273            .map_err(lookup::Error::FailedConversion)
274    }
275
276    /// Returns the last found immutable section with a given `name` and optional `subsection_name`.
277    pub fn section(&self, name: impl AsRef<str>, subsection_name: impl AsBStrOpt) -> ExnResult<file::SectionRef<'_>> {
278        self.section_filter(name, subsection_name, |_| true)?
279            .ok_or_else(lookup::existing::section_missing)
280    }
281
282    /// Returns the last found immutable section with a given `section_key`, identifying the name and subsection name like `core`
283    /// or `remote.origin`.
284    pub fn section_by_key(&self, section_key: impl crate::AsBStr) -> ExnResult<file::SectionRef<'_>> {
285        let key = crate::parse::section::unvalidated::KeyRef::parse(section_key.as_bstr())
286            .ok_or_else(lookup::existing::key_missing)?;
287        self.section(key.section_name, key.subsection_name)
288    }
289
290    /// Returns the last found immutable section with a given `name` and optional `subsection_name`, that matches `filter`.
291    ///
292    /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)`
293    /// is returned.
294    pub fn section_filter(
295        &self,
296        name: impl AsRef<str>,
297        subsection_name: impl AsBStrOpt,
298        mut filter: impl FnMut(&Metadata) -> bool,
299    ) -> ExnResult<Option<file::SectionRef<'_>>> {
300        Ok(self
301            .section_ids_by_name_and_subname(name.as_ref(), subsection_name.as_bstr_opt())?
302            .rev()
303            .find_map({
304                let sections = &self.sections;
305                move |id| {
306                    let s = &sections[&id];
307                    filter(&s.meta).then_some(file::SectionRef::from_data(s, &self.backing))
308                }
309            }))
310    }
311
312    /// Like [`section_filter()`](File::section_filter()), but identifies the section with `section_key` like `core` or `remote.origin`.
313    pub fn section_filter_by_key(
314        &self,
315        section_key: impl crate::AsBStr,
316        filter: impl FnMut(&Metadata) -> bool,
317    ) -> ExnResult<Option<file::SectionRef<'_>>> {
318        let key = crate::parse::section::unvalidated::KeyRef::parse(section_key.as_bstr())
319            .ok_or_else(lookup::existing::key_missing)?;
320        self.section_filter(key.section_name, key.subsection_name, filter)
321    }
322
323    /// Gets all sections that match the provided `name`, ignoring any subsections.
324    ///
325    /// # Examples
326    ///
327    /// Provided the following config:
328    ///
329    /// ```text
330    /// [core]
331    ///     a = b
332    /// [core ""]
333    ///     c = d
334    /// [core "apple"]
335    ///     e = f
336    /// ```
337    ///
338    /// Calling this method will yield all sections:
339    ///
340    /// ```
341    /// # use gix_config::File;
342    /// # use gix_config::{Integer, Boolean};
343    /// # use std::convert::TryFrom;
344    /// let config = r#"
345    ///     [core]
346    ///         a = b
347    ///     [core ""]
348    ///         c = d
349    ///     [core "apple"]
350    ///         e = f
351    /// "#;
352    /// let git_config = gix_config::File::try_from(config)?;
353    /// assert_eq!(git_config.sections_by_name("core").map_or(0, |s|s.count()), 3);
354    /// # Ok::<(), Box<dyn std::error::Error>>(())
355    /// ```
356    #[must_use]
357    pub fn sections_by_name(&self, name: impl AsRef<str>) -> Option<impl Iterator<Item = file::SectionRef<'_>> + '_> {
358        self.section_ids_by_name(name.as_ref()).ok().map(move |ids| {
359            ids.map(move |id| {
360                file::SectionRef::from_data(
361                    self.sections
362                        .get(&id)
363                        .expect("section doesn't have id from from lookup"),
364                    &self.backing,
365                )
366            })
367        })
368    }
369
370    /// Similar to [`sections_by_name()`](Self::sections_by_name()), but returns an identifier for this section as well to allow
371    /// referring to it unambiguously even in the light of deletions.
372    #[must_use]
373    pub fn sections_and_ids_by_name(
374        &self,
375        name: impl AsRef<str>,
376    ) -> Option<impl Iterator<Item = (file::SectionRef<'_>, SectionId)> + '_> {
377        self.section_ids_by_name(name.as_ref()).ok().map(move |ids| {
378            ids.map(move |id| {
379                (
380                    file::SectionRef::from_data(
381                        self.sections
382                            .get(&id)
383                            .expect("section doesn't have id from from lookup"),
384                        &self.backing,
385                    ),
386                    id,
387                )
388            })
389        })
390    }
391
392    /// Gets all sections that match the provided `name`, ignoring any subsections, and pass the `filter`.
393    #[must_use]
394    pub fn sections_by_name_and_filter<'a>(
395        &'a self,
396        name: impl AsRef<str>,
397        mut filter: impl FnMut(&Metadata) -> bool + 'a,
398    ) -> Option<impl Iterator<Item = file::SectionRef<'a>> + 'a> {
399        self.section_ids_by_name(name.as_ref()).ok().map(move |ids| {
400            ids.filter_map(move |id| {
401                let s = self
402                    .sections
403                    .get(&id)
404                    .expect("section doesn't have id from from lookup");
405                filter(&s.meta).then_some(file::SectionRef::from_data(s, &self.backing))
406            })
407        })
408    }
409
410    /// Returns the number of values in the config, no matter in which section.
411    ///
412    /// For example, a config with multiple empty sections will return 0.
413    /// This ignores any comments.
414    #[must_use]
415    pub fn num_values(&self) -> usize {
416        self.sections.values().map(|section| section.num_values()).sum()
417    }
418
419    /// Returns if there are no entries in the config. This will return true
420    /// if there are only empty sections, with whitespace and comments not being considered
421    /// void.
422    #[must_use]
423    pub fn is_void(&self) -> bool {
424        self.sections.values().all(|s| s.is_void())
425    }
426
427    /// Return this file's metadata, typically set when it was first created to indicate its origins.
428    ///
429    /// It will be used in all newly created sections to identify them.
430    /// Change it with [`File::set_meta()`].
431    pub fn meta(&self) -> &Metadata {
432        &self.meta
433    }
434
435    /// Change the origin of this instance to be the given `meta`data.
436    ///
437    /// This is useful to control what origin about-to-be-added sections receive.
438    pub fn set_meta(&mut self, meta: impl Into<OwnShared<Metadata>>) -> &mut Self {
439        self.meta = meta.into();
440        self
441    }
442
443    /// Similar to [`meta()`](File::meta()), but with shared ownership.
444    pub fn meta_owned(&self) -> OwnShared<Metadata> {
445        OwnShared::clone(&self.meta)
446    }
447
448    /// Return an iterator over all sections, in order of occurrence in the file itself.
449    pub fn sections(&self) -> impl Iterator<Item = file::SectionRef<'_>> + '_ {
450        self.section_order
451            .iter()
452            .map(|id| file::SectionRef::from_data(&self.sections[id], &self.backing))
453    }
454
455    /// Return an iterator over all sections and their ids, in order of occurrence in the file itself.
456    pub fn sections_and_ids(&self) -> impl Iterator<Item = (file::SectionRef<'_>, SectionId)> + '_ {
457        self.section_order
458            .iter()
459            .map(|id| (file::SectionRef::from_data(&self.sections[id], &self.backing), *id))
460    }
461
462    /// Return an iterator over all section ids, in order of occurrence in the file itself.
463    pub fn section_ids(&mut self) -> impl Iterator<Item = SectionId> + '_ {
464        self.section_order.iter().copied()
465    }
466
467    /// Return an iterator over all sections along with non-section events that are placed right after them,
468    /// in order of occurrence in the file itself.
469    ///
470    /// This allows to reproduce the look of sections perfectly when serializing them with
471    /// [`write_to()`](file::SectionRef::write_to()).
472    pub fn sections_and_postmatter(&self) -> impl Iterator<Item = (file::SectionRef<'_>, Vec<EventRef<'_>>)> {
473        self.section_order.iter().map(move |id| {
474            let s = file::SectionRef::from_data(&self.sections[id], &self.backing);
475            let pm: Vec<_> = self
476                .frontmatter_post_section
477                .get(id)
478                .map(|events| events.iter().map(|event| event.as_ref_in(&self.backing)).collect())
479                .unwrap_or_default();
480            (s, pm)
481        })
482    }
483
484    /// Return all events which are in front of the first of our sections, or `None` if there are none.
485    pub fn frontmatter(&self) -> Option<impl Iterator<Item = EventRef<'_>> + '_> {
486        (!self.frontmatter_events.is_empty()).then(|| {
487            self.frontmatter_events
488                .iter()
489                .map(move |event| event.as_ref_in(&self.backing))
490        })
491    }
492
493    /// Return the newline characters that have been detected in this config file or the default ones
494    /// for the current platform.
495    ///
496    /// Note that the first found newline is the one we use in the assumption of consistency.
497    pub fn detect_newline_style(&self) -> &BStr {
498        self.frontmatter_events
499            .iter()
500            .find_map(|event| extract_newline(event, &self.backing))
501            .or_else(|| {
502                self.sections()
503                    .find_map(|section| section.body_data().detect_newline_style_in(&self.backing))
504            })
505            .unwrap_or_else(|| platform_newline())
506    }
507
508    pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> {
509        self.detect_newline_style().as_bytes().into()
510    }
511}