Skip to main content

gix_config/file/section/
mod.rs

1use bstr::{BStr, BString, ByteSlice};
2use smallvec::SmallVec;
3
4use crate::{
5    file,
6    file::{IntoBStringOpt, Metadata, Section, SectionData, SectionMut, SectionRef},
7    parse,
8    parse::{Event, section},
9};
10
11pub(crate) mod body;
12pub(crate) use body::BodyData;
13pub use body::{BodyRef, BodyRefIter};
14use gix_features::threading::OwnShared;
15
16use crate::file::{SectionId, write::platform_newline};
17
18/// Errors related to changing values in a section.
19pub mod value {
20    /// The error returned when adding or changing a value in a section.
21    #[derive(Debug, thiserror::Error)]
22    #[allow(missing_docs)]
23    pub enum Error {
24        #[error(transparent)]
25        ValueName(#[from] crate::parse::section::value_name::Error),
26        #[error(transparent)]
27        Span(#[from] crate::parse::span::Error),
28    }
29}
30
31impl std::ops::Deref for SectionData {
32    type Target = BodyData;
33
34    fn deref(&self) -> &Self::Target {
35        &self.body
36    }
37}
38
39/// A view of a section header whose bytes are owned by the containing [`File`][crate::File].
40#[derive(Copy, Clone, Debug)]
41pub struct HeaderRef<'a> {
42    pub(crate) header: &'a section::HeaderData,
43    pub(crate) backing: &'a [u8],
44}
45
46impl<'a> HeaderRef<'a> {
47    /// Return true if this is a header like `[legacy.subsection]`, or false otherwise.
48    pub fn is_legacy(&self) -> bool {
49        self.header
50            .separator
51            .as_ref()
52            .is_some_and(|separator| separator.as_slice_in(self.backing) == b".")
53    }
54
55    /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`.
56    pub fn subsection_name(&self) -> Option<&'a BStr> {
57        self.header
58            .subsection_name
59            .as_ref()
60            .map(|subsection_name| subsection_name.value_in(self.backing))
61    }
62
63    /// Return the name of the header, like "remote" in `[remote "origin"]`.
64    pub fn name(&self) -> &'a BStr {
65        self.header.name.as_bstr_in(self.backing)
66    }
67
68    /// Serialize this header view into a `BString` for convenience.
69    #[must_use]
70    pub fn to_bstring(&self) -> BString {
71        let mut buf = Vec::new();
72        self.header
73            .write_to_in(self.backing, &mut buf)
74            .expect("io error impossible");
75        buf.into()
76    }
77}
78
79/// Instantiation and conversion
80impl<'file> SectionRef<'file> {
81    pub(crate) fn from_data(data: &'file SectionData, backing: &'file [u8]) -> Self {
82        SectionRef { data, backing }
83    }
84}
85
86impl Section {
87    /// Create an owned section with an empty body.
88    pub fn new(
89        name: impl AsRef<str>,
90        subsection: impl IntoBStringOpt,
91        meta: impl Into<OwnShared<file::Metadata>>,
92    ) -> Result<Self, parse::section::header::Error> {
93        let mut backing = Vec::new();
94        let data = SectionData::new(name, subsection.into_bstring_opt(), meta, &mut backing)?;
95        Ok(Section { backing, data })
96    }
97
98    /// Return a read-only view of this section.
99    pub fn to_ref(&self) -> SectionRef<'_> {
100        SectionRef::from_data(&self.data, &self.backing)
101    }
102
103    /// Return a mutable view of this section.
104    pub fn to_mut(&mut self) -> SectionMut<'_> {
105        let newline = self
106            .data
107            .body
108            .detect_newline_style_in(&self.backing)
109            .unwrap_or_else(|| platform_newline())
110            .as_bytes()
111            .into();
112        SectionMut::new(&mut self.data, &mut self.backing, None, newline)
113    }
114
115    pub(crate) fn from_data(data: &SectionData, source: &[u8]) -> Self {
116        let mut backing = Vec::new();
117        let data = data
118            .copy_to_backing_in(source, &mut backing)
119            .expect("copying into an empty buffer cannot exceed the source buffer's span limit");
120        Section { backing, data }
121    }
122
123    pub(crate) fn into_data(self, target: &mut Vec<u8>) -> Result<SectionData, parse::span::Error> {
124        self.data.copy_to_backing_in(&self.backing, target)
125    }
126}
127
128impl SectionData {
129    /// Create a new section with the given `name` and optional, `subsection`, `meta`-data and an empty body.
130    pub(crate) fn new(
131        name: impl AsRef<str>,
132        subsection: impl Into<Option<BString>>,
133        meta: impl Into<OwnShared<file::Metadata>>,
134        backing: &mut Vec<u8>,
135    ) -> Result<Self, parse::section::header::Error> {
136        Ok(SectionData {
137            header: parse::section::HeaderData::new_in(name, subsection, backing)?,
138            body: Default::default(),
139            meta: meta.into(),
140            id: SectionId::default(),
141        })
142    }
143
144    /// Returns a mutable version of this section for adjustment of values.
145    pub(crate) fn to_mut<'a>(
146        &'a mut self,
147        backing: &'a mut Vec<u8>,
148        lookup: file::mutable::section::LookupMut<'a>,
149        newline: SmallVec<[u8; 2]>,
150    ) -> SectionMut<'a> {
151        SectionMut::new(self, backing, Some(lookup), newline)
152    }
153
154    pub(crate) fn meta(&self) -> &Metadata {
155        &self.meta
156    }
157
158    pub(crate) fn copy_to_backing_in(&self, source: &[u8], target: &mut Vec<u8>) -> Result<Self, parse::span::Error> {
159        Ok(SectionData {
160            header: self.header.copy_to_backing_in(source, target)?,
161            body: self.body.copy_to_backing_in(source, target)?,
162            meta: OwnShared::clone(&self.meta),
163            id: self.id,
164        })
165    }
166}
167
168/// Access
169impl<'file> SectionRef<'file> {
170    /// Copy this view into a self-contained owned section.
171    #[must_use]
172    pub fn to_owned(self) -> Section {
173        Section::from_data(self.data, self.backing)
174    }
175
176    /// Return our header.
177    pub fn header(&self) -> HeaderRef<'file> {
178        HeaderRef {
179            header: &self.data.header,
180            backing: self.backing,
181        }
182    }
183
184    /// Return the unique `id` of the section, for use with the `*_by_id()` family of methods
185    /// in [`gix_config::File`][crate::File].
186    pub fn id(&self) -> SectionId {
187        self.data.id
188    }
189
190    /// Return our body, containing all value names and values.
191    pub fn body(&self) -> BodyRef<'file> {
192        BodyRef {
193            body: &self.data.body,
194            backing: self.backing,
195        }
196    }
197
198    pub(crate) fn body_data(&self) -> &'file BodyData {
199        &self.data.body
200    }
201
202    /// Serialize this type into a `BString` for convenience.
203    ///
204    /// Note that `to_string()` can also be used, but might not be lossless.
205    #[must_use]
206    pub fn to_bstring(&self) -> BString {
207        let mut buf = Vec::new();
208        self.write_to(&mut buf).expect("io error impossible");
209        buf.into()
210    }
211
212    /// Stream ourselves to the given `out`, in order to reproduce this section mostly losslessly
213    /// as it was parsed.
214    pub fn write_to(&self, mut out: &mut dyn std::io::Write) -> std::io::Result<()> {
215        let nl = self
216            .body_data()
217            .detect_newline_style_in(self.backing)
218            .unwrap_or_else(|| platform_newline());
219        self.write_to_with_newline(&mut out, nl)
220    }
221
222    pub(crate) fn write_to_with_newline(&self, mut out: &mut dyn std::io::Write, nl: &BStr) -> std::io::Result<()> {
223        self.data.header.write_to_in(self.backing, &mut *out)?;
224
225        if self.body_data().0.is_empty() {
226            return Ok(());
227        }
228
229        if !self
230            .body_data()
231            .as_ref()
232            .iter()
233            .take_while(|e| !matches!(e, Event::SectionValueName(_)))
234            .any(|e| e.to_bstr_lossy_in(self.backing).contains_str(nl))
235        {
236            out.write_all(nl)?;
237        }
238
239        let mut saw_newline_after_value = true;
240        let mut in_key_value_pair = false;
241        for (idx, event) in self.body_data().as_ref().iter().enumerate() {
242            match event {
243                Event::SectionValueName(_) => {
244                    if !saw_newline_after_value {
245                        out.write_all(nl)?;
246                    }
247                    saw_newline_after_value = false;
248                    in_key_value_pair = true;
249                }
250                Event::Newline(_) if !in_key_value_pair => {
251                    saw_newline_after_value = true;
252                }
253                Event::Value(_) | Event::ValueDone(_) => {
254                    in_key_value_pair = false;
255                }
256                _ => {}
257            }
258            event.write_to_in(self.backing, &mut out)?;
259            if let Event::ValueNotDone(_) = event {
260                if self
261                    .body_data()
262                    .0
263                    .get(idx + 1)
264                    .filter(|e| matches!(e, Event::Newline(_)))
265                    .is_none()
266                {
267                    out.write_all(nl)?;
268                }
269            }
270        }
271        Ok(())
272    }
273
274    /// Return additional information about this sections origin.
275    pub fn meta(&self) -> &'file Metadata {
276        &self.data.meta
277    }
278
279    /// Retrieves the last matching value in this section with the given value name, if present.
280    #[must_use]
281    pub fn value(&self, value_name: impl AsRef<str>) -> Option<BString> {
282        self.data
283            .body
284            .value_implicit_in(self.backing, value_name.as_ref())
285            .flatten()
286    }
287
288    /// Retrieves the last matching value in this section, including implicit values.
289    #[must_use]
290    pub fn value_implicit(&self, value_name: &str) -> Option<Option<BString>> {
291        self.data.body.value_implicit_in(self.backing, value_name)
292    }
293
294    /// Retrieves all values that have the provided value name.
295    #[must_use]
296    pub fn values(&self, value_name: &str) -> Vec<BString> {
297        self.data.body.values_in(self.backing, value_name)
298    }
299
300    /// Returns an iterator visiting all value names in order.
301    pub fn value_names(&self) -> impl Iterator<Item = String> + '_ {
302        self.data.body.as_ref().iter().filter_map(move |e| match e {
303            Event::SectionValueName(k) => Some(
304                k.as_bstr_in(self.backing)
305                    .to_str()
306                    .expect("parsed value names are ASCII")
307                    .to_owned(),
308            ),
309            _ => None,
310        })
311    }
312
313    /// Returns true if the section contains the provided value name.
314    #[must_use]
315    pub fn contains_value_name(&self, value_name: &str) -> bool {
316        self.data.body.contains_value_name_in(self.backing, value_name)
317    }
318}