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