Skip to main content

gix_config/file/access/
comfort.rs

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