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