From 9147fa52fda7a1a06629ad9be01a294458d1c7a7 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Fri, 23 Jul 2021 12:46:03 +0100 Subject: [PATCH] wip impl config reading, compiles Signed-off-by: Ian Jackson --- Cargo.lock | 20 ++++++++ Cargo.toml | 4 +- src/bin/client.rs | 2 +- src/config.rs | 128 ++++++++++++++++++++++++++-------------------- src/lib.rs | 3 ++ src/prelude.rs | 19 +++++-- src/utils.rs | 17 ++++++ 7 files changed, 133 insertions(+), 60 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 91032b7..b3abe38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,6 +95,18 @@ dependencies = [ "termcolor", ] +[[package]] +name = "extend" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c89e2933a4ec753dc007a4d6a7f9b6dc8e89b8fe89cabc252ccddf39c08bb1" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fehler" version = "1.0.0" @@ -180,11 +192,13 @@ dependencies = [ "anyhow", "configparser", "env_logger", + "extend", "fehler", "hyper", "log", "structopt", "tokio", + "void", ] [[package]] @@ -625,6 +639,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 7ef89be..96415da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,10 @@ description="Asinine HTTP-over-IP" anyhow = "1" configparser = "2" env_logger = "0.9" +extend = "1" fehler = "1" hyper = "0.14" -log="0.4" +log = "0.4" structopt = "0.3" tokio = { version = "1", features = ["full"] } +void = "1" diff --git a/src/bin/client.rs b/src/bin/client.rs index bf2d596..c9fc43a 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -6,5 +6,5 @@ use hippotat::prelude::*; #[throws(AE)] fn main() { - config::read(); + config::read()?; } diff --git a/src/config.rs b/src/config.rs index 1d6caf2..4053c60 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,8 +6,6 @@ use crate::prelude::*; 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 @@ -67,28 +65,37 @@ pub struct Config { static OUTSIDE_SECTION: &str = "["; +#[derive(Default,Debug)] struct Aggregate { sections: HashMap, } +type OkAnyway<'f,A> = &'f dyn Fn(ErrorKind) -> Option; +#[ext] +impl<'f,A> OkAnyway<'f,A> { + fn ok(self, r: &Result) -> Option { + 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(&mut self, path: &Path, anyway: OkAnyway) -> Option { - 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")); } @@ -97,59 +104,67 @@ impl Aggregate { // xxx save Arc 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(&mut self, path: &Path, anyway: OkAnyway) -> Option { - 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::).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::(); + let mk = |leaf: &str| { + [ toplevel, &PathBuf::from(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)? + + match self.read_file(&main, &anyway_none) + .dcontext(main).context(desc)? { - Ok(()) => break, - Err(EK::NotFound) => { }, - x => panic!("huh? {:?}", &x), + None => break, + Some(AnywayNone) => { }, } } @@ -158,10 +173,9 @@ impl Aggregate { ("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) => { }, } } } @@ -170,16 +184,20 @@ impl Aggregate { #[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::)?; + } } - self.read_dir(extra, |_| false)??; } } @@ -189,7 +207,7 @@ pub fn read() { 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 { @@ -198,6 +216,6 @@ pub fn read() { eprintln!("GOT {:?}", agg); - Ok::<_,AE>(); - }).context("read configuration"); + Ok::<_,AE>(()) + })().context("read configuration")?; } diff --git a/src/lib.rs b/src/lib.rs index acd51bd..0667ee9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // There is NO WARRANTY. +#![feature(io_error_more)] // EK::IsADirectory + pub mod prelude; pub mod config; +pub mod utils; diff --git a/src/prelude.rs b/src/prelude.rs index 6783d35..dbba79a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,11 +2,24 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // There is NO WARRANTY. +pub use std::collections::HashMap; +pub use std::fs; +pub use std::fmt::Debug; +pub use std::io::{self, ErrorKind, Read as _}; +pub use std::mem; +pub use std::net::IpAddr; +pub use std::path::{Path, PathBuf}; + +pub use anyhow::{anyhow, Context}; +pub use extend::ext; +pub use fehler::{throw, throws}; pub use hyper::Uri; pub use structopt::StructOpt; pub use tokio::time::Duration; - -pub use std::net::IpAddr; -pub use std::path::PathBuf; +pub use void::{self, Void}; pub use crate::config; +pub use crate::utils::*; + +pub use anyhow::Error as AE; +pub use ErrorKind as EK; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..317c840 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,17 @@ +// Copyright 2021 Ian Jackson and contributors to Hippotat +// SPDX-License-Identifier: AGPL-3.0-or-later +// There is NO WARRANTY. + +use crate::prelude::*; + +#[ext(pub)] +impl T where T: Debug { + fn to_debug(&self) -> String { format!("{:?}", self) } +} + +#[ext(pub)] +impl Result where AE: From { + fn dcontext(self, d: D) -> anyhow::Result { + self.map_err(|e| AE::from(e)).with_context(|| d.to_debug()) + } +} -- 2.30.2