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