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
18pub mod value {
20 #[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#[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 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 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 pub fn name(&self) -> &'a BStr {
65 self.header.name.as_bstr_in(self.backing)
66 }
67
68 #[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
79impl<'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 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 pub fn to_ref(&self) -> SectionRef<'_> {
100 SectionRef::from_data(&self.data, &self.backing)
101 }
102
103 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 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 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
168impl<'file> SectionRef<'file> {
170 #[must_use]
172 pub fn to_owned(self) -> Section {
173 Section::from_data(self.data, self.backing)
174 }
175
176 pub fn header(&self) -> HeaderRef<'file> {
178 HeaderRef {
179 header: &self.data.header,
180 backing: self.backing,
181 }
182 }
183
184 pub fn id(&self) -> SectionId {
187 self.data.id
188 }
189
190 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 #[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 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 pub fn meta(&self) -> &'file Metadata {
276 &self.data.meta
277 }
278
279 #[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 #[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 #[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 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 #[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}