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.
///
#[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<PathBuf>,
}
pub url: Uri,
pub vroutes: Vec<CidrString>,
}
+
+type SectionMap = HashMap<String, Option<String>>;
+
+pub struct Config {
+ opts: Opts,
+}
+
+static OUTSIDE_SECTION: &str = "[";
+
+struct Aggregate {
+ sections: HashMap<String, SectionMap>,
+}
+
+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<PathBuf> 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::<PathBuf>();
+
+ 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");
+}