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#[derive(Clone, Debug)]
24pub struct Location {
25 file: Arc<str>,
26 line: u64,
27}
28
29impl Location {
30 #[must_use]
32 pub fn file(&self) -> &str {
33 &self.file
34 }
35
36 #[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#[derive(Debug)]
51struct Value {
52 value: Box<str>,
53 defined_at: Location,
54 read: AtomicBool,
55}
56
57#[derive(Clone, Debug, Default)]
59pub struct Configuration {
60 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#[non_exhaustive]
75#[derive(Debug)]
76pub enum Error {
77 #[cfg(feature = "std")]
81 IO(Box<str>, std::io::Error),
82 IllegalCharacter(Location, char),
86 InvalidUtf8(Location),
88 BadInt(Location, Box<str>),
90 BadUInt(Location, Box<str>),
92 BadFloat(Location, Box<str>),
94 BadBool(Location, Box<str>),
96 UnmatchedLeftBrace(Location),
98 InvalidKey(Location, Box<str>),
103 InvalidValue(Location),
108 InvalidLine(Location),
110 StrayCharsAfterString(Location),
114 UnterminatedString(Location, char),
116 InvalidEscapeSequence(Location, Box<str>),
118 DuplicateKey(Box<str>, Location, Location),
120 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
198pub 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 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 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
318pub trait Read {
324 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
413fn 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 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 "ed[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 continue;
609 }
610 if line.starts_with('[') {
611 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, ¤t_section);
619 }
620 } else {
621 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 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 pub fn load<R: Read>(filename: &str, mut reader: R) -> Result<Self> {
685 Parser::default().load(filename, &mut reader)
688 }
689
690 #[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 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 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 fn subkey_end_idx(&self, key: &str) -> usize {
715 let key_slash = format!("{key}/");
717 self.binary_search_for(&key_slash).unwrap_or_else(|x| x)
718 }
719
720 #[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 pub fn keys(&self) -> Keys<'_> {
748 Keys {
749 iter: self.items.iter(),
750 prev: None,
751 }
752 }
753
754 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 #[must_use]
774 pub fn get(&self, key: &str) -> Option<&str> {
775 Some(self.get_val(key, true)?.value.as_ref())
776 }
777
778 #[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 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 #[must_use]
797 pub fn has(&self, key: &str) -> bool {
798 self.get_val(key, false).is_some()
799 }
800
801 #[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 #[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 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 #[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 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 #[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 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 #[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 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 #[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 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 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 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 pub fn unread_keys(&self) -> UnreadKeys<'_> {
943 UnreadKeys(self.items.iter())
944 }
945}
946
947type ItemsIter<'a> = core::slice::Iter<'a, (Box<str>, Arc<Value>)>;
949
950#[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#[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#[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 fn into_iter(self) -> Self::IntoIter {
1009 ConfigurationIter(self.items.iter())
1010 }
1011}