gix_config/file/access/
comfort.rs

1use std::borrow::Cow;
2
3use bstr::BStr;
4
5use crate::{file::Metadata, value, AsKey, File};
6
7/// Comfortable API for accessing values
8impl File<'_> {
9    /// Like [`string_by()`](File::string_by()), but suitable for statically known `key`s like `remote.origin.url`.
10    pub fn string(&self, key: impl AsKey) -> Option<Cow<'_, BStr>> {
11        self.string_filter(key, |_| true)
12    }
13
14    /// Like [`value()`](File::value()), but returning `None` if the string wasn't found.
15    ///
16    /// As strings perform no conversions, this will never fail.
17    pub fn string_by(
18        &self,
19        section_name: impl AsRef<str>,
20        subsection_name: Option<&BStr>,
21        value_name: impl AsRef<str>,
22    ) -> Option<Cow<'_, BStr>> {
23        self.string_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), |_| true)
24    }
25
26    /// Like [`string_filter_by()`](File::string_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
27    pub fn string_filter(&self, key: impl AsKey, filter: impl FnMut(&Metadata) -> bool) -> Option<Cow<'_, BStr>> {
28        let key = key.try_as_key()?;
29        self.raw_value_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
30            .ok()
31    }
32
33    /// Like [`string()`](File::string()), but the section containing the returned value must pass `filter` as well.
34    pub fn string_filter_by(
35        &self,
36        section_name: impl AsRef<str>,
37        subsection_name: Option<&BStr>,
38        value_name: impl AsRef<str>,
39        filter: impl FnMut(&Metadata) -> bool,
40    ) -> Option<Cow<'_, BStr>> {
41        self.raw_value_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), filter)
42            .ok()
43    }
44
45    /// Like [`path_by()`](File::path_by()), but suitable for statically known `key`s like `remote.origin.url`.
46    pub fn path(&self, key: impl AsKey) -> Option<crate::Path<'_>> {
47        self.path_filter(key, |_| true)
48    }
49
50    /// Like [`value()`](File::value()), but returning `None` if the path wasn't found.
51    ///
52    /// Note that this path is not vetted and should only point to resources which can't be used
53    /// to pose a security risk. Prefer using [`path_filter()`](File::path_filter()) instead.
54    ///
55    /// As paths perform no conversions, this will never fail.
56    pub fn path_by(
57        &self,
58        section_name: impl AsRef<str>,
59        subsection_name: Option<&BStr>,
60        value_name: impl AsRef<str>,
61    ) -> Option<crate::Path<'_>> {
62        self.path_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), |_| true)
63    }
64
65    /// Like [`path_filter_by()`](File::path_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
66    pub fn path_filter(&self, key: impl AsKey, filter: impl FnMut(&Metadata) -> bool) -> Option<crate::Path<'_>> {
67        let key = key.try_as_key()?;
68        self.path_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
69    }
70
71    /// Like [`path()`](File::path()), but the section containing the returned value must pass `filter` as well.
72    ///
73    /// This should be the preferred way of accessing paths as those from untrusted
74    /// locations can be
75    ///
76    /// As paths perform no conversions, this will never fail.
77    pub fn path_filter_by(
78        &self,
79        section_name: impl AsRef<str>,
80        subsection_name: Option<&BStr>,
81        value_name: impl AsRef<str>,
82        filter: impl FnMut(&Metadata) -> bool,
83    ) -> Option<crate::Path<'_>> {
84        self.raw_value_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), filter)
85            .ok()
86            .map(crate::Path::from)
87    }
88
89    /// Like [`boolean_by()`](File::boolean_by()), but suitable for statically known `key`s like `remote.origin.url`.
90    pub fn boolean(&self, key: impl AsKey) -> Option<Result<bool, value::Error>> {
91        self.boolean_filter(key, |_| true)
92    }
93
94    /// Like [`value()`](File::value()), but returning `None` if the boolean value wasn't found.
95    pub fn boolean_by(
96        &self,
97        section_name: impl AsRef<str>,
98        subsection_name: Option<&BStr>,
99        value_name: impl AsRef<str>,
100    ) -> Option<Result<bool, value::Error>> {
101        self.boolean_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), |_| true)
102    }
103
104    /// Like [`boolean_filter_by()`](File::boolean_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
105    pub fn boolean_filter(
106        &self,
107        key: impl AsKey,
108        filter: impl FnMut(&Metadata) -> bool,
109    ) -> Option<Result<bool, value::Error>> {
110        let key = key.try_as_key()?;
111        self.boolean_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
112    }
113
114    /// Like [`boolean_by()`](File::boolean_by()), but the section containing the returned value must pass `filter` as well.
115    pub fn boolean_filter_by(
116        &self,
117        section_name: impl AsRef<str>,
118        subsection_name: Option<&BStr>,
119        value_name: impl AsRef<str>,
120        mut filter: impl FnMut(&Metadata) -> bool,
121    ) -> Option<Result<bool, value::Error>> {
122        let section_name = section_name.as_ref();
123        let section_ids = self
124            .section_ids_by_name_and_subname(section_name, subsection_name)
125            .ok()?;
126        let key = value_name.as_ref();
127        for section_id in section_ids.rev() {
128            let section = self.sections.get(&section_id).expect("known section id");
129            if !filter(section.meta()) {
130                continue;
131            }
132            match section.value_implicit(key) {
133                Some(Some(v)) => return Some(crate::Boolean::try_from(v).map(Into::into)),
134                Some(None) => return Some(Ok(true)),
135                None => continue,
136            }
137        }
138        None
139    }
140
141    /// Like [`integer_by()`](File::integer_by()), but suitable for statically known `key`s like `remote.origin.url`.
142    pub fn integer(&self, key: impl AsKey) -> Option<Result<i64, value::Error>> {
143        self.integer_filter(key, |_| true)
144    }
145
146    /// Like [`value()`](File::value()), but returning an `Option` if the integer wasn't found.
147    pub fn integer_by(
148        &self,
149        section_name: impl AsRef<str>,
150        subsection_name: Option<&BStr>,
151        value_name: impl AsRef<str>,
152    ) -> Option<Result<i64, value::Error>> {
153        self.integer_filter_by(section_name, subsection_name, value_name, |_| true)
154    }
155
156    /// Like [`integer_filter_by()`](File::integer_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
157    pub fn integer_filter(
158        &self,
159        key: impl AsKey,
160        filter: impl FnMut(&Metadata) -> bool,
161    ) -> Option<Result<i64, value::Error>> {
162        let key = key.try_as_key()?;
163        self.integer_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
164    }
165
166    /// Like [`integer_by()`](File::integer_by()), but the section containing the returned value must pass `filter` as well.
167    pub fn integer_filter_by(
168        &self,
169        section_name: impl AsRef<str>,
170        subsection_name: Option<&BStr>,
171        value_name: impl AsRef<str>,
172        filter: impl FnMut(&Metadata) -> bool,
173    ) -> Option<Result<i64, value::Error>> {
174        let int = self
175            .raw_value_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), filter)
176            .ok()?;
177        Some(crate::Integer::try_from(int.as_ref()).and_then(|b| {
178            b.to_decimal()
179                .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned()))
180        }))
181    }
182
183    /// Like [`strings_by()`](File::strings_by()), but suitable for statically known `key`s like `remote.origin.url`.
184    pub fn strings(&self, key: impl AsKey) -> Option<Vec<Cow<'_, BStr>>> {
185        let key = key.try_as_key()?;
186        self.strings_by(key.section_name, key.subsection_name, key.value_name)
187    }
188
189    /// Similar to [`values_by(…)`](File::values_by()) but returning strings if at least one of them was found.
190    pub fn strings_by(
191        &self,
192        section_name: impl AsRef<str>,
193        subsection_name: Option<&BStr>,
194        value_name: impl AsRef<str>,
195    ) -> Option<Vec<Cow<'_, BStr>>> {
196        self.raw_values_by(section_name.as_ref(), subsection_name, value_name.as_ref())
197            .ok()
198    }
199
200    /// Like [`strings_filter_by()`](File::strings_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
201    pub fn strings_filter(&self, key: impl AsKey, filter: impl FnMut(&Metadata) -> bool) -> Option<Vec<Cow<'_, BStr>>> {
202        let key = key.try_as_key()?;
203        self.strings_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
204    }
205
206    /// Similar to [`strings_by(…)`](File::strings_by()), but all values are in sections that passed `filter`.
207    pub fn strings_filter_by(
208        &self,
209        section_name: impl AsRef<str>,
210        subsection_name: Option<&BStr>,
211        value_name: impl AsRef<str>,
212        filter: impl FnMut(&Metadata) -> bool,
213    ) -> Option<Vec<Cow<'_, BStr>>> {
214        self.raw_values_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), filter)
215            .ok()
216    }
217
218    /// Like [`integers()`](File::integers()), but suitable for statically known `key`s like `remote.origin.url`.
219    pub fn integers(&self, key: impl AsKey) -> Option<Result<Vec<i64>, value::Error>> {
220        self.integers_filter(key, |_| true)
221    }
222
223    /// Similar to [`values_by(…)`](File::values_by()) but returning integers if at least one of them was found
224    /// and if none of them overflows.
225    pub fn integers_by(
226        &self,
227        section_name: impl AsRef<str>,
228        subsection_name: Option<&BStr>,
229        value_name: impl AsRef<str>,
230    ) -> Option<Result<Vec<i64>, value::Error>> {
231        self.integers_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), |_| true)
232    }
233
234    /// Like [`integers_filter_by()`](File::integers_filter_by()), but suitable for statically known `key`s like `remote.origin.url`.
235    pub fn integers_filter(
236        &self,
237        key: impl AsKey,
238        filter: impl FnMut(&Metadata) -> bool,
239    ) -> Option<Result<Vec<i64>, value::Error>> {
240        let key = key.try_as_key()?;
241        self.integers_filter_by(key.section_name, key.subsection_name, key.value_name, filter)
242    }
243
244    /// Similar to [`integers_by(…)`](File::integers_by()) but all integers are in sections that passed `filter`
245    /// and that are not overflowing.
246    pub fn integers_filter_by(
247        &self,
248        section_name: impl AsRef<str>,
249        subsection_name: Option<&BStr>,
250        value_name: impl AsRef<str>,
251        filter: impl FnMut(&Metadata) -> bool,
252    ) -> Option<Result<Vec<i64>, value::Error>> {
253        self.raw_values_filter_by(section_name.as_ref(), subsection_name, value_name.as_ref(), filter)
254            .ok()
255            .map(|values| {
256                values
257                    .into_iter()
258                    .map(|v| {
259                        crate::Integer::try_from(v.as_ref()).and_then(|int| {
260                            int.to_decimal()
261                                .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned()))
262                        })
263                    })
264                    .collect()
265            })
266    }
267}