From 45ed8d3ee402ef9de72a0e21c4e7572506bfaf5c Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Fri, 23 Jul 2021 11:01:31 +0100 Subject: [PATCH] wip impl config reading Signed-off-by: Ian Jackson --- src/bin/client.rs | 4 +- src/config.rs | 151 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 4 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index b8d2cf5..bf2d596 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -4,7 +4,7 @@ use hippotat::prelude::*; +#[throws(AE)] fn main() { - let opts = config::Opts::from_args(); - eprintln!("{:#?}", &opts); + config::read(); } diff --git a/src/config.rs b/src/config.rs index a048245..1d6caf2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,9 +4,13 @@ use crate::prelude::*; +use configparser::ini::Ini; + +static MAIN_CONFIGS: [&str;_] = ["main.cfg", "config.d", "secrets.d"]; + #[derive(StructOpt,Debug)] pub struct Opts { - /// Default config file or directory + /// Top-level config file or directory /// /// Look for `main.cfg`, `config.d` and `secrets.d` here. /// @@ -14,7 +18,7 @@ pub struct Opts { #[structopt(long, default_value="/etc/hippotat")] pub config: PathBuf, - /// Read this in addition, after the other config files + /// Additional config files or dirs, which can override the others #[structopt(long, multiple=true, number_of_values=1)] pub extra_config: Vec, } @@ -54,3 +58,146 @@ pub struct InstanceConfig { pub url: Uri, pub vroutes: Vec, } + +type SectionMap = HashMap>; + +pub struct Config { + opts: Opts, +} + +static OUTSIDE_SECTION: &str = "["; + +struct Aggregate { + sections: HashMap, +} + +impl Aggregate { + #[throws(AE)] // AE does not include path + fn read_file(&mut self, path: Path, + ekok: &Fn(ErrorKind) -> Result<(),()>) + -> Result<(), io::ErrorKind> + { + let f = match File::open(path) { + Err(e) if ekok(e.kind()) => return Err(e.kind()), + r => r.context("open")?; + } + + let mut s = String::new(); + f.read_to_string(&mut s).context("read")?; + + let mut ini = configparser::ini::Ini::new_cs(); + ini.set_default_section(OUTSIDE_SECTION); + ini.read(s).context("parse as INI"); + let map = mem::take(ini.get_mut_map); + if map.get(OUTSIDE_SECTION).is_some() { + throw!(anyhow!("INI file contains settings outside a section")); + } + + // xxx parse section names here + // xxx save Arc where we found each item + + self.sections.extend(map.drain()); + Ok(()) + } + + #[throws(AE)] // AE includes path + fn read_dir_d(&mut self, path: Path + ekok: &Fn(ErrorKind)) + -> Result<(), io::ErrorKind> + { + let wc = || format("{:?}", path); + for ent in match fs::read_dir(path) { + Err(e) if ekok(e.kind()) => return Err(e.kind()), + r => r.context("open directory").with_context(wc)?; + } { + let ent = ent.context("read directory").with_context(wc)?; + let leaf = ent.file_name().as_str(); + let leaf = if let Some(leaf) = leaf { leaf } else { continue }; //utf8? + if leaf.length() == 0 { continue } + if ! leaf.chars().all( + |c| c=='-' || c=='_' || c.is_ascii_alphenumeric() + ) { continue } + + // OK we want this one + self.read_file(&ent.path, &|_| false) + .with_context(format!("{:?}", &ent.path))??; + } + } + + #[throws(AE)] // AE includes everything + fn read_toplevel(&mut self, toplevel: &Path) { + match se;lf.read_file( + toplevel, + |k| matches!(k, EK::NotFound || EK::IsDirectory) + ) + .with_context(format!("{:?}", toplevel)) + .context("top-level config directory (or file)")? + { + Err(EK::NotFound) => { }, + + Err(EK::IsDirectory) => { + let mk = |leaf| [ toplevel, leaf ].iter().collect::(); + + for &(try_main, desc) in &[ + ("main.cfg", "main config file"), + ("master.cfg", "obsolete-named main config file"), + ] { + let main = mk(try_main); + match self.read_file(&main, |e| e == EK::NotFound) + .with_context(format!("{:?}", &main)) + .context(desc)? + { + Ok(()) => break, + Err(EK::NotFound) => { }, + x => panic!("huh? {:?}", &x), + } + } + + for &(try_dir, desc) in &[ + ("config.d", "per-link config directory"), + ("secrets.d", "per-link secrets directory"), + ] { + let dir = mk(try_dir); + match agg.read_dir(&dir, |k| k == EK::NotFound).context(desc)? { + Ok(()) => { }, + Err(EK::NotFound) => { }, + x => panic!("huh? {:?}", &x), + } + } + } + } + } + + #[throws(AE)] // AE includes extra, but does that this is extra + fn read_extra(&mut self, extra: &Path) { + + match self.read_file(extra, |k| k == EK::IsDirectory) + .with_context(format!("{:?}", extra))? + { + Ok(()) => return, + Err(EK::IsDirectory) => { } + x => panic!("huh? {:?}", &x), + } + + self.read_dir(extra, |_| false)??; + } +} + + +#[throws(AE)] +pub fn read() { + let opts = config::Opts::from_args(); + + (||{ + let agg = Aggregate::default(); + + agg.read_toplevel(&opts.config)?; + for extra in &opts.extra_config { + agg.read_extra(extra).context("extra config")?; + } + + eprintln!("GOT {:?}", agg); + + Ok::<_,AE>(); + }).context("read configuration"); +} -- 2.30.2