1use alloc::string::{String, ToString};
24use alloc::vec::Vec;
25
26use crate::de::NsonDeserialize;
27use crate::error::{Error, Result};
28use crate::formats::tree;
29use crate::formats::Format;
30use crate::map::Map;
31use crate::number::Number;
32use crate::ser::NsonSerialize;
33use crate::value::Value;
34use crate::write::Write;
35
36#[derive(Clone, Copy, Debug)]
38pub struct Ini;
39
40pub type IniDecoder<'de> = tree::TreeDecoder<'de>;
43
44impl Format for Ini {
45 const NAME: &'static str = "ini";
46 const MIME: &'static str = "text/plain";
47 const EXTENSIONS: &'static [&'static str] = &["ini", "cfg", "conf"];
48 const BINARY: bool = false;
49
50 fn encode<T: NsonSerialize + ?Sized>(self, value: &T) -> Result<Vec<u8>> {
51 let mut encoder = IniEncoder::new(Vec::new());
52 let mut checked = crate::ser::CheckedEncoder::new(&mut encoder);
53 T::nextencode(value, &mut checked)?;
54 checked.finish()?;
55 encoder.finish()
56 }
57
58 fn decode<'de, T: NsonDeserialize<'de>>(self, input: &'de [u8]) -> Result<T> {
59 let value = parse_ini(input)?;
60 let mut decoder = tree::TreeDecoder::new(tree::value_to_tokens(&value)?);
61 let out = T::nextdecode(&mut decoder)?;
62 decoder.end()?;
63 Ok(out)
64 }
65}
66
67pub struct IniEncoder<W: Write> {
73 writer: W,
74 collector: tree::CollectEncoder,
75}
76
77impl<W: Write> IniEncoder<W> {
78 pub fn new(writer: W) -> Self {
80 Self {
81 writer,
82 collector: tree::CollectEncoder::new(),
83 }
84 }
85
86 pub fn finish(mut self) -> Result<W> {
88 let root = self.collector.take_root()?;
89 let mut out = Vec::with_capacity(256);
90 emit_ini(&root, &mut out)?;
91 self.writer.write_all(&out)?;
92 self.writer.flush()?;
93 Ok(self.writer)
94 }
95}
96
97impl<W: Write> crate::ser::FormatEncoder for IniEncoder<W> {
98 type Error = crate::error::Error;
99
100 fn begin_array(&mut self) -> Result<(), Self::Error> {
101 self.collector.begin_array()
102 }
103 fn separator(&mut self) -> Result<(), Self::Error> {
104 self.collector.separator()
105 }
106 fn end_array(&mut self) -> Result<(), Self::Error> {
107 self.collector.end_array()
108 }
109 fn begin_object(&mut self) -> Result<(), Self::Error> {
110 self.collector.begin_object()
111 }
112 fn key(&mut self, key: &str) -> Result<(), Self::Error> {
113 self.collector.key(key)
114 }
115 fn end_object(&mut self) -> Result<(), Self::Error> {
116 self.collector.end_object()
117 }
118 fn write_null(&mut self) -> Result<(), Self::Error> {
119 self.collector.write_null()
120 }
121 fn write_bool(&mut self, value: bool) -> Result<(), Self::Error> {
122 self.collector.write_bool(value)
123 }
124 fn write_str(&mut self, value: &str) -> Result<(), Self::Error> {
125 self.collector.write_str(value)
126 }
127 fn write_char(&mut self, value: char) -> Result<(), Self::Error> {
128 self.collector.write_char(value)
129 }
130 fn write_number(&mut self, value: &Number) -> Result<(), Self::Error> {
131 self.collector.write_number(value)
132 }
133 fn write_i64(&mut self, value: i64) -> Result<(), Self::Error> {
134 self.collector.write_i64(value)
135 }
136 fn write_u64(&mut self, value: u64) -> Result<(), Self::Error> {
137 self.collector.write_u64(value)
138 }
139 fn write_i128(&mut self, value: i128) -> Result<(), Self::Error> {
140 self.collector.write_i128(value)
141 }
142 fn write_u128(&mut self, value: u128) -> Result<(), Self::Error> {
143 self.collector.write_u128(value)
144 }
145 fn write_f64(&mut self, value: f64) -> Result<(), Self::Error> {
146 self.collector.write_f64(value)
147 }
148 fn write_f32(&mut self, value: f32) -> Result<(), Self::Error> {
149 self.collector.write_f32(value)
150 }
151 fn write_none(&mut self) -> Result<(), Self::Error> {
152 self.collector.write_none()
153 }
154 fn is_human_readable(&self) -> bool {
155 true
156 }
157}
158
159fn scalar_text(v: &Value) -> Result<String> {
161 match v {
162 Value::String(s) => Ok(s.clone()),
163 Value::Number(n) => tree::number_string(n),
164 Value::Bool(b) => Ok(b.to_string()),
165 other => Err(Error::custom(alloc::format!(
166 "ini: {other:?} is not representable as a scalar value"
167 ))),
168 }
169}
170
171fn looks_non_string(value: &str) -> bool {
176 if matches!(value, "true" | "false") {
177 return true;
178 }
179 if parse_ini_int(value).is_ok() {
180 return true;
181 }
182 value.contains(['.', 'e', 'E'])
183 && value
184 .bytes()
185 .all(|b| b.is_ascii_digit() || matches!(b, b'.' | b'e' | b'E' | b'+' | b'-'))
186 && tree::parse_float(value).is_some()
187}
188
189fn emit_value(out: &mut Vec<u8>, value: &str, from_string: bool) {
193 let needs_quotes = value.contains(['=', ';', '#', '\n', '\r'])
194 || value.starts_with(char::is_whitespace)
195 || value.ends_with(char::is_whitespace)
196 || (from_string && looks_non_string(value));
197 if needs_quotes {
198 out.push(b'"');
199 for &b in value.as_bytes() {
200 match b {
201 b'\\' => out.extend_from_slice(b"\\\\"),
202 b'"' => out.extend_from_slice(b"\\\""),
203 b'\n' => out.extend_from_slice(b"\\n"),
204 b'\t' => out.extend_from_slice(b"\\t"),
205 b'\r' => out.extend_from_slice(b"\\r"),
206 other => out.push(other),
207 }
208 }
209 out.push(b'"');
210 } else {
211 out.extend_from_slice(value.as_bytes());
212 }
213}
214
215fn emit_ini(root: &Value, out: &mut Vec<u8>) -> Result<()> {
217 let map = root
218 .as_object()
219 .ok_or_else(|| Error::custom("ini: root must be an object (a table)"))?;
220 for (key, value) in map.iter() {
222 if matches!(value, Value::Object(_)) {
223 continue;
224 }
225 out.extend_from_slice(key.as_bytes());
226 out.extend_from_slice(b" = ");
227 emit_value(out, &scalar_text(value)?, matches!(value, Value::String(_)));
228 out.push(b'\n');
229 }
230 for (key, value) in map.iter() {
232 let Value::Object(section) = value else {
233 continue;
234 };
235 out.push(b'[');
236 out.extend_from_slice(key.as_bytes());
237 out.push(b']');
238 out.push(b'\n');
239 for (skey, svalue) in section.iter() {
240 out.extend_from_slice(skey.as_bytes());
241 out.extend_from_slice(b" = ");
242 emit_value(
243 out,
244 &scalar_text(svalue)?,
245 matches!(svalue, Value::String(_)),
246 );
247 out.push(b'\n');
248 }
249 out.push(b'\n');
250 }
251 Ok(())
252}
253
254fn strip_comment(line: &[u8]) -> &[u8] {
260 let mut in_single = false;
261 let mut in_double = false;
262 for (i, &b) in line.iter().enumerate() {
263 match b {
264 b'\'' if !in_double => in_single = !in_single,
265 b'"' if !in_single => in_double = !in_double,
266 b';' | b'#' if !in_single && !in_double => return &line[..i],
267 _ => {}
268 }
269 }
270 line
271}
272
273fn trim(mut line: &[u8]) -> &[u8] {
274 while let Some((first, rest)) = line.split_first() {
275 if first.is_ascii_whitespace() {
276 line = rest;
277 } else {
278 break;
279 }
280 }
281 while let Some((last, rest)) = line.split_last() {
282 if last.is_ascii_whitespace() {
283 line = rest;
284 } else {
285 break;
286 }
287 }
288 line
289}
290
291fn parse_quoted(raw: &[u8], quote: u8) -> Result<String> {
295 let mut out = Vec::with_capacity(raw.len());
296 if quote == b'\'' {
297 return String::from_utf8(raw.to_vec())
298 .map_err(|_| Error::custom("ini: invalid utf-8 in literal string"));
299 }
300 let mut i = 0;
301 while i < raw.len() {
302 let b = raw[i];
303 if b == b'\\' {
304 i += 1;
305 let esc = *raw
306 .get(i)
307 .ok_or_else(|| Error::custom("ini: truncated escape"))?;
308 match esc {
309 b'\\' => out.push(b'\\'),
310 b'"' => out.push(b'"'),
311 b'n' => out.push(b'\n'),
312 b't' => out.push(b'\t'),
313 b'r' => out.push(b'\r'),
314 other => {
315 return Err(Error::custom(alloc::format!(
316 "ini: unsupported escape \\{}",
317 other as char
318 )));
319 }
320 }
321 i += 1;
322 } else {
323 out.push(b);
324 i += 1;
325 }
326 }
327 String::from_utf8(out).map_err(|_| Error::custom("ini: invalid utf-8 in value"))
328}
329
330fn classify_value(raw: &[u8]) -> Value {
339 let Ok(text) = core::str::from_utf8(raw) else {
340 return Value::String(String::from_utf8_lossy(raw).into_owned());
341 };
342 match text {
343 "true" => return Value::Bool(true),
344 "false" => return Value::Bool(false),
345 _ => {}
346 }
347 if let Ok(n) = parse_ini_int(text) {
349 return Value::Number(n);
350 }
351 if text.contains(['.', 'e', 'E'])
353 && text
354 .bytes()
355 .all(|b| b.is_ascii_digit() || matches!(b, b'.' | b'e' | b'E' | b'+' | b'-'))
356 {
357 if let Some(v) = tree::parse_float(text) {
358 return Value::Number(Number::F64(v));
359 }
360 }
361 Value::String(text.to_string())
362}
363
364fn parse_ini_int(text: &str) -> Result<Number> {
366 if text.is_empty() || !text.bytes().all(|b| b.is_ascii_digit() || b == b'-') {
367 return Err(Error::custom("ini: not an integer"));
368 }
369 let (negative, body) = match text.strip_prefix('-') {
370 Some(rest) => (true, rest),
371 None => (false, text),
372 };
373 if body.is_empty() || !body.bytes().all(|b| b.is_ascii_digit()) {
374 return Err(Error::custom("ini: not an integer"));
375 }
376 let magnitude: u128 = body
377 .parse()
378 .map_err(|_| Error::custom("ini: integer overflow"))?;
379 if negative {
380 if magnitude == (i64::MAX as u128) + 1 {
381 Ok(Number::I64(i64::MIN))
382 } else if magnitude <= i64::MAX as u128 {
383 Ok(Number::I64(-(magnitude as i64)))
384 } else {
385 Ok(Number::I128(-(magnitude as i128)))
386 }
387 } else if magnitude <= u64::MAX as u128 {
388 Ok(Number::U64(magnitude as u64))
389 } else {
390 Ok(Number::U128(magnitude))
391 }
392}
393
394fn parse_pair(line: &[u8]) -> Result<(String, Value)> {
396 let eq = line
397 .iter()
398 .position(|b| *b == b'=')
399 .ok_or_else(|| Error::custom("ini: expected `key = value`"))?;
400 let key = trim(&line[..eq]);
401 let key = core::str::from_utf8(key).map_err(|_| Error::custom("ini: invalid utf-8 in key"))?;
402 let key = key.trim().to_string();
403 if key.is_empty() {
404 return Err(Error::custom("ini: empty key"));
405 }
406 let value = trim(&line[eq + 1..]);
407 let value = if value.len() >= 2
409 && ((value[0] == b'"' && value[value.len() - 1] == b'"')
410 || (value[0] == b'\'' && value[value.len() - 1] == b'\''))
411 {
412 Value::String(parse_quoted(&value[1..value.len() - 1], value[0])?)
413 } else {
414 classify_value(value)
415 };
416 Ok((key, value))
417}
418
419fn parse_ini(input: &[u8]) -> Result<Value> {
424 let mut root: Map = Map::new();
425 let mut current_name: Option<String> = None;
426 let mut current: Map = Map::new();
427 let mut lines = input.split(|b| *b == b'\n');
428 for line in lines.by_ref() {
429 let line = strip_comment(line);
430 let line = trim(line);
431 if line.is_empty() {
432 continue;
433 }
434 if line[0] == b'[' {
435 if let Some(name) = current_name.take() {
437 match root.get_mut(&name) {
438 Some(Value::Object(existing)) => {
439 for (k, v) in core::mem::take(&mut current) {
440 existing.insert(k, v);
441 }
442 }
443 _ => {
444 root.insert(name, Value::Object(core::mem::take(&mut current)));
445 }
446 }
447 }
448 let close = line
449 .iter()
450 .rposition(|b| *b == b']')
451 .ok_or_else(|| Error::custom("ini: unterminated section header"))?;
452 let name = trim(&line[1..close]);
453 let name = core::str::from_utf8(name)
454 .map_err(|_| Error::custom("ini: invalid utf-8 in section"))?;
455 let name = name.trim();
456 if name.is_empty() {
457 return Err(Error::custom("ini: empty section name"));
458 }
459 current_name = Some(name.to_string());
460 continue;
461 }
462 let (key, value) = parse_pair(line)?;
463 match ¤t_name {
464 None => {
465 if root.insert(key, value).is_some() {
466 return Err(Error::custom("ini: duplicate key in global section"));
467 }
468 }
469 Some(_) => {
470 if current.insert(key, value).is_some() {
471 return Err(Error::custom("ini: duplicate key in section"));
472 }
473 }
474 }
475 }
476 if let Some(name) = current_name {
478 match root.get_mut(&name) {
479 Some(Value::Object(existing)) => {
480 for (k, v) in current {
481 existing.insert(k, v);
482 }
483 }
484 _ => {
485 root.insert(name, Value::Object(current));
486 }
487 }
488 }
489 Ok(Value::Object(root))
490}