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 = §ions[&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}