use configparser::ini::Ini;
-static MAIN_CONFIGS: [&str;_] = ["main.cfg", "config.d", "secrets.d"];
-
#[derive(StructOpt,Debug)]
pub struct Opts {
/// Top-level config file or directory
static OUTSIDE_SECTION: &str = "[";
+#[derive(Default,Debug)]
struct Aggregate {
sections: HashMap<String, SectionMap>,
}
+type OkAnyway<'f,A> = &'f dyn Fn(ErrorKind) -> Option<A>;
+#[ext]
+impl<'f,A> OkAnyway<'f,A> {
+ fn ok<T>(self, r: &Result<T, io::Error>) -> Option<A> {
+ let e = r.as_ref().err()?;
+ let k = e.kind();
+ let a = self(k)?;
+ Some(a)
+ }
+}
+
impl Aggregate {
#[throws(AE)] // AE does not include path
- fn read_file(&mut self, path: Path,
- ekok: &Fn(ErrorKind) -> Result<(),()>)
- -> Result<(), io::ErrorKind>
+ fn read_file<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
{
- let f = match File::open(path) {
- Err(e) if ekok(e.kind()) => return Err(e.kind()),
- r => r.context("open")?;
- }
+ let f = fs::File::open(path);
+ if let Some(anyway) = anyway.ok(&f) { return Some(anyway) }
+ let mut f = f.context("open")?;
let mut s = String::new();
f.read_to_string(&mut s).context("read")?;
- let mut ini = configparser::ini::Ini::new_cs();
+ let mut 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);
+ ini.read(s).map_err(|e| anyhow!("{}", e)).context("parse as INI")?;
+ let mut map = mem::take(ini.get_mut_map());
if map.get(OUTSIDE_SECTION).is_some() {
throw!(anyhow!("INI file contains settings outside a section"));
}
// xxx save Arc<PathBuf> where we found each item
self.sections.extend(map.drain());
- Ok(())
+ None
}
#[throws(AE)] // AE includes path
- fn read_dir_d(&mut self, path: Path
- ekok: &Fn(ErrorKind))
- -> Result<(), io::ErrorKind>
+ fn read_dir_d<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
{
- 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 dir = fs::read_dir(path);
+ if let Some(anyway) = anyway.ok(&dir) { return Some(anyway) }
+ let dir = dir.context("open directory").dcontext(path)?;
+ for ent in dir {
+ let ent = ent.context("read directory").dcontext(path)?;
+ let leaf = ent.file_name();
+ let leaf = leaf.to_str();
let leaf = if let Some(leaf) = leaf { leaf } else { continue }; //utf8?
- if leaf.length() == 0 { continue }
+ if leaf.len() == 0 { continue }
if ! leaf.chars().all(
- |c| c=='-' || c=='_' || c.is_ascii_alphenumeric()
+ |c| c=='-' || c=='_' || c.is_ascii_alphanumeric()
) { continue }
// OK we want this one
- self.read_file(&ent.path, &|_| false)
- .with_context(format!("{:?}", &ent.path))??;
+ let ent = ent.path();
+ self.read_file(&ent, &|_| None::<Void>).dcontext(&ent)?;
}
+ None
}
#[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)")?
+ enum Anyway { None, Dir }
+ match self.read_file(toplevel, &|k| match k {
+ EK::NotFound => Some(Anyway::None),
+ EK::IsADirectory => Some(Anyway::Dir),
+ _ => None,
+ })
+ .dcontext(toplevel).context("top-level config directory (or file)")?
{
- Err(EK::NotFound) => { },
+ None | Some(Anyway::None) => { },
+
+ Some(Anyway::Dir) => {
+ struct AnywayNone;
+ let anyway_none = |k| match k {
+ EK::NotFound => Some(AnywayNone),
+ _ => None,
+ };
- Err(EK::IsDirectory) => {
- let mk = |leaf| [ toplevel, leaf ].iter().collect::<PathBuf>();
+ let mk = |leaf: &str| {
+ [ toplevel, &PathBuf::from(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)?
+
+ match self.read_file(&main, &anyway_none)
+ .dcontext(main).context(desc)?
{
- Ok(()) => break,
- Err(EK::NotFound) => { },
- x => panic!("huh? {:?}", &x),
+ None => break,
+ Some(AnywayNone) => { },
}
}
("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),
+ match self.read_dir_d(&dir, &anyway_none).context(desc)? {
+ None => { },
+ Some(AnywayNone) => { },
}
}
}
#[throws(AE)] // AE includes extra, but does that this is extra
fn read_extra(&mut self, extra: &Path) {
+ struct AnywayDir;
- match self.read_file(extra, |k| k == EK::IsDirectory)
- .with_context(format!("{:?}", extra))?
+ match self.read_file(extra, &|k| match k {
+ EK::IsADirectory => Some(AnywayDir),
+ _ => None,
+ })
+ .dcontext(extra)?
{
- Ok(()) => return,
- Err(EK::IsDirectory) => { }
- x => panic!("huh? {:?}", &x),
+ None => return,
+ Some(AnywayDir) => {
+ self.read_dir_d(extra, &|_| None::<Void>)?;
+ }
}
- self.read_dir(extra, |_| false)??;
}
}
let opts = config::Opts::from_args();
(||{
- let agg = Aggregate::default();
+ let mut agg = Aggregate::default();
agg.read_toplevel(&opts.config)?;
for extra in &opts.extra_config {
eprintln!("GOT {:?}", agg);
- Ok::<_,AE>();
- }).context("read configuration");
+ Ok::<_,AE>(())
+ })().context("read configuration")?;
}