Skip to main content

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