chiark / gitweb /
wip impl config reading, compiles
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 23 Jul 2021 11:46:03 +0000 (12:46 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 23 Jul 2021 11:46:03 +0000 (12:46 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
Cargo.lock
Cargo.toml
src/bin/client.rs
src/config.rs
src/lib.rs
src/prelude.rs
src/utils.rs [new file with mode: 0644]

index 91032b7276c33e2802fcf508a6f43880111a0bc2..b3abe388c36080d6f800ce803413beb0f17c441a 100644 (file)
@@ -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"
index 7ef89be915f2165f1da2839e7527e92de765c982..96415dae1d01a17bd4b8f64c009fccfbc27c36c2 100644 (file)
@@ -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"
index bf2d5969a25b93c4940c2bff96251a573757f21b..c9fc43a2f45f4e07c6733ddca872ccd2a7276c08 100644 (file)
@@ -6,5 +6,5 @@ use hippotat::prelude::*;
 
 #[throws(AE)]
 fn main() {
-  config::read();
+  config::read()?;
 }
index 1d6caf29813d9356aa9955ec292a30d6de14a0a2..4053c60189e311c8fc225042c6d48004ff13474d 100644 (file)
@@ -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<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"));
     }
@@ -97,59 +104,67 @@ impl Aggregate {
     // 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) => { },
           }
         }
 
@@ -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::<Void>)?;
+      }
     }
 
-    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")?;
 }
index acd51bd110b63523f5ba03743948150030784b6d..0667ee978352e61360bba1e66f66c852a6cef948 100644 (file)
@@ -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;
index 6783d35b41ed1af000360e3d67f7342bc702eb09..dbba79a15f29b5e1ad7b0603a52735d98144e77e 100644 (file)
@@ -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 (file)
index 0000000..317c840
--- /dev/null
@@ -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> T where T: Debug {
+  fn to_debug(&self) -> String { format!("{:?}", self) }
+}
+
+#[ext(pub)]
+impl<T,E> Result<T,E> where AE: From<E> {
+  fn dcontext<D:Debug>(self, d: D) -> anyhow::Result<T> {
+    self.map_err(|e| AE::from(e)).with_context(|| d.to_debug())
+  }
+}