Add graceful error handling to df script
This commit is contained in:
12
manager/Cargo.lock
generated
12
manager/Cargo.lock
generated
@ -120,6 +120,17 @@ dependencies = [
|
|||||||
"vec_map",
|
"vec_map",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compound-error"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "106f619aa16d817037b5820326364166e67c52d1a78eb6f37ba354ec82b617c5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -152,6 +163,7 @@ name = "dotfiles"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
|
"compound-error",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ futures = "0.3.13"
|
|||||||
async-recursion = "0.3.2"
|
async-recursion = "0.3.2"
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
templar = "0.5.0"
|
templar = "0.5.0"
|
||||||
|
compound-error = "0.1.2"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0.125"
|
version = "1.0.125"
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
|
use crate::error::{Error, ErrorLocation, Errors};
|
||||||
use crate::{ColorMode, Config};
|
use crate::{ColorMode, Config};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::ErrorKind;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use templar::{Context, InnerData, StandardContext, Templar};
|
use templar::{Context, InnerData, StandardContext, Templar};
|
||||||
use tokio::fs::{copy, create_dir, read_dir, read_to_string, write};
|
use tokio::fs::{copy, create_dir, read_dir, read_to_string, write};
|
||||||
use tokio::try_join;
|
use tokio::join;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct TemplateContext<'a> {
|
struct TemplateContext<'a> {
|
||||||
@ -18,10 +19,14 @@ struct TemplateContext<'a> {
|
|||||||
|
|
||||||
const TEMPLATE_EXTENSION: &str = "tpl";
|
const TEMPLATE_EXTENSION: &str = "tpl";
|
||||||
|
|
||||||
pub async fn build_tree(cfg: &Config) -> io::Result<()> {
|
pub async fn build_tree(cfg: &Config) -> Result<(), Errors> {
|
||||||
let tpl = Templar::global();
|
let tpl = Templar::global();
|
||||||
|
|
||||||
let hostname = read_to_string("/etc/hostname").await?;
|
let hostname_path: PathBuf = "/etc/hostname".into();
|
||||||
|
let hostname = read_to_string(&hostname_path)
|
||||||
|
.await
|
||||||
|
.with_location(&hostname_path)?;
|
||||||
|
|
||||||
let darkmode = cfg.color == ColorMode::Dark;
|
let darkmode = cfg.color == ColorMode::Dark;
|
||||||
let ctx = StandardContext::new();
|
let ctx = StandardContext::new();
|
||||||
ctx.set(
|
ctx.set(
|
||||||
@ -43,7 +48,7 @@ async fn dir(
|
|||||||
ctx: &StandardContext,
|
ctx: &StandardContext,
|
||||||
tpl: &Templar,
|
tpl: &Templar,
|
||||||
relative: PathBuf,
|
relative: PathBuf,
|
||||||
) -> io::Result<()> {
|
) -> Result<(), Errors> {
|
||||||
let template_path = cfg.template_dir.join(&relative);
|
let template_path = cfg.template_dir.join(&relative);
|
||||||
let build_path = cfg.build_dir.join(&relative);
|
let build_path = cfg.build_dir.join(&relative);
|
||||||
|
|
||||||
@ -52,16 +57,18 @@ async fn dir(
|
|||||||
match create_dir(&build_path).await {
|
match create_dir(&build_path).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
|
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e.with_location(&build_path).into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut walker = read_dir(&template_path).await?;
|
let mut walker = read_dir(&template_path)
|
||||||
|
.await
|
||||||
|
.with_location(&template_path)?;
|
||||||
|
|
||||||
let mut dir_tasks = vec![];
|
let mut dir_tasks = vec![];
|
||||||
let mut file_tasks = vec![];
|
let mut file_tasks = vec![];
|
||||||
|
|
||||||
while let Some(entry) = walker.next_entry().await? {
|
while let Some(entry) = walker.next_entry().await.with_location(&&template_path)? {
|
||||||
let meta = entry.metadata().await?;
|
let meta = entry.metadata().await.with_location(&entry.path())?;
|
||||||
let new_relative = relative.join(entry.file_name());
|
let new_relative = relative.join(entry.file_name());
|
||||||
|
|
||||||
if meta.is_dir() {
|
if meta.is_dir() {
|
||||||
@ -71,23 +78,25 @@ async fn dir(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dirs = async {
|
let dirs = async { join_all(dir_tasks).await.into_iter().collect::<Vec<_>>() };
|
||||||
join_all(dir_tasks)
|
let files = async { join_all(file_tasks).await.into_iter().collect::<Vec<_>>() };
|
||||||
.await
|
let (dirs, files) = join!(dirs, files);
|
||||||
.into_iter()
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
let files = async {
|
let mut errors: Errors = files
|
||||||
join_all(file_tasks)
|
.into_iter()
|
||||||
.await
|
.filter_map(|r| r.err())
|
||||||
.into_iter()
|
.collect::<Vec<_>>()
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.into();
|
||||||
};
|
|
||||||
|
|
||||||
try_join!(dirs, files)?;
|
for error in dirs.into_iter().filter_map(|r| r.err()) {
|
||||||
|
errors.join(error);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn file(
|
async fn file(
|
||||||
@ -95,31 +104,35 @@ async fn file(
|
|||||||
ctx: &StandardContext,
|
ctx: &StandardContext,
|
||||||
tpl: &Templar,
|
tpl: &Templar,
|
||||||
relative: PathBuf,
|
relative: PathBuf,
|
||||||
) -> io::Result<()> {
|
) -> Result<(), Error> {
|
||||||
let template_path = cfg.template_dir.join(&relative);
|
let template_path = cfg.template_dir.join(&relative);
|
||||||
let mut new_path = cfg.build_dir.join(&relative);
|
let mut new_path = cfg.build_dir.join(&relative);
|
||||||
|
|
||||||
info!("rendering {:?}", template_path);
|
info!("rendering {:?}", template_path);
|
||||||
let file_data = read_to_string(&template_path).await?;
|
|
||||||
|
|
||||||
if template_path.extension() == Some(OsStr::new(TEMPLATE_EXTENSION)) {
|
if template_path.extension() == Some(OsStr::new(TEMPLATE_EXTENSION)) {
|
||||||
// perform templating
|
// perform templating
|
||||||
// TODO: error handling
|
let file_str = read_to_string(&template_path)
|
||||||
|
.await
|
||||||
|
.with_location(&template_path)?;
|
||||||
|
|
||||||
let rendered = tpl
|
let rendered = tpl
|
||||||
.parse_template(&file_data)
|
.parse_template(&file_str)
|
||||||
.expect("failed to parse template")
|
.with_location(&template_path)?
|
||||||
.render(ctx)
|
.render(ctx)
|
||||||
.expect("failed to render template");
|
.with_location(&template_path)?;
|
||||||
|
|
||||||
// remove template file extension
|
// remove template file extension
|
||||||
new_path.set_extension("");
|
new_path.set_extension("");
|
||||||
|
|
||||||
// write the rendered file
|
// write the rendered file
|
||||||
write(&new_path, &rendered).await?;
|
write(&new_path, &rendered).await.with_location(&new_path)?;
|
||||||
} else {
|
} else {
|
||||||
// else just copy the file
|
// else just copy the file
|
||||||
info!("copying {:?}", template_path);
|
info!("copying {:?}", template_path);
|
||||||
copy(&template_path, &new_path).await?;
|
copy(&template_path, &new_path)
|
||||||
|
.await
|
||||||
|
.with_location(&template_path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
91
manager/src/error.rs
Normal file
91
manager/src/error.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use compound_error::CompoundError;
|
||||||
|
use std::io;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Errors {
|
||||||
|
errors: Vec<Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Error {
|
||||||
|
location: PathBuf,
|
||||||
|
inner: InnerError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(CompoundError, Debug)]
|
||||||
|
pub enum InnerError {
|
||||||
|
IoErr(io::Error),
|
||||||
|
TemplateErr(templar::TemplarError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<Error>> for Errors {
|
||||||
|
fn from(errors: Vec<Error>) -> Self {
|
||||||
|
Errors { errors }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<E> for Errors
|
||||||
|
where
|
||||||
|
E: Into<Error>,
|
||||||
|
{
|
||||||
|
fn from(error: E) -> Self {
|
||||||
|
Errors {
|
||||||
|
errors: vec![error.into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Errors {
|
||||||
|
pub fn join(&mut self, mut other: Errors) {
|
||||||
|
self.errors.append(&mut other.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.errors.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(self) {
|
||||||
|
if self.errors.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error!("{} errors occured:", self.errors.len());
|
||||||
|
for (i, error) in self.errors.iter().enumerate() {
|
||||||
|
error!("{:.2}. {:?}", i, error.location);
|
||||||
|
error!(" {:?}", error.inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ErrorLocation {
|
||||||
|
type Err;
|
||||||
|
fn with_location(self, path: &Path) -> Self::Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ErrorLocation for T
|
||||||
|
where
|
||||||
|
T: Into<InnerError>,
|
||||||
|
{
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn with_location(self, path: &Path) -> Error {
|
||||||
|
Error {
|
||||||
|
location: path.to_owned(),
|
||||||
|
inner: self.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> ErrorLocation for Result<T, E>
|
||||||
|
where
|
||||||
|
E: Into<InnerError>,
|
||||||
|
{
|
||||||
|
type Err = Result<T, Error>;
|
||||||
|
|
||||||
|
fn with_location(self, path: &Path) -> Result<T, Error> {
|
||||||
|
self.map_err(|e| Error {
|
||||||
|
location: path.to_owned(),
|
||||||
|
inner: e.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,18 @@
|
|||||||
|
use crate::error::{Error, ErrorLocation, Errors};
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::ErrorKind;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::fs::{create_dir, read_dir, remove_file, symlink};
|
use tokio::fs::{create_dir, read_dir, remove_file, symlink};
|
||||||
use tokio::try_join;
|
use tokio::join;
|
||||||
|
|
||||||
pub async fn link_tree(cfg: &Config) -> io::Result<()> {
|
pub async fn link_tree(cfg: &Config) -> Result<(), Errors> {
|
||||||
dir(cfg, PathBuf::new()).await
|
dir(cfg, PathBuf::new()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
async fn dir(cfg: &Config, relative: PathBuf) -> io::Result<()> {
|
async fn dir(cfg: &Config, relative: PathBuf) -> Result<(), Errors> {
|
||||||
let build_path = cfg.build_dir.join(&relative);
|
let build_path = cfg.build_dir.join(&relative);
|
||||||
let link_path = cfg.link_dir.join(&relative);
|
let link_path = cfg.link_dir.join(&relative);
|
||||||
|
|
||||||
@ -20,16 +21,16 @@ async fn dir(cfg: &Config, relative: PathBuf) -> io::Result<()> {
|
|||||||
match create_dir(&link_path).await {
|
match create_dir(&link_path).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
|
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e.with_location(&link_path).into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut walker = read_dir(&build_path).await?;
|
let mut walker = read_dir(&build_path).await.with_location(&build_path)?;
|
||||||
|
|
||||||
let mut dir_tasks = vec![];
|
let mut dir_tasks = vec![];
|
||||||
let mut file_tasks = vec![];
|
let mut file_tasks = vec![];
|
||||||
|
|
||||||
while let Some(entry) = walker.next_entry().await? {
|
while let Some(entry) = walker.next_entry().await.with_location(&build_path)? {
|
||||||
let meta = entry.metadata().await?;
|
let meta = entry.metadata().await.with_location(&entry.path())?;
|
||||||
let new_relative = relative.join(entry.file_name());
|
let new_relative = relative.join(entry.file_name());
|
||||||
|
|
||||||
if meta.is_dir() {
|
if meta.is_dir() {
|
||||||
@ -39,26 +40,28 @@ async fn dir(cfg: &Config, relative: PathBuf) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dirs = async {
|
let dirs = async { join_all(dir_tasks).await.into_iter().collect::<Vec<_>>() };
|
||||||
join_all(dir_tasks)
|
let files = async { join_all(file_tasks).await.into_iter().collect::<Vec<_>>() };
|
||||||
.await
|
let (dirs, files) = join!(dirs, files);
|
||||||
.into_iter()
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
let files = async {
|
let mut errors: Errors = files
|
||||||
join_all(file_tasks)
|
.into_iter()
|
||||||
.await
|
.filter_map(|r| r.err())
|
||||||
.into_iter()
|
.collect::<Vec<_>>()
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.into();
|
||||||
};
|
|
||||||
|
|
||||||
try_join!(dirs, files)?;
|
for error in dirs.into_iter().filter_map(|r| r.err()) {
|
||||||
|
errors.join(error);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn file(cfg: &Config, relative: PathBuf) -> io::Result<()> {
|
async fn file(cfg: &Config, relative: PathBuf) -> Result<(), Error> {
|
||||||
let build_path = cfg.build_dir.join(&relative);
|
let build_path = cfg.build_dir.join(&relative);
|
||||||
let link_path = cfg.link_dir.join(&relative);
|
let link_path = cfg.link_dir.join(&relative);
|
||||||
|
|
||||||
@ -67,7 +70,7 @@ async fn file(cfg: &Config, relative: PathBuf) -> io::Result<()> {
|
|||||||
info!("removed existing file {:?}", link_path);
|
info!("removed existing file {:?}", link_path);
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {}
|
Err(e) if e.kind() == ErrorKind::NotFound => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e.with_location(&link_path)),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("linking {:?} to {:?}", link_path, build_path);
|
info!("linking {:?} to {:?}", link_path, build_path);
|
||||||
@ -85,7 +88,9 @@ async fn file(cfg: &Config, relative: PathBuf) -> io::Result<()> {
|
|||||||
relative_symlink
|
relative_symlink
|
||||||
};
|
};
|
||||||
|
|
||||||
symlink(symlink_content, link_path).await?;
|
symlink(symlink_content, &link_path)
|
||||||
|
.await
|
||||||
|
.with_location(&link_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,16 @@
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
|
mod error;
|
||||||
mod linker;
|
mod linker;
|
||||||
|
|
||||||
use builder::build_tree;
|
use builder::build_tree;
|
||||||
|
use error::Errors;
|
||||||
use linker::link_tree;
|
use linker::link_tree;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio::io;
|
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
@ -45,7 +46,14 @@ pub enum ColorMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() {
|
||||||
|
match run().await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(errors) => errors.log(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() -> Result<(), Errors> {
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
let filter_level = match opt.verbosity {
|
let filter_level = match opt.verbosity {
|
||||||
|
|||||||
Reference in New Issue
Block a user