Skip to main content

gix_config/file/access/
read_only.rs

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