serde_ini_spanned/
lib.rs

1#![forbid(unsafe_code)]
2
3pub mod diagnostics;
4pub mod lines;
5pub mod parse;
6pub mod spanned;
7pub mod value;
8
9pub use parse::{Config as ParseConfig, Error};
10pub use spanned::{DerefInner, Span, Spanned};
11pub use value::{Options, Section, SectionProxy, SectionProxyMut, Value, from_reader, from_str};
12
13#[cfg(test)]
14pub mod tests {
15    use crate::{
16        SectionProxy, Spanned,
17        value::{Options, Value},
18    };
19    use codespan_reporting::{diagnostic::Diagnostic, files, term};
20    use std::sync::{Mutex, RwLock};
21
22    static INIT: std::sync::Once = std::sync::Once::new();
23
24    /// Initialize test
25    ///
26    /// This ensures `color_eyre` is setup once.
27    pub fn init() {
28        INIT.call_once(|| {
29            color_eyre::install().ok();
30        });
31    }
32
33    // this makes writing tests quick and concise but is confusing if included in the library
34    impl From<&str> for Spanned<String> {
35        fn from(value: &str) -> Self {
36            Spanned::dummy(value.to_string())
37        }
38    }
39
40    pub(crate) trait SectionProxyExt<'a> {
41        fn items_vec(self) -> Vec<(&'a str, &'a str)>;
42        fn keys_vec(self) -> Vec<&'a str>;
43    }
44
45    impl<'a> SectionProxyExt<'a> for SectionProxy<'a> {
46        fn items_vec(self) -> Vec<(&'a str, &'a str)> {
47            self.iter()
48                .map(|(k, v)| (k.as_ref().as_str(), v.as_ref().as_str()))
49                .collect::<Vec<_>>()
50        }
51
52        fn keys_vec(self) -> Vec<&'a str> {
53            self.keys()
54                .map(|k| k.as_ref().as_str())
55                .collect::<Vec<&'a str>>()
56        }
57    }
58
59    // this makes writing tests quick and concise but may be confusing if it was included in the library
60    impl From<String> for Spanned<String> {
61        fn from(value: String) -> Self {
62            Spanned::dummy(value)
63        }
64    }
65
66    #[derive(Debug)]
67    pub(crate) struct Printer {
68        writer: Mutex<term::termcolor::Buffer>,
69        diagnostic_config: term::Config,
70        files: RwLock<files::SimpleFiles<String, String>>,
71    }
72
73    impl Default for Printer {
74        fn default() -> Self {
75            Self::new()
76        }
77    }
78
79    impl Printer {
80        #[must_use]
81        pub(crate) fn new() -> Self {
82            let writer = term::termcolor::Buffer::ansi();
83            let diagnostic_config = term::Config {
84                styles: term::Styles::with_blue(term::termcolor::Color::Blue),
85                ..term::Config::default()
86            };
87            Self {
88                writer: Mutex::new(writer),
89                diagnostic_config,
90                files: RwLock::new(files::SimpleFiles::new()),
91            }
92        }
93
94        pub(crate) fn add_source_file(&self, name: String, source: String) -> usize {
95            let mut files = self.files.write().unwrap();
96            files.add(name, source)
97        }
98
99        pub(crate) fn emit(&self, diagnostic: &Diagnostic<usize>) -> Result<(), files::Error> {
100            term::emit(
101                &mut *self.writer.lock().unwrap(),
102                &self.diagnostic_config,
103                &*self.files.read().unwrap(),
104                diagnostic,
105            )
106        }
107
108        /// Print written diagnostics to stderr.
109        ///
110        /// This is a workaround for <https://github.com/BurntSushi/termcolor/issues/51>.
111        pub(crate) fn print(&self) {
112            use std::io::Write;
113            let mut writer = self.writer.lock().unwrap();
114            let _ = writer.flush();
115            eprintln!("{}", String::from_utf8_lossy(writer.as_slice()));
116        }
117    }
118
119    /// Parse an INI string and print diagnostics.
120    pub(crate) fn parse(
121        config: &str,
122        options: Options,
123        printer: &Printer,
124    ) -> (Result<Value, super::Error>, usize, Vec<Diagnostic<usize>>) {
125        let file_id = printer.add_source_file("config.ini".to_string(), config.to_string());
126        let mut diagnostics = vec![];
127        let config = crate::from_str(config, options, file_id, &mut diagnostics);
128        if let Err(ref err) = config {
129            diagnostics.extend(err.to_diagnostics(file_id));
130        }
131        for diagnostic in &diagnostics {
132            printer.emit(diagnostic).expect("emit diagnostic");
133        }
134        printer.print();
135        (config, file_id, diagnostics)
136    }
137}