gix_config/file/access/
read_only.rs

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