chiark / gitweb /
wip queue
[hippotat.git] / src / ini.rs
1 // Copyright 2021 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 use crate::prelude::*;
6
7 use std::io::BufRead;
8 use std::rc::Rc;
9
10 #[derive(Debug,Clone)]
11 #[derive(Hash,Eq,PartialEq,Ord,PartialOrd)]
12 pub struct Loc {
13   pub file: Arc<PathBuf>,
14   pub lno: usize,
15   pub section: Option<Arc<str>>,
16 }
17
18 #[derive(Debug,Clone)]
19 pub struct Val {
20   pub val: String,
21   pub loc: Loc,
22 }
23
24 pub type Parsed = HashMap<Arc<str>, Section>;
25
26 #[derive(Debug)]
27 pub struct Section {
28   /// Location of first encounter
29   pub loc: Loc,
30   pub values: HashMap<String, Val>,
31 }
32
33 impl Display for Loc {
34   #[throws(fmt::Error)]
35   fn fmt(&self, f: &mut fmt::Formatter) {
36     write!(f, "{:?}:{}", &self.file, self.lno)?;
37     if let Some(s) = &self.section {
38       write!(f, " ")?;
39       let dbg = format!("{:?}", &s);
40       if let Some(mid) = (||{
41         let mid = dbg.strip_prefix(r#"""#)?;
42         let mid = mid.strip_suffix(r#"""#)?;
43         Some(mid)
44       })() {
45         write!(f, "[{}]", mid)?;
46       } else {
47         write!(f, "{}", dbg)?;
48       }
49     }
50   }
51 }
52
53
54 #[throws(AE)]
55 pub fn read(parsed: &mut Parsed, file: &mut dyn BufRead, path_for_loc: &Path)
56 //->Result<(), AE>
57 {
58   let parsed = Rc::new(RefCell::new(parsed));
59   let path: Arc<PathBuf> = path_for_loc.to_owned().into();
60   let mut section: Option<RefMut<Section>> = None;
61   for (lno, line) in file.lines().enumerate() {
62     let line = line.context("read")?;
63     let line = line.trim();
64
65     if line.is_empty() { continue }
66     if regex_is_match!(r#"^ [;\#] "#x, line) { continue }
67
68     let loc = Loc {
69       lno,
70       file: path.clone(),
71       section: section.as_ref().map(|s| s.loc.section.as_ref().unwrap().clone()),
72     };
73     (|| Ok::<(),AE>({
74
75       if let Some((_,new,)) =
76         regex_captures!(r#"^ \[ \s* (.+?) \s* \] $"#x, line)
77       {
78         let new: Arc<str> = new.to_owned().into();
79
80         section.take(); // drops previous RefCell borrow of parsed
81
82         let new_section = RefMut::map(parsed.borrow_mut(), |p| {
83
84           p.entry(new.clone())
85             .or_insert_with(|| {
86               Section {
87                 loc: Loc { section: Some(new), file: path.clone(), lno },
88                 values: default(),
89                 }
90             })
91
92         });
93
94         section = Some(new_section);
95
96       } else if let Some((_, key, val)) =
97         regex_captures!(r#"^ ( [^\[] .*? ) \s* = \s* (.*) $"#x, line)
98       {
99         let val = Val { loc: loc.clone(), val: val.into() };
100
101         section
102           .as_mut()
103           .ok_or_else(|| anyhow!("value outside section"))?
104           .values
105           .insert(key.into(), val);
106
107       } else {
108         throw!(if line.starts_with("[") {
109           anyhow!(r#"syntax error (section missing final "]"?)"#)
110         } else {
111           anyhow!(r#"syntax error (setting missing "="?)"#)
112         })
113       }
114
115     }))().with_context(|| loc.to_string())?
116   }
117 }