pom_parser/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![deny(missing_docs)]
4#![warn(clippy::semicolon_if_nothing_returned)]
5#![warn(clippy::redundant_closure_for_method_calls)]
6
7extern crate alloc;
8use alloc::borrow::ToOwned;
9use alloc::boxed::Box;
10use alloc::string::String;
11use alloc::sync::Arc;
12use alloc::vec::Vec;
13use alloc::{format, vec};
14use core::fmt;
15use core::iter::FusedIterator;
16use core::mem::take;
17use core::sync::atomic::{AtomicBool, Ordering};
18
19#[cfg(test)]
20mod tests;
21
22/// File and line information
23#[derive(Clone, Debug)]
24pub struct Location {
25	file: Arc<str>,
26	line: u64,
27}
28
29impl Location {
30	/// File name
31	#[must_use]
32	pub fn file(&self) -> &str {
33		&self.file
34	}
35
36	/// Line number
37	#[must_use]
38	pub fn line(&self) -> u64 {
39		self.line
40	}
41}
42
43impl fmt::Display for Location {
44	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45		write!(f, "{}:{}", self.file, self.line)
46	}
47}
48
49/// A string value, together with location information about where it is defined.
50#[derive(Debug)]
51struct Value {
52	value: Box<str>,
53	defined_at: Location,
54	read: AtomicBool,
55}
56
57/// A parsed POM configuration.
58#[derive(Clone, Debug, Default)]
59pub struct Configuration {
60	/// List of items in configuration, sorted by key.
61	items: Vec<(Box<str>, Arc<Value>)>,
62}
63
64impl fmt::Display for Configuration {
65	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66		for (key, value) in &self.items {
67			writeln!(f, "{key} = {value:?}")?;
68		}
69		Ok(())
70	}
71}
72
73/// A parsing error.
74#[non_exhaustive]
75#[derive(Debug)]
76pub enum Error {
77	/// I/O error
78	///
79	/// The first field is a description of what led to the error.
80	#[cfg(feature = "std")]
81	IO(Box<str>, std::io::Error),
82	/// Illegal character in POM file
83	///
84	/// Specifically, an ASCII control character other than LF, CR immediately followed by LF, and tab.
85	IllegalCharacter(Location, char),
86	/// Invalid UTF-8 in POM file
87	InvalidUtf8(Location),
88	/// Couldn't parse signed integer.
89	BadInt(Location, Box<str>),
90	/// Couldn't parse unsigned integer.
91	BadUInt(Location, Box<str>),
92	/// Couldn't parse floating-point number.
93	BadFloat(Location, Box<str>),
94	/// Couldn't parse boolean.
95	BadBool(Location, Box<str>),
96	/// Opening \[ without matching \].
97	UnmatchedLeftBrace(Location),
98	/// Key contains invalid characters.
99	///
100	/// The valid characters are anything outside of ASCII,
101	/// as well as `a`–`z`, `A`–`Z`, `0`–`9`, and each of `/.-*_`.
102	InvalidKey(Location, Box<str>),
103	/// Value contains a null character.
104	///
105	/// These are not allowed for interoperability with languages
106	/// with null-terminated strings (C).
107	InvalidValue(Location),
108	/// Line is not a `[section-header]` or `key = value`.
109	InvalidLine(Location),
110	/// Characters appear after a quoted value.
111	///
112	/// e.g. `key = "value" foo`
113	StrayCharsAfterString(Location),
114	/// String opened but never closed with a matching character.
115	UnterminatedString(Location, char),
116	/// Invalid escape sequence appears in a quoted value.
117	InvalidEscapeSequence(Location, Box<str>),
118	/// Key is defined twice in a file
119	DuplicateKey(Box<str>, Location, Location),
120	/// Used when there is more than one error in a file.
121	///
122	/// None of the errors in the array will be [`Error::Multiple`]'s,
123	/// and the array will contain at least two elements.
124	Multiple(Box<[Error]>),
125}
126
127impl fmt::Display for Error {
128	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129		match self {
130			#[cfg(feature = "std")]
131			Self::IO(message, io_err) => write!(f, "{message}: {io_err}"),
132			Self::IllegalCharacter(location, c) => {
133				write!(f, "{location}: illegal character {c:?}")
134			}
135			Self::InvalidUtf8(location) => write!(f, "{location}: invalid UTF-8"),
136			Self::BadInt(location, value) => {
137				write!(f, "{location}: invalid integer: {value:?}")
138			}
139			Self::BadUInt(location, value) => {
140				write!(f, "{location}: invalid (unsigned) integer: {value:?}",)
141			}
142			Self::BadFloat(location, value) => {
143				write!(f, "{location}: invalid number: {value:?}")
144			}
145			Self::BadBool(location, value) => write!(
146				f,
147				"{location}: value {value:?} should be off/false/no or on/true/yes",
148			),
149			Self::UnmatchedLeftBrace(location) => {
150				write!(f, "{location}: Line starting with [ must end with ]")
151			}
152			Self::InvalidKey(location, key) => {
153				write!(f, "{location}: invalid key {key:?}")
154			}
155			Self::InvalidValue(location) => write!(f, "{location}: value contains null characters"),
156			Self::InvalidLine(location) => write!(
157				f,
158				"{location}: line should either start with [ or contain ="
159			),
160			Self::StrayCharsAfterString(location) => {
161				write!(f, "{location}: stray characters after string value")
162			}
163			Self::UnterminatedString(location, delimiter) => {
164				write!(f, "{location}: missing {delimiter} to close string")
165			}
166			Self::InvalidEscapeSequence(location, sequence) => write!(
167				f,
168				"{location}: invalid escape sequence {sequence:?} (try using \\\\ instead of \\ maybe?)",
169			),
170			Self::DuplicateKey(key, loc1, loc2) => {
171				write!(f, "{loc2}: key {key} was already defined at {loc1}")
172			}
173			Self::Multiple(errs) => {
174				let mut first = true;
175				for err in errs {
176					if !first {
177						writeln!(f)?;
178					}
179					first = false;
180					write!(f, "{err}")?;
181				}
182				Ok(())
183			}
184		}
185	}
186}
187
188impl core::error::Error for Error {
189	fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
190		#[cfg(feature = "std")]
191		if let Error::IO(_, e) = self {
192			return Some(e);
193		}
194		None
195	}
196}
197
198/// Type alias for [`std::result::Result`] with [`Error`] as the error.
199pub type Result<T> = core::result::Result<T, Error>;
200
201fn parse_int(location: &Location, string: &str) -> Result<i64> {
202	let bad_int = || Error::BadInt(location.clone(), string.into());
203	let mut sign = "+";
204	let mut signless = string;
205	if let Some(s) = string.strip_prefix(['-', '+']) {
206		sign = &string[..1];
207		signless = s;
208	}
209	if signless.starts_with('+') {
210		return Err(bad_int());
211	}
212	let uint = parse_uint(location, signless).map_err(|_| bad_int())? as i64;
213	if sign == "-" { Ok(-uint) } else { Ok(uint) }
214}
215
216fn parse_uint(location: &Location, string: &str) -> Result<u64> {
217	let bad_uint = || Error::BadUInt(location.clone(), string.into());
218	if !string
219		.bytes()
220		.all(|c| c.is_ascii_hexdigit() || c == b'x' || c == b'X' || c == b'+')
221	{
222		return Err(bad_uint());
223	}
224	let signless = string.strip_prefix('+').unwrap_or(string);
225	let mut base = 16;
226	let baseless = signless
227		.strip_prefix("0x")
228		.or_else(|| signless.strip_prefix("0X"))
229		.unwrap_or_else(|| {
230			base = 10;
231			signless
232		});
233	if baseless.len() > 1 && baseless.starts_with('0') && base == 10 {
234		// decimal leading zeroes are not allowed
235		return Err(bad_uint());
236	}
237	for digit in baseless.bytes() {
238		if base == 10 && !digit.is_ascii_digit() {
239			return Err(bad_uint());
240		}
241	}
242	let val = u64::from_str_radix(baseless, base).map_err(|_| bad_uint())?;
243	if val >= (1u64 << 53) {
244		return Err(bad_uint());
245	}
246	Ok(val)
247}
248
249fn parse_float(location: &Location, string: &str) -> Result<f64> {
250	let bad_float = || Error::BadFloat(location.clone(), string.into());
251	if !string.bytes().all(|c| {
252		c.is_ascii_digit() || c == b'.' || c == b'+' || c == b'-' || c == b'e' || c == b'E'
253	}) {
254		return Err(bad_float());
255	}
256	for (i, c) in string.bytes().enumerate() {
257		if c == b'.' {
258			// decimal point must be preceded and followed by a digit
259			let ok = |j| string.as_bytes().get(j).is_some_and(u8::is_ascii_digit);
260			if !(ok(i.wrapping_sub(1)) && ok(i + 1)) {
261				return Err(bad_float());
262			}
263		}
264	}
265	string.parse().map_err(|_| bad_float())
266}
267
268fn parse_bool(location: &Location, string: &str) -> Result<bool> {
269	match string {
270		"yes" | "on" | "true" => Ok(true),
271		"no" | "off" | "false" => Ok(false),
272		_ => Err(Error::BadBool(location.clone(), string.into())),
273	}
274}
275
276fn parse_list(string: &str) -> Vec<String> {
277	let mut list = vec![];
278	let mut item = String::new();
279	let mut push = |item: &mut String, push_empty: bool| {
280		let mut item = take(item);
281		while item.ends_with([' ', '\t', '\n']) {
282			item.pop();
283		}
284		let leading_space_stripped = item.trim_start_matches([' ', '\t', '\n']);
285		if push_empty || !leading_space_stripped.is_empty() {
286			list.push(if leading_space_stripped.len() == item.len() {
287				item
288			} else {
289				leading_space_stripped.to_owned()
290			});
291		}
292	};
293	let mut chars = string.chars();
294	while let Some(c) = chars.next() {
295		if c == ',' {
296			push(&mut item, true);
297			item = String::new();
298		} else if c == '\\' {
299			if let Some(next) = chars.next() {
300				if next == ',' || next == '\\' {
301					item.push(next);
302				} else {
303					item.push('\\');
304					item.push(next);
305				}
306			} else {
307				item.push('\\');
308				break;
309			}
310		} else {
311			item.push(c);
312		}
313	}
314	push(&mut item, false);
315	list
316}
317
318/// Trait for reading configurations.
319///
320/// Ordinarily you won't need to implement this trait, since it is
321/// already implemented by any `T` implementing [`std::io::BufRead`] (or else `&str` and `&[u8]`,
322/// if the `std` feature is not enabled).
323pub trait Read {
324	/// Read up to the next line feed (or EOF), not including the line feed itself.
325	///
326	/// Puts the line in `line` and returns `Ok(true)`.
327	/// If the end of file has been reached, `line` is unmodified and `Ok(false)` is returned.
328	///
329	/// You don't need to check for valid UTF-8 here — that is already done in the code which uses
330	/// this trait.
331	fn read_until_lf(&mut self, line: &mut Vec<u8>) -> Result<bool>;
332}
333
334#[cfg(feature = "std")]
335impl<R: std::io::BufRead> Read for R {
336	fn read_until_lf(&mut self, line: &mut Vec<u8>) -> Result<bool> {
337		self.read_until(b'\n', line)
338			.map_err(|e| Error::IO("read error".into(), e))?;
339		if line.ends_with(b"\n") {
340			line.pop();
341			Ok(true)
342		} else {
343			Ok(!line.is_empty())
344		}
345	}
346}
347
348#[cfg(not(feature = "std"))]
349impl Read for &str {
350	fn read_until_lf(&mut self, line: &mut Vec<u8>) -> Result<bool> {
351		match self.split_once('\n') {
352			Some((pre, post)) => {
353				*self = post;
354				line.extend_from_slice(pre.as_bytes());
355				Ok(true)
356			}
357			None => {
358				if self.is_empty() {
359					return Ok(false);
360				}
361				line.extend_from_slice(self.as_bytes());
362				*self = "";
363				Ok(true)
364			}
365		}
366	}
367}
368
369#[cfg(not(feature = "std"))]
370impl Read for &[u8] {
371	fn read_until_lf(&mut self, line: &mut Vec<u8>) -> Result<bool> {
372		match self.iter().position(|&c| c == b'\n') {
373			Some(i) => {
374				line.extend_from_slice(&self[..i]);
375				*self = &self[i + 1..];
376				Ok(true)
377			}
378			None => {
379				if self.is_empty() {
380					return Ok(false);
381				}
382				line.extend_from_slice(self);
383				*self = b"";
384				Ok(true)
385			}
386		}
387	}
388}
389
390fn is_illegal_byte(c: u8) -> bool {
391	(0..0x1f).contains(&c) && c != b'\t'
392}
393
394fn process_line(line_buf: &mut Vec<u8>, location: impl Fn() -> Location) -> Result<&str> {
395	line_buf.pop_if(|c| *c == b'\r');
396	for c in line_buf.iter() {
397		if is_illegal_byte(*c) {
398			return Err(Error::IllegalCharacter(location(), char::from(*c)));
399		}
400	}
401	str::from_utf8(line_buf).map_err(|_| Error::InvalidUtf8(location()))
402}
403
404fn parse_hex_digit(c: char) -> Option<u32> {
405	Some(match c {
406		'0'..='9' => (c as u32) - ('0' as u32),
407		'a'..='f' => (c as u32) - ('a' as u32) + 10,
408		'A'..='F' => (c as u32) - ('A' as u32) + 10,
409		_ => return None,
410	})
411}
412
413/// Returns `Ok(())` if `errors` is empty, otherwise a compound error
414/// containing all the `errors`.
415fn check_error_vec(mut errors: Vec<Error>) -> Result<()> {
416	match errors.len() {
417		0 => Ok(()),
418		1 => Err(errors.pop().unwrap()),
419		_ => Err(Error::Multiple(errors.into())),
420	}
421}
422
423#[derive(Default)]
424struct Parser {
425	nonfatal_errors: Vec<Error>,
426}
427
428impl Parser {
429	fn check_valid_key(&mut self, location: &Location, s: &str) {
430		if s.is_empty()
431			|| s.starts_with('.')
432			|| s.ends_with('.')
433			|| s.contains("..")
434			|| !s.bytes().all(|c| {
435				c >= 0x80
436					|| c.is_ascii_alphanumeric()
437					|| matches!(c, b'.' | b'_' | b'/' | b'*' | b'-')
438			}) {
439			self.nonfatal_errors
440				.push(Error::InvalidKey(location.clone(), s.into()));
441		}
442	}
443
444	fn parse_escape_sequence(
445		&mut self,
446		chars: &mut core::str::Chars,
447		value: &mut String,
448		location: impl Fn() -> Location,
449	) {
450		let invalid_escape = |s: String| Error::InvalidEscapeSequence(location(), s.into());
451		let Some(c) = chars.next() else {
452			self.nonfatal_errors
453				.push(invalid_escape("\\(newline)".into()));
454			return;
455		};
456		match c {
457			'n' => value.push('\n'),
458			'r' => value.push('\r'),
459			't' => value.push('\t'),
460			'\\' | '\'' | '"' | '`' => value.push(c),
461			',' => value.push_str("\\,"),
462			'x' => {
463				let Some(c1) = chars.next() else {
464					self.nonfatal_errors.push(invalid_escape("\\x".into()));
465					return;
466				};
467				let Some(c2) = chars.next() else {
468					self.nonfatal_errors
469						.push(invalid_escape(format!("\\x{c1}")));
470					return;
471				};
472				let (Some(nibble1), Some(nibble2)) = (parse_hex_digit(c1), parse_hex_digit(c2))
473				else {
474					self.nonfatal_errors
475						.push(invalid_escape(format!("\\x{c1}{c2}")));
476					return;
477				};
478				let char_code = nibble1 << 4 | nibble2;
479				if char_code == 0 {
480					self.nonfatal_errors.push(Error::InvalidValue(location()));
481				}
482				if char_code >= 0x80 {
483					self.nonfatal_errors
484						.push(invalid_escape(format!("\\x{c1}{c2}")));
485				}
486				value.push(char::try_from(char_code).unwrap());
487			}
488			'u' => {
489				let mut c = chars.next();
490				if c != Some('{') {
491					self.nonfatal_errors.push(invalid_escape("\\u".into()));
492					return;
493				}
494				let mut code = 0u32;
495				for i in 0..7 {
496					c = chars.next();
497					if i == 6 {
498						break;
499					}
500					let Some(c) = c else {
501						break;
502					};
503					if c == '}' {
504						break;
505					}
506					code <<= 4;
507					let Some(digit) = parse_hex_digit(c) else {
508						self.nonfatal_errors
509							.push(invalid_escape(format!("\\u{{{code:x}{c}")));
510						return;
511					};
512					code |= digit;
513				}
514				if c != Some('}') {
515					self.nonfatal_errors
516						.push(invalid_escape("\\u{ has no matching }".into()));
517					return;
518				}
519				if code == 0 {
520					self.nonfatal_errors.push(Error::InvalidValue(location()));
521				}
522				let Ok(c) = char::try_from(code) else {
523					self.nonfatal_errors
524						.push(invalid_escape(format!("\\u{{{code:x}}}")));
525					return;
526				};
527				value.push(c);
528			}
529			_ => {
530				self.nonfatal_errors.push(invalid_escape(format!("\\{c}")));
531			}
532		}
533	}
534
535	/// Returns (unquoted value, new line number)
536	fn read_quoted_value(
537		&mut self,
538		quoted: &str,
539		reader: &mut dyn Read,
540		start_location: &Location,
541	) -> Result<(String, u64)> {
542		let delimiter: char = quoted.chars().next().unwrap();
543		let mut unquoted = String::new();
544		let mut line_number = start_location.line;
545		let location = |line_number: u64| Location {
546			file: start_location.file.clone(),
547			line: line_number,
548		};
549		let mut line_buf = vec![];
550		let mut first = true;
551		loop {
552			let line = if first {
553				first = false;
554				&quoted[1..]
555			} else {
556				line_buf.truncate(0);
557				if !reader.read_until_lf(&mut line_buf)? {
558					break;
559				}
560				line_number += 1;
561				process_line(&mut line_buf, || location(line_number))?
562			};
563			let mut chars = line.chars();
564			while let Some(c) = chars.next() {
565				if c == delimiter {
566					if !chars.all(|c| c == ' ' || c == '\t') {
567						self.nonfatal_errors
568							.push(Error::StrayCharsAfterString(location(line_number)));
569					}
570					return Ok((unquoted, line_number));
571				} else if c == '\\' {
572					self.parse_escape_sequence(&mut chars, &mut unquoted, || location(line_number));
573				} else if c == '\0' {
574					self.nonfatal_errors
575						.push(Error::InvalidValue(location(line_number)));
576				} else {
577					unquoted.push(c);
578				}
579			}
580			unquoted.push('\n');
581		}
582		Err(Error::UnterminatedString(start_location.clone(), delimiter))
583	}
584
585	fn load(&mut self, filename: &str, reader: &mut dyn Read) -> Result<Configuration> {
586		let mut items: Vec<(Box<str>, Arc<Value>)> = vec![];
587		let mut line: Vec<u8> = vec![];
588		let mut line_number: u64 = 0;
589		let mut current_section = String::new();
590		let filename: Arc<str> = filename.into();
591		loop {
592			line.truncate(0);
593			if !reader.read_until_lf(&mut line)? {
594				break;
595			}
596			if line_number == 0 && line.starts_with(b"\xEF\xBB\xBF") {
597				line.drain(..3);
598			}
599			line_number += 1;
600			let location = Location {
601				file: filename.clone(),
602				line: line_number,
603			};
604			let mut line = process_line(&mut line, || location.clone())?;
605			line = line.trim_start_matches(['\t', ' ']);
606			if line.is_empty() || line.starts_with('#') {
607				// comment/blank line
608				continue;
609			}
610			if line.starts_with('[') {
611				// [section.header]
612				line = line.trim_end_matches(['\t', ' ']);
613				if !line.ends_with(']') {
614					return Err(Error::UnmatchedLeftBrace(location));
615				}
616				current_section = line[1..line.len() - 1].into();
617				if !current_section.is_empty() {
618					self.check_valid_key(&location, &current_section);
619				}
620			} else {
621				// key = value
622				let (mut relative_key, mut value) = line
623					.split_once('=')
624					.ok_or_else(|| Error::InvalidLine(location.clone()))?;
625				relative_key = relative_key.trim_end_matches(['\t', ' ']);
626				self.check_valid_key(&location, relative_key);
627				let key: String = if current_section.is_empty() {
628					relative_key.into()
629				} else {
630					format!("{current_section}.{relative_key}")
631				};
632				value = value.trim_start_matches(['\t', ' ']);
633				if value.starts_with(['`', '"']) {
634					let (value, new_line_number) =
635						self.read_quoted_value(value, reader, &location)?;
636					items.push((
637						key.into(),
638						Arc::new(Value {
639							value: value.into(),
640							defined_at: location,
641							read: AtomicBool::new(false),
642						}),
643					));
644					line_number = new_line_number;
645				} else {
646					value = value.trim_end_matches(['\t', ' ']);
647					if value.contains('\0') {
648						return Err(Error::InvalidValue(location));
649					}
650					items.push((
651						key.into(),
652						Arc::new(Value {
653							value: value.into(),
654							defined_at: location,
655							read: AtomicBool::new(false),
656						}),
657					));
658				}
659			}
660		}
661		items.sort_unstable_by(|(k1, _), (k2, _)| k1.cmp(k2));
662		for window in items.windows(2) {
663			if window[0].0 == window[1].0 {
664				// duplicate key
665				self.nonfatal_errors.push(Error::DuplicateKey(
666					window[0].0.clone(),
667					window[0].1.defined_at.clone(),
668					window[1].1.defined_at.clone(),
669				));
670			}
671		}
672		check_error_vec(take(&mut self.nonfatal_errors))?;
673		Ok(Configuration { items })
674	}
675}
676
677impl Configuration {
678	/// Load a configuration.
679	///
680	/// `reader` can be `&[u8]` or anything that implements [`std::io::BufRead`]
681	/// (if the `std` feature is enabled) such as `std::io::BufReader<std::fs::File>`.
682	///
683	/// `filename` is used in error messages.
684	pub fn load<R: Read>(filename: &str, mut reader: R) -> Result<Self> {
685		// avoid big code size by using dyn reference.
686		// the impact on performance is not really important.
687		Parser::default().load(filename, &mut reader)
688	}
689
690	/// Load a configuration from a file path.
691	#[cfg(feature = "std")]
692	pub fn load_path<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
693		let p = path.as_ref();
694		let filename = p.to_string_lossy();
695		let file = std::fs::File::open(p).map_err(|e| Error::IO(filename.clone().into(), e))?;
696		Configuration::load(&filename, std::io::BufReader::new(file))
697	}
698
699	/// Binary search `self.items`.
700	///
701	/// See [`std::slice::binary_search_by`].
702	fn binary_search_for(&self, key: &str) -> core::result::Result<usize, usize> {
703		self.items.binary_search_by(|(k, _)| k.as_ref().cmp(key))
704	}
705
706	/// Returns the index of the first key which is greater than `key` + "."
707	fn subkey_start_idx(&self, key: &str) -> usize {
708		let key_dot = format!("{key}.");
709		self.binary_search_for(&key_dot)
710			.expect_err("items should not contain a key ending in .")
711	}
712
713	/// Returns the index of the first key which is greater than `key` + "." + `x` for all strings `x`.
714	fn subkey_end_idx(&self, key: &str) -> usize {
715		// NB: / is the next ASCII character after .
716		let key_slash = format!("{key}/");
717		self.binary_search_for(&key_slash).unwrap_or_else(|x| x)
718	}
719
720	/// Extract a section out of a configuration.
721	///
722	/// More specifically, this will give you the configuration consisting of all
723	/// keys starting with `key.` in `self`, together with their values.
724	#[must_use]
725	pub fn section(&self, key: &str) -> Configuration {
726		let start_idx = self.subkey_start_idx(key);
727		let end_idx = self.subkey_end_idx(key);
728		Configuration {
729			items: self.items[start_idx..end_idx]
730				.iter()
731				.map(|(k, v)| (k[key.len() + 1..].into(), v.clone()))
732				.collect(),
733		}
734	}
735
736	/// Get all “direct keys” in this configuration.
737	///
738	/// More specifically, this returns an iterator of all unique
739	/// first components of keys in `self`.
740	///
741	/// (So if there were keys `sheep.age`, `sheep.colour`, and `farmer-name`,
742	///  this would give an iterator yielding
743	///  `"farmer-name"` and `"sheep"` in some order.)
744	///
745	/// The order of items returned is arbitrary and may change
746	/// in future versions without notice.
747	pub fn keys(&self) -> Keys<'_> {
748		Keys {
749			iter: self.items.iter(),
750			prev: None,
751		}
752	}
753
754	/// Get all defined keys in this configuration, including nested ones,
755	/// and their values.
756	///
757	/// The order of items returned is arbitrary and may change
758	/// in future versions without notice.
759	pub fn iter(&self) -> ConfigurationIter<'_> {
760		self.into_iter()
761	}
762
763	fn get_val(&self, key: &str, mark_read: bool) -> Option<&Value> {
764		let idx = self.binary_search_for(key).ok()?;
765		let v = &self.items[idx].1;
766		if mark_read {
767			v.read.store(true, Ordering::Relaxed);
768		}
769		Some(v)
770	}
771
772	/// Get value associated with `key`, if any.
773	#[must_use]
774	pub fn get(&self, key: &str) -> Option<&str> {
775		Some(self.get_val(key, true)?.value.as_ref())
776	}
777
778	/// Get location in the configuration file where `key` is defined, if any.
779	#[must_use]
780	pub fn location(&self, key: &str) -> Option<Location> {
781		if let Some(val) = self.get_val(key, false) {
782			Some(val.defined_at.clone())
783		} else {
784			// Check if `key` has any defined subkeys
785			let start_idx = self.subkey_start_idx(key);
786			let end_idx = self.subkey_end_idx(key);
787			self.items[start_idx..end_idx]
788				.iter()
789				.map(|(_, value)| &value.defined_at)
790				.min_by_key(|loc| loc.line)
791				.cloned()
792		}
793	}
794
795	/// Returns `true` if `key` is defined in this configuration.
796	#[must_use]
797	pub fn has(&self, key: &str) -> bool {
798		self.get_val(key, false).is_some()
799	}
800
801	/// Get value associated with `key`, or else use `default` if it isn't defined.
802	#[must_use]
803	pub fn get_or_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
804		self.get(key).unwrap_or(default)
805	}
806
807	/// Get value associated with `key`, and parse it as an integer.
808	///
809	/// Returns `None` if `key` is not defined,
810	/// and `Some(Err(…))` if `key` is defined but not an integer.
811	#[must_use]
812	pub fn get_int(&self, key: &str) -> Option<Result<i64>> {
813		let Value {
814			value, defined_at, ..
815		} = self.get_val(key, true)?;
816		Some(parse_int(defined_at, value.as_ref()))
817	}
818
819	/// Get value associated with `key`, and parse it as an integer, or else use `default`.
820	///
821	/// Returns `Err(…)` if `key` is defined but not an integer.
822	pub fn get_int_or_default(&self, key: &str, default: i64) -> Result<i64> {
823		self.get_int(key).unwrap_or(Ok(default))
824	}
825
826	/// Get value associated with `key`, and parse it as an unsigned integer.
827	///
828	/// Returns `None` if `key` is not defined,
829	/// and `Some(Err(…))` if `key` is defined but not an unsigned integer.
830	#[must_use]
831	pub fn get_uint(&self, key: &str) -> Option<Result<u64>> {
832		let Value {
833			value, defined_at, ..
834		} = self.get_val(key, true)?;
835		Some(parse_uint(defined_at, value.as_ref()))
836	}
837
838	/// Get value associated with `key`, and parse it as an unsinged integer, or else use `default`.
839	///
840	/// Returns `Err(…)` if `key` is defined but not an unsigned integer.
841	pub fn get_uint_or_default(&self, key: &str, default: u64) -> Result<u64> {
842		self.get_uint(key).unwrap_or(Ok(default))
843	}
844
845	/// Get value associated with `key`, and parse it as a float.
846	///
847	/// Returns `None` if `key` is not defined,
848	/// and `Some(Err(…))` if `key` is defined but not a float.
849	#[must_use]
850	pub fn get_float(&self, key: &str) -> Option<Result<f64>> {
851		let Value {
852			value, defined_at, ..
853		} = self.get_val(key, true)?;
854		Some(parse_float(defined_at, value.as_ref()))
855	}
856
857	/// Get value associated with `key`, and parse it as a float, or else use `default`.
858	///
859	/// Returns `Err(…)` if `key` is defined but not a float.
860	pub fn get_float_or_default(&self, key: &str, default: f64) -> Result<f64> {
861		self.get_float(key).unwrap_or(Ok(default))
862	}
863
864	/// Get value associated with `key`, and parse it as a boolean.
865	///
866	/// Returns `None` if `key` is not defined,
867	/// and `Some(Err(…))` if `key` is defined but not equal to one of
868	/// `off`, `no`, `false`, `on`, `yes`, `true`.
869	#[must_use]
870	pub fn get_bool(&self, key: &str) -> Option<Result<bool>> {
871		let Value {
872			value, defined_at, ..
873		} = self.get_val(key, true)?;
874		Some(parse_bool(defined_at, value.as_ref()))
875	}
876
877	/// Get value associated with `key`, and parse it as a boolean, or else use `default`.
878	///
879	/// Returns `Err(…)` if `key` is defined but not equal to one of
880	/// `off`, `no`, `false`, `on`, `yes`, `true`.
881	pub fn get_bool_or_default(&self, key: &str, default: bool) -> Result<bool> {
882		self.get_bool(key).unwrap_or(Ok(default))
883	}
884
885	/// Get value associated with `key`, and parse it as a comma-separated list.
886	///
887	/// Commas in list entries can be escaped with `\,`.
888	#[must_use]
889	pub fn get_list(&self, key: &str) -> Option<Vec<String>> {
890		let value = &self.get_val(key, true)?.value;
891		Some(parse_list(value.as_ref()))
892	}
893
894	/// Get value associated with `key`, and parse it as a comma-separated list, or else use `default`.
895	///
896	/// If you want `default = []`, use [`Self::get_list_or_empty`] instead
897	/// (this method will be cumbersome to use since Rust can't infer the type of `[]`).
898	///
899	/// Commas in list entries can be escaped with `\,`.
900	///
901	/// `default` can be any iterable-of-strings (`&[&str]`, `Vec<String>`, etc.).
902	pub fn get_list_or_default<L>(&self, key: &str, default: L) -> Vec<String>
903	where
904		L: IntoIterator,
905		String: From<L::Item>,
906	{
907		self.get_list(key)
908			.unwrap_or_else(|| default.into_iter().map(String::from).collect())
909	}
910
911	/// Get value associated with `key`, and parse it as a comma-separated list, or else use `[]`.
912	pub fn get_list_or_empty(&self, key: &str) -> Vec<String> {
913		let empty: [&'static str; 0] = [];
914		self.get_list_or_default(key, empty)
915	}
916
917	/// Merge `conf` into `self`, preferring values in `conf`.
918	pub fn merge(&mut self, conf: &Configuration) {
919		let mut must_sort = false;
920		for (key, value) in &conf.items {
921			if let Ok(i) = self.binary_search_for(key) {
922				self.items[i].1 = value.clone();
923			} else {
924				self.items.push((key.clone(), value.clone()));
925				must_sort = true;
926			}
927		}
928		if must_sort {
929			self.items.sort_unstable_by(|(k1, _), (k2, _)| k1.cmp(k2));
930		}
931	}
932
933	/// Returns an iterator over all keys whose values have not been read.
934	///
935	/// This includes getting them through [`Self::get`], [`Self::get_or_default`], [`Self::get_int`], etc.
936	/// It also includes getting them through [`Self::get`] called on a section obtained via [`Self::section`].
937	///
938	/// The order of the items returned is arbitrary and may change in future versions without notice.
939	///
940	/// Beware of race conditions when using this function in a multithreaded program
941	/// (you should wait for all threads to finish reading the configuration before calling this).
942	pub fn unread_keys(&self) -> UnreadKeys<'_> {
943		UnreadKeys(self.items.iter())
944	}
945}
946
947// Type returned by `Configuration.items.iter()`
948type ItemsIter<'a> = core::slice::Iter<'a, (Box<str>, Arc<Value>)>;
949
950/// Opaque type returned by [`Configuration::iter`].
951#[derive(Clone, Debug)]
952pub struct ConfigurationIter<'a>(ItemsIter<'a>);
953
954impl<'a> Iterator for ConfigurationIter<'a> {
955	type Item = (&'a str, &'a str);
956	fn next(&mut self) -> Option<Self::Item> {
957		let (key, val) = self.0.next()?;
958		Some((key, val.value.as_ref()))
959	}
960}
961
962impl FusedIterator for ConfigurationIter<'_> {}
963
964/// Opaque type returned by [`Configuration::keys`].
965#[derive(Clone, Debug)]
966pub struct Keys<'a> {
967	prev: Option<&'a str>,
968	iter: ItemsIter<'a>,
969}
970impl<'a> Iterator for Keys<'a> {
971	type Item = &'a str;
972	fn next(&mut self) -> Option<Self::Item> {
973		loop {
974			let (key, _) = self.iter.next()?;
975			let first_component: &str = key.split_once('.').map_or(key.as_ref(), |(c, _)| c);
976			if self.prev != Some(first_component) {
977				self.prev = Some(first_component);
978				return Some(first_component);
979			}
980		}
981	}
982}
983
984impl FusedIterator for Keys<'_> {}
985
986/// Opaque type returned by [`Configuration::unread_keys`].
987#[derive(Clone, Debug)]
988pub struct UnreadKeys<'a>(ItemsIter<'a>);
989
990impl<'a> Iterator for UnreadKeys<'a> {
991	type Item = &'a str;
992	fn next(&mut self) -> Option<Self::Item> {
993		loop {
994			let (k, v) = self.0.next()?;
995			if !v.read.load(Ordering::Relaxed) {
996				return Some(k.as_ref());
997			}
998		}
999	}
1000}
1001
1002impl FusedIterator for UnreadKeys<'_> {}
1003
1004impl<'a> IntoIterator for &'a Configuration {
1005	type IntoIter = ConfigurationIter<'a>;
1006	type Item = (&'a str, &'a str);
1007	/// See [`Configuration::iter`].
1008	fn into_iter(self) -> Self::IntoIter {
1009		ConfigurationIter(self.items.iter())
1010	}
1011}