Better error model

This commit is contained in:
Roey Darwish Dror
2018-12-11 16:43:26 +02:00
parent f23b6435bf
commit 370310948b
16 changed files with 216 additions and 124 deletions

View File

@@ -1,5 +1,6 @@
use super::error::{Error, ErrorKind};
use directories::BaseDirs; use directories::BaseDirs;
use failure; use failure::ResultExt;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use shellexpand; use shellexpand;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@@ -17,13 +18,14 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn read(base_dirs: &BaseDirs) -> Result<Config, failure::Error> { pub fn read(base_dirs: &BaseDirs) -> Result<Config, Error> {
let config_path = base_dirs.config_dir().join("topgrade.toml"); let config_path = base_dirs.config_dir().join("topgrade.toml");
if !config_path.exists() { if !config_path.exists() {
return Ok(Default::default()); return Ok(Default::default());
} }
let mut result: Self = toml::from_str(&fs::read_to_string(config_path)?)?; let mut result: Self = toml::from_str(&fs::read_to_string(config_path).context(ErrorKind::Configuration)?)
.context(ErrorKind::Configuration)?;
if let Some(ref mut paths) = &mut result.git_repos { if let Some(ref mut paths) = &mut result.git_repos {
for path in paths.iter_mut() { for path in paths.iter_mut() {

80
src/error.rs Normal file
View File

@@ -0,0 +1,80 @@
use failure::{Backtrace, Context, Fail};
use std::fmt::{self, Display};
use std::process::ExitStatus;
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "Error asking the user for retry")]
Retry,
#[fail(display = "Cannot find the user base directories")]
NoBaseDirectories,
#[fail(display = "A step failed")]
StepFailed,
#[fail(display = "Error reading the configuration")]
Configuration,
#[fail(display = "A custom pre-command failed")]
PreCommand,
#[fail(display = "{}", _0)]
ProcessFailed(ExitStatus),
#[fail(display = "Unknown Linux Distribution")]
#[cfg(target_os = "linux")]
UnknownLinuxDistribution,
#[fail(display = "Detected Python is not the system Python")]
#[cfg(target_os = "linux")]
NotSystemPython,
#[fail(display = "Process execution failure")]
ProcessExecution,
#[fail(display = "Self-update failure")]
#[cfg(feature = "self-update")]
SelfUpdate,
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
pub fn kind(&self) -> ErrorKind {
*self.inner.get_context()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error {
inner: Context::new(kind),
}
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner }
}
}

View File

@@ -1,7 +1,7 @@
use super::error::{Error, ErrorKind};
use super::utils::Check; use super::utils::Check;
use failure; use failure::ResultExt;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::io;
use std::path::Path; use std::path::Path;
use std::process::{Child, Command, ExitStatus}; use std::process::{Child, Command, ExitStatus};
@@ -63,9 +63,9 @@ impl Executor {
self self
} }
pub fn spawn(&mut self) -> Result<ExecutorChild, io::Error> { pub fn spawn(&mut self) -> Result<ExecutorChild, Error> {
match self { let result = match self {
Executor::Wet(c) => c.spawn().map(ExecutorChild::Wet), Executor::Wet(c) => c.spawn().context(ErrorKind::ProcessExecution).map(ExecutorChild::Wet)?,
Executor::Dry(c) => { Executor::Dry(c) => {
print!( print!(
"Dry running: {} {}", "Dry running: {} {}",
@@ -80,9 +80,11 @@ impl Executor {
Some(dir) => println!(" in {}", dir.to_string_lossy()), Some(dir) => println!(" in {}", dir.to_string_lossy()),
None => println!(), None => println!(),
}; };
Ok(ExecutorChild::Dry) ExecutorChild::Dry
}
} }
};
Ok(result)
} }
} }
@@ -99,11 +101,16 @@ pub enum ExecutorChild {
} }
impl ExecutorChild { impl ExecutorChild {
pub fn wait(&mut self) -> Result<ExecutorExitStatus, io::Error> { pub fn wait(&mut self) -> Result<ExecutorExitStatus, Error> {
match self { let result = match self {
ExecutorChild::Wet(c) => c.wait().map(ExecutorExitStatus::Wet), ExecutorChild::Wet(c) => c
ExecutorChild::Dry => Ok(ExecutorExitStatus::Dry), .wait()
} .context(ErrorKind::ProcessExecution)
.map(ExecutorExitStatus::Wet)?,
ExecutorChild::Dry => ExecutorExitStatus::Dry,
};
Ok(result)
} }
} }
@@ -113,7 +120,7 @@ pub enum ExecutorExitStatus {
} }
impl Check for ExecutorExitStatus { impl Check for ExecutorExitStatus {
fn check(self) -> Result<(), failure::Error> { fn check(self) -> Result<(), Error> {
match self { match self {
ExecutorExitStatus::Wet(e) => e.check(), ExecutorExitStatus::Wet(e) => e.check(),
ExecutorExitStatus::Dry => Ok(()), ExecutorExitStatus::Dry => Ok(()),

View File

@@ -1,7 +1,8 @@
use super::error::{Error, ErrorKind};
use super::executor::Executor; use super::executor::Executor;
use super::terminal::{print_separator, print_warning}; use super::terminal::{print_separator, print_warning};
use super::utils::Check; use super::utils::Check;
use failure; use failure::ResultExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@@ -10,7 +11,7 @@ pub fn upgrade_freebsd(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&'stati
print_separator("FreeBSD Update"); print_separator("FreeBSD Update");
if let Some(sudo) = sudo { if let Some(sudo) = sudo {
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(sudo, dry_run) Executor::new(sudo, dry_run)
.args(&["/usr/sbin/freebsd-update", "fetch", "install"]) .args(&["/usr/sbin/freebsd-update", "fetch", "install"])
.spawn()? .spawn()?
@@ -32,7 +33,7 @@ pub fn upgrade_packages(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&'stat
print_separator("FreeBSD Packages"); print_separator("FreeBSD Packages");
if let Some(sudo) = sudo { if let Some(sudo) = sudo {
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(sudo, dry_run) Executor::new(sudo, dry_run)
.args(&["/usr/sbin/pkg", "upgrade"]) .args(&["/usr/sbin/pkg", "upgrade"])
.spawn()? .spawn()?
@@ -49,13 +50,15 @@ pub fn upgrade_packages(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&'stat
} }
} }
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<(), failure::Error> { pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<(), Error> {
if let Some(sudo) = sudo { if let Some(sudo) = sudo {
println!(); println!();
Command::new(sudo) Command::new(sudo)
.args(&["/usr/sbin/pkg", "audit", "-Fr"]) .args(&["/usr/sbin/pkg", "audit", "-Fr"])
.spawn()? .spawn()
.wait()?; .context(ErrorKind::ProcessExecution)?
.wait()
.context(ErrorKind::ProcessExecution)?;
} }
Ok(()) Ok(())
} }

View File

@@ -1,8 +1,9 @@
use super::error::{Error, ErrorKind};
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::{self, Check, PathExt}; use super::utils::{self, Check, PathExt};
use directories::BaseDirs; use directories::BaseDirs;
use failure::Error; use failure::ResultExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@@ -229,9 +230,12 @@ pub fn run_composer_update(base_dirs: &BaseDirs, dry_run: bool) -> Option<(&'sta
let composer_home = || -> Result<PathBuf, Error> { let composer_home = || -> Result<PathBuf, Error> {
let output = Command::new(&composer) let output = Command::new(&composer)
.args(&["global", "config", "--absolute", "home"]) .args(&["global", "config", "--absolute", "home"])
.output()?; .output()
.context(ErrorKind::ProcessExecution)?;
output.status.check()?; output.status.check()?;
Ok(PathBuf::from(&String::from_utf8(output.stdout)?)) Ok(PathBuf::from(
&String::from_utf8(output.stdout).context(ErrorKind::ProcessExecution)?,
))
}(); }();
if let Ok(composer_home) = composer_home { if let Ok(composer_home) = composer_home {

View File

@@ -1,7 +1,7 @@
use super::error::Error;
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::{which, Check}; use super::utils::{which, Check};
use failure::Error;
use log::{debug, error}; use log::{debug, error};
use std::collections::HashSet; use std::collections::HashSet;
use std::io; use std::io;

View File

@@ -1,8 +1,8 @@
use super::error::{Error, ErrorKind};
use super::executor::Executor; use super::executor::Executor;
use super::terminal::{print_separator, print_warning}; use super::terminal::{print_separator, print_warning};
use super::utils::{which, Check}; use super::utils::{which, Check};
use failure; use failure::ResultExt;
use failure_derive::Fail;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use walkdir::WalkDir; use walkdir::WalkDir;
@@ -19,17 +19,9 @@ pub enum Distribution {
Void, Void,
} }
#[derive(Debug, Fail)]
#[fail(display = "Unknown Linux Distribution")]
struct UnknownLinuxDistribution;
#[derive(Debug, Fail)]
#[fail(display = "Detected Python is not the system Python")]
struct NotSystemPython;
impl Distribution { impl Distribution {
pub fn detect() -> Result<Self, failure::Error> { pub fn detect() -> Result<Self, Error> {
let content = fs::read_to_string("/etc/os-release")?; let content = fs::read_to_string("/etc/os-release").context(ErrorKind::UnknownLinuxDistribution)?;
if content.contains("Arch") | content.contains("Manjaro") | content.contains("Antergos") { if content.contains("Arch") | content.contains("Manjaro") | content.contains("Antergos") {
return Ok(Distribution::Arch); return Ok(Distribution::Arch);
@@ -63,7 +55,7 @@ impl Distribution {
return Ok(Distribution::Gentoo); return Ok(Distribution::Gentoo);
} }
Err(UnknownLinuxDistribution.into()) Err(ErrorKind::UnknownLinuxDistribution)?
} }
#[must_use] #[must_use]
@@ -111,7 +103,7 @@ pub fn show_pacnew() {
} }
} }
fn upgrade_arch_linux(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_arch_linux(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(yay) = which("yay") { if let Some(yay) = which("yay") {
if let Some(python) = which("python") { if let Some(python) = which("python") {
if python != PathBuf::from("/usr/bin/python") { if python != PathBuf::from("/usr/bin/python") {
@@ -120,7 +112,7 @@ fn upgrade_arch_linux(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failu
It's dangerous to run yay since Python based AUR packages will be installed in the wrong location", It's dangerous to run yay since Python based AUR packages will be installed in the wrong location",
python python
)); ));
return Err(NotSystemPython.into()); return Err(ErrorKind::NotSystemPython)?;
} }
} }
@@ -138,7 +130,7 @@ It's dangerous to run yay since Python based AUR packages will be installed in t
Ok(()) Ok(())
} }
fn upgrade_redhat(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_redhat(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(sudo) = &sudo { if let Some(sudo) = &sudo {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&["/usr/bin/yum", "upgrade"]) .args(&["/usr/bin/yum", "upgrade"])
@@ -152,7 +144,7 @@ fn upgrade_redhat(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::
Ok(()) Ok(())
} }
fn upgrade_opensuse(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_opensuse(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(sudo) = &sudo { if let Some(sudo) = &sudo {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&["/usr/bin/zypper", "refresh"]) .args(&["/usr/bin/zypper", "refresh"])
@@ -172,7 +164,7 @@ fn upgrade_opensuse(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure
Ok(()) Ok(())
} }
fn upgrade_void(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_void(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(sudo) = &sudo { if let Some(sudo) = &sudo {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&["/usr/bin/xbps-install", "-Su"]) .args(&["/usr/bin/xbps-install", "-Su"])
@@ -186,7 +178,7 @@ fn upgrade_void(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Er
Ok(()) Ok(())
} }
fn upgrade_fedora(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_fedora(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(sudo) = &sudo { if let Some(sudo) = &sudo {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&["/usr/bin/dnf", "upgrade"]) .args(&["/usr/bin/dnf", "upgrade"])
@@ -200,7 +192,7 @@ fn upgrade_fedora(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::
Ok(()) Ok(())
} }
fn upgrade_gentoo(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_gentoo(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(sudo) = &sudo { if let Some(sudo) = &sudo {
if let Some(layman) = which("layman") { if let Some(layman) = which("layman") {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
@@ -236,7 +228,7 @@ fn upgrade_gentoo(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::
Ok(()) Ok(())
} }
fn upgrade_debian(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), failure::Error> { fn upgrade_debian(sudo: &Option<PathBuf>, dry_run: bool) -> Result<(), Error> {
if let Some(sudo) = &sudo { if let Some(sudo) = &sudo {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&["/usr/bin/apt", "update"]) .args(&["/usr/bin/apt", "update"])
@@ -262,7 +254,7 @@ pub fn run_needrestart(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&'stati
if let Some(needrestart) = which("needrestart") { if let Some(needrestart) = which("needrestart") {
print_separator("Check for needed restarts"); print_separator("Check for needed restarts");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.arg(needrestart) .arg(needrestart)
.spawn()? .spawn()?
@@ -285,7 +277,7 @@ pub fn run_fwupdmgr(dry_run: bool) -> Option<(&'static str, bool)> {
if let Some(fwupdmgr) = which("fwupdmgr") { if let Some(fwupdmgr) = which("fwupdmgr") {
print_separator("Firmware upgrades"); print_separator("Firmware upgrades");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&fwupdmgr, dry_run) Executor::new(&fwupdmgr, dry_run)
.arg("refresh") .arg("refresh")
.spawn()? .spawn()?
@@ -311,7 +303,7 @@ pub fn flatpak_user_update(dry_run: bool) -> Option<(&'static str, bool)> {
if let Some(flatpak) = which("flatpak") { if let Some(flatpak) = which("flatpak") {
print_separator("Flatpak User Packages"); print_separator("Flatpak User Packages");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&flatpak, dry_run) Executor::new(&flatpak, dry_run)
.args(&["update", "--user", "-y"]) .args(&["update", "--user", "-y"])
.spawn()? .spawn()?
@@ -333,7 +325,7 @@ pub fn flatpak_global_update(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&
if let Some(flatpak) = which("flatpak") { if let Some(flatpak) = which("flatpak") {
print_separator("Flatpak Global Packages"); print_separator("Flatpak Global Packages");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&[flatpak.to_str().unwrap(), "update", "-y"]) .args(&[flatpak.to_str().unwrap(), "update", "-y"])
.spawn()? .spawn()?
@@ -357,7 +349,7 @@ pub fn run_snap(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&'static str,
if PathBuf::from("/var/snapd.socket").exists() { if PathBuf::from("/var/snapd.socket").exists() {
print_separator("snap"); print_separator("snap");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.args(&[snap.to_str().unwrap(), "refresh"]) .args(&[snap.to_str().unwrap(), "refresh"])
.spawn()? .spawn()?
@@ -382,7 +374,7 @@ pub fn run_etc_update(sudo: &Option<PathBuf>, dry_run: bool) -> Option<(&'static
if let Some(etc_update) = which("etc-update") { if let Some(etc_update) = which("etc-update") {
print_separator("etc-update"); print_separator("etc-update");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&sudo, dry_run) Executor::new(&sudo, dry_run)
.arg(&etc_update.to_str().unwrap()) .arg(&etc_update.to_str().unwrap())
.spawn()? .spawn()?

View File

@@ -1,13 +1,13 @@
use super::error::Error;
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::Check; use super::utils::Check;
use failure;
#[must_use] #[must_use]
pub fn upgrade_macos(dry_run: bool) -> Option<(&'static str, bool)> { pub fn upgrade_macos(dry_run: bool) -> Option<(&'static str, bool)> {
print_separator("App Store"); print_separator("App Store");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new("softwareupdate", dry_run) Executor::new("softwareupdate", dry_run)
.args(&["--install", "--all"]) .args(&["--install", "--all"])
.spawn()? .spawn()?

View File

@@ -32,6 +32,7 @@ mod windows;
mod config; mod config;
mod ctrlc; mod ctrlc;
mod error;
mod executor; mod executor;
mod generic; mod generic;
mod git; mod git;
@@ -44,29 +45,17 @@ mod utils;
mod vim; mod vim;
use self::config::Config; use self::config::Config;
use self::error::{Error, ErrorKind};
use self::git::{Git, Repositories}; use self::git::{Git, Repositories};
use self::report::Report; use self::report::Report;
use failure::Error; use failure::{Fail, ResultExt};
use failure_derive::Fail;
use std::borrow::Cow; use std::borrow::Cow;
use std::env; use std::env;
use std::io::ErrorKind; use std::io;
use std::process::exit; use std::process::exit;
use structopt::StructOpt; use structopt::StructOpt;
use terminal::*; use terminal::*;
#[derive(Fail, Debug)]
#[fail(display = "A step failed")]
struct StepFailed;
#[derive(Fail, Debug)]
#[fail(display = "Cannot find the user base directories")]
struct NoBaseDirectories;
#[derive(Fail, Debug)]
#[fail(display = "Process Interrupted")]
pub struct Interrupted;
fn execute<'a, F, M>(func: F, no_retry: bool) -> Result<Option<(M, bool)>, Error> fn execute<'a, F, M>(func: F, no_retry: bool) -> Result<Option<(M, bool)>, Error>
where where
M: Into<Cow<'a, str>>, M: Into<Cow<'a, str>>,
@@ -83,14 +72,7 @@ where
} }
let should_ask = !running || !no_retry; let should_ask = !running || !no_retry;
let should_retry = should_ask let should_retry = should_ask && should_retry(running).context(ErrorKind::Retry)?;
&& should_retry(running).map_err(|e| {
if e.kind() == ErrorKind::Interrupted {
Error::from(Interrupted)
} else {
Error::from(e)
}
})?;
if !should_retry { if !should_retry {
return Ok(Some((key, success))); return Ok(Some((key, success)));
@@ -114,7 +96,7 @@ fn run() -> Result<(), Error> {
env_logger::init(); env_logger::init();
let base_dirs = directories::BaseDirs::new().ok_or(NoBaseDirectories)?; let base_dirs = directories::BaseDirs::new().ok_or(ErrorKind::NoBaseDirectories)?;
let git = Git::new(); let git = Git::new();
let mut git_repos = Repositories::new(&git); let mut git_repos = Repositories::new(&git);
@@ -135,7 +117,7 @@ fn run() -> Result<(), Error> {
if let Some(commands) = config.pre_commands() { if let Some(commands) = config.pre_commands() {
for (name, command) in commands { for (name, command) in commands {
generic::run_custom_command(&name, &command, opt.dry_run)?; generic::run_custom_command(&name, &command, opt.dry_run).context(ErrorKind::PreCommand)?;
} }
} }
@@ -322,7 +304,7 @@ fn run() -> Result<(), Error> {
if report.data().iter().all(|(_, succeeded)| *succeeded) { if report.data().iter().all(|(_, succeeded)| *succeeded) {
Ok(()) Ok(())
} else { } else {
Err(StepFailed.into()) Err(ErrorKind::StepFailed)?
} }
} }
@@ -332,9 +314,21 @@ fn main() {
exit(0); exit(0);
} }
Err(error) => { Err(error) => {
if (error.downcast_ref::<StepFailed>().is_some()) || (error.downcast_ref::<Interrupted>().is_some()) { let should_print = match error.kind() {
} else { ErrorKind::StepFailed => false,
println!("ERROR: {}", error) ErrorKind::Retry => error
.cause()
.and_then(|cause| cause.downcast_ref::<io::Error>())
.filter(|io_error| io_error.kind() == io::ErrorKind::Interrupted)
.is_none(),
_ => true,
};
if should_print {
println!("Error: {}", error);
if let Some(cause) = error.cause() {
println!("Caused by: {}", cause);
}
} }
exit(1); exit(1);
} }

View File

@@ -1,8 +1,9 @@
use super::error::{Error, ErrorKind};
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::{which, Check, PathExt}; use super::utils::{which, Check, PathExt};
use directories::BaseDirs; use directories::BaseDirs;
use failure; use failure::ResultExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@@ -15,15 +16,20 @@ impl NPM {
Self { command } Self { command }
} }
fn root(&self) -> Result<PathBuf, failure::Error> { fn root(&self) -> Result<PathBuf, Error> {
let output = Command::new(&self.command).args(&["root", "-g"]).output()?; let output = Command::new(&self.command)
.args(&["root", "-g"])
.output()
.context(ErrorKind::ProcessExecution)?;
output.status.check()?; output.status.check()?;
Ok(PathBuf::from(&String::from_utf8(output.stdout)?)) Ok(PathBuf::from(
&String::from_utf8(output.stdout).context(ErrorKind::ProcessExecution)?,
))
} }
fn upgrade(&self, dry_run: bool) -> Result<(), failure::Error> { fn upgrade(&self, dry_run: bool) -> Result<(), Error> {
Executor::new(&self.command, dry_run) Executor::new(&self.command, dry_run)
.args(&["update", "-g"]) .args(&["update", "-g"])
.spawn()? .spawn()?
@@ -53,7 +59,7 @@ pub fn yarn_global_update(dry_run: bool) -> Option<(&'static str, bool)> {
if let Some(yarn) = which("yarn") { if let Some(yarn) = which("yarn") {
print_separator("Yarn"); print_separator("Yarn");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&yarn, dry_run) Executor::new(&yarn, dry_run)
.args(&["global", "upgrade", "-s"]) .args(&["global", "upgrade", "-s"])
.spawn()? .spawn()?

View File

@@ -1,5 +1,6 @@
use super::error::{Error, ErrorKind};
use super::terminal::*; use super::terminal::*;
use failure::Error; use failure::ResultExt;
use self_update_crate; use self_update_crate;
#[cfg(unix)] #[cfg(unix)]
use std::env; use std::env;
@@ -13,8 +14,9 @@ pub fn self_update() -> Result<(), Error> {
#[cfg(unix)] #[cfg(unix)]
let current_exe = env::current_exe(); let current_exe = env::current_exe();
let target = self_update_crate::get_target()?; let target = self_update_crate::get_target().context(ErrorKind::SelfUpdate)?;
let result = self_update_crate::backends::github::Update::configure()? let result = self_update_crate::backends::github::Update::configure()
.context(ErrorKind::SelfUpdate)?
.repo_owner("r-darwish") .repo_owner("r-darwish")
.repo_name("topgrade") .repo_name("topgrade")
.target(&target) .target(&target)
@@ -23,8 +25,10 @@ pub fn self_update() -> Result<(), Error> {
.show_download_progress(true) .show_download_progress(true)
.current_version(self_update_crate::cargo_crate_version!()) .current_version(self_update_crate::cargo_crate_version!())
.no_confirm(true) .no_confirm(true)
.build()? .build()
.update()?; .context(ErrorKind::SelfUpdate)?
.update()
.context(ErrorKind::SelfUpdate)?;
if let self_update_crate::Status::Updated(version) = &result { if let self_update_crate::Status::Updated(version) = &result {
println!("\nTopgrade upgraded to {}", version); println!("\nTopgrade upgraded to {}", version);
@@ -36,8 +40,10 @@ pub fn self_update() -> Result<(), Error> {
{ {
if result.updated() { if result.updated() {
print_warning("Respawning..."); print_warning("Respawning...");
let err = Command::new(current_exe?).args(env::args().skip(1)).exec(); let err = Command::new(current_exe.context(ErrorKind::SelfUpdate)?)
Err(err)? .args(env::args().skip(1))
.exec();
Err(err).context(ErrorKind::SelfUpdate)?
} }
} }

View File

@@ -1,9 +1,10 @@
use super::error::{Error, ErrorKind};
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::which; use super::utils::which;
use super::utils::{Check, PathExt}; use super::utils::{Check, PathExt};
use directories::BaseDirs; use directories::BaseDirs;
use failure::Error; use failure::ResultExt;
use std::env; use std::env;
use std::io; use std::io;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
@@ -41,8 +42,10 @@ fn has_session(tmux: &Path, session_name: &str) -> Result<bool, io::Error> {
fn run_in_session(tmux: &Path, command: &str) -> Result<(), Error> { fn run_in_session(tmux: &Path, command: &str) -> Result<(), Error> {
Command::new(tmux) Command::new(tmux)
.args(&["new-window", "-a", "-t", "topgrade:1", command]) .args(&["new-window", "-a", "-t", "topgrade:1", command])
.spawn()? .spawn()
.wait()? .context(ErrorKind::ProcessExecution)?
.wait()
.context(ErrorKind::ProcessExecution)?
.check()?; .check()?;
Ok(()) Ok(())

View File

@@ -2,7 +2,7 @@ use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::{which, Check}; use super::utils::{which, Check};
use directories::BaseDirs; use directories::BaseDirs;
use failure::Error; use Error;
pub fn run_zplug(base_dirs: &BaseDirs, dry_run: bool) -> Option<(&'static str, bool)> { pub fn run_zplug(base_dirs: &BaseDirs, dry_run: bool) -> Option<(&'static str, bool)> {
if let Some(zsh) = which("zsh") { if let Some(zsh) = which("zsh") {

View File

@@ -1,5 +1,4 @@
use failure::Error; use super::error::{Error, ErrorKind};
use failure_derive::Fail;
use log::{debug, error}; use log::{debug, error};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::Debug; use std::fmt::Debug;
@@ -7,10 +6,6 @@ use std::path::{Path, PathBuf};
use std::process::{ExitStatus, Output}; use std::process::{ExitStatus, Output};
use which as which_mod; use which as which_mod;
#[derive(Fail, Debug)]
#[fail(display = "Process failed")]
pub struct ProcessFailed;
pub trait Check { pub trait Check {
fn check(self) -> Result<(), Error>; fn check(self) -> Result<(), Error>;
} }
@@ -20,7 +15,7 @@ impl Check for ExitStatus {
if self.success() { if self.success() {
Ok(()) Ok(())
} else { } else {
Err(Error::from(ProcessFailed {})) Err(ErrorKind::ProcessFailed(self))?
} }
} }
} }

View File

@@ -1,8 +1,8 @@
use super::error::Error;
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::{which, Check, PathExt}; use super::utils::{which, Check, PathExt};
use directories::BaseDirs; use directories::BaseDirs;
use failure;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@@ -54,12 +54,7 @@ fn nvimrc(base_dirs: &BaseDirs) -> Option<PathBuf> {
} }
#[must_use] #[must_use]
fn upgrade( fn upgrade(vim: &PathBuf, vimrc: &PathBuf, plugin_framework: PluginFramework, dry_run: bool) -> Result<(), Error> {
vim: &PathBuf,
vimrc: &PathBuf,
plugin_framework: PluginFramework,
dry_run: bool,
) -> Result<(), failure::Error> {
Executor::new(&vim, dry_run) Executor::new(&vim, dry_run)
.args(&[ .args(&[
"-N", "-N",

View File

@@ -1,7 +1,8 @@
use super::error::{Error, ErrorKind};
use super::executor::Executor; use super::executor::Executor;
use super::terminal::print_separator; use super::terminal::print_separator;
use super::utils::{self, which, Check}; use super::utils::{self, which, Check};
use failure; use failure::ResultExt;
use log::error; use log::error;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@@ -11,7 +12,7 @@ pub fn run_chocolatey(dry_run: bool) -> Option<(&'static str, bool)> {
if let Some(choco) = utils::which("choco") { if let Some(choco) = utils::which("choco") {
print_separator("Chocolatey"); print_separator("Chocolatey");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&choco, dry_run) Executor::new(&choco, dry_run)
.args(&["upgrade", "all"]) .args(&["upgrade", "all"])
.spawn()? .spawn()?
@@ -32,7 +33,7 @@ pub fn run_scoop(dry_run: bool) -> Option<(&'static str, bool)> {
if let Some(scoop) = utils::which("scoop") { if let Some(scoop) = utils::which("scoop") {
print_separator("Scoop"); print_separator("Scoop");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&scoop, dry_run) Executor::new(&scoop, dry_run)
.args(&["update"]) .args(&["update"])
.spawn()? .spawn()?
@@ -65,10 +66,11 @@ impl Powershell {
} }
pub fn has_command(powershell: &PathBuf, command: &str) -> bool { pub fn has_command(powershell: &PathBuf, command: &str) -> bool {
|| -> Result<(), failure::Error> { || -> Result<(), Error> {
Command::new(&powershell) Command::new(&powershell)
.args(&["-Command", &format!("Get-Command {}", command)]) .args(&["-Command", &format!("Get-Command {}", command)])
.output()? .output()
.context(ErrorKind::ProcessExecution)?
.check()?; .check()?;
Ok(()) Ok(())
}() }()
@@ -77,8 +79,11 @@ impl Powershell {
pub fn profile(&self) -> Option<PathBuf> { pub fn profile(&self) -> Option<PathBuf> {
if let Some(powershell) = &self.path { if let Some(powershell) = &self.path {
let result = || -> Result<PathBuf, failure::Error> { let result = || -> Result<PathBuf, Error> {
let output = Command::new(powershell).args(&["-Command", "echo $profile"]).output()?; let output = Command::new(powershell)
.args(&["-Command", "echo $profile"])
.output()
.context(ErrorKind::ProcessExecution)?;
output.status.check()?; output.status.check()?;
Ok(PathBuf::from( Ok(PathBuf::from(
String::from_utf8_lossy(&output.stdout).trim().to_string(), String::from_utf8_lossy(&output.stdout).trim().to_string(),
@@ -98,7 +103,7 @@ impl Powershell {
if let Some(powershell) = &self.path { if let Some(powershell) = &self.path {
print_separator("Powershell Modules Update"); print_separator("Powershell Modules Update");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&powershell, dry_run) Executor::new(&powershell, dry_run)
.arg("Update-Module") .arg("Update-Module")
.spawn()? .spawn()?
@@ -120,7 +125,7 @@ impl Powershell {
if Self::has_command(&powershell, "Install-WindowsUpdate") { if Self::has_command(&powershell, "Install-WindowsUpdate") {
print_separator("Windows Update"); print_separator("Windows Update");
let success = || -> Result<(), failure::Error> { let success = || -> Result<(), Error> {
Executor::new(&powershell, dry_run) Executor::new(&powershell, dry_run)
.args(&["-Command", "Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -Verbose"]) .args(&["-Command", "Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -Verbose"])
.spawn()? .spawn()?