gix_config/file/mutable/
mod.rs

1use std::borrow::Cow;
2
3use bstr::{BStr, BString, ByteSlice, ByteVec};
4
5use crate::{file, parse::Event};
6
7pub(crate) mod multi_value;
8pub(crate) mod section;
9pub(crate) mod value;
10
11fn escape_value(value: &BStr) -> BString {
12    let starts_with_whitespace = value.first().is_some_and(u8::is_ascii_whitespace);
13    let ends_with_whitespace = value
14        .get(value.len().saturating_sub(1))
15        .is_some_and(u8::is_ascii_whitespace);
16    let contains_comment_indicators = value.find_byteset(b";#").is_some();
17    let quote = starts_with_whitespace || ends_with_whitespace || contains_comment_indicators;
18
19    let mut buf: BString = Vec::with_capacity(value.len()).into();
20    if quote {
21        buf.push(b'"');
22    }
23
24    for b in value.iter().copied() {
25        match b {
26            b'\n' => buf.push_str(r"\n"),
27            b'\t' => buf.push_str(r"\t"),
28            b'"' => buf.push_str(r#"\""#),
29            b'\\' => buf.push_str(r"\\"),
30            _ => buf.push(b),
31        }
32    }
33
34    if quote {
35        buf.push(b'"');
36    }
37    buf
38}
39
40#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
41struct Whitespace<'a> {
42    pre_key: Option<Cow<'a, BStr>>,
43    pre_sep: Option<Cow<'a, BStr>>,
44    post_sep: Option<Cow<'a, BStr>>,
45}
46
47impl Default for Whitespace<'_> {
48    fn default() -> Self {
49        Whitespace {
50            pre_key: Some(b"\t".as_bstr().into()),
51            pre_sep: Some(b" ".as_bstr().into()),
52            post_sep: Some(b" ".as_bstr().into()),
53        }
54    }
55}
56
57impl<'a> Whitespace<'a> {
58    fn key_value_separators(&self) -> Vec<Event<'a>> {
59        let mut out = Vec::with_capacity(3);
60        if let Some(ws) = &self.pre_sep {
61            out.push(Event::Whitespace(ws.clone()));
62        }
63        out.push(Event::KeyValueSeparator);
64        if let Some(ws) = &self.post_sep {
65            out.push(Event::Whitespace(ws.clone()));
66        }
67        out
68    }
69
70    fn from_body(s: &file::section::Body<'a>) -> Self {
71        let key_pos =
72            s.0.iter()
73                .enumerate()
74                .find_map(|(idx, e)| matches!(e, Event::SectionValueName(_)).then(|| idx));
75        key_pos
76            .map(|key_pos| {
77                let pre_key = s.0[..key_pos].iter().next_back().and_then(|e| match e {
78                    Event::Whitespace(s) => Some(s.clone()),
79                    _ => None,
80                });
81                let from_key = &s.0[key_pos..];
82                let (pre_sep, post_sep) = from_key
83                    .iter()
84                    .enumerate()
85                    .find_map(|(idx, e)| matches!(e, Event::KeyValueSeparator).then(|| idx))
86                    .map(|sep_pos| {
87                        (
88                            from_key.get(sep_pos - 1).and_then(|e| match e {
89                                Event::Whitespace(ws) => Some(ws.clone()),
90                                _ => None,
91                            }),
92                            from_key.get(sep_pos + 1).and_then(|e| match e {
93                                Event::Whitespace(ws) => Some(ws.clone()),
94                                _ => None,
95                            }),
96                        )
97                    })
98                    .unwrap_or_default();
99                Whitespace {
100                    pre_key,
101                    pre_sep,
102                    post_sep,
103                }
104            })
105            .unwrap_or_default()
106    }
107}