diff --git a/examples/debian.yml b/examples/debian.yml index a391f8b..01a1988 100644 --- a/examples/debian.yml +++ b/examples/debian.yml @@ -12,7 +12,7 @@ systems: params: - apt - upgrade - yes: -y + - --yes current_dir: null env: {} - install: @@ -28,4 +28,3 @@ systems: - -a current_dir: null env: {} -nice: null diff --git a/src/command.rs b/src/command.rs index 3975f2e..5922445 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,10 +1,10 @@ use crate::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::fmt; use std::io::{stdout, Write}; use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::process::{Command, ExitStatus, Stdio}; +use std::{fmt, io}; #[derive(Debug, Serialize, Deserialize, PartialEq)] enum UpdateSteps { @@ -32,13 +32,12 @@ pub struct System { struct Cmd { exe: String, params: Vec, - yes: Option, current_dir: Option, env: HashMap, } #[derive(Debug, Serialize, Deserialize)] -struct ActualCmd { +pub(crate) struct ActualCmd { exe: String, params: Vec, current_dir: Option, @@ -106,29 +105,13 @@ impl Updater { Ok(serde_yaml::from_str(&file).unwrap()) } - pub fn update_all(&self, opt: &Opt) -> Result<()> { - let errors: Vec<_> = self - .systems - .iter() - .filter_map(|sys| { - if let Err(err) = self.update(sys, opt) { - eprintln!("Error catched {}", err); - Some(err) - } else { - None - } - }) - .collect(); - - // TODO: - if errors.is_empty() { - Ok(()) - } else { - Err(Error::new( - errors, - ErrorKind::Fetch, // TODO: Why should I choose here? - )) + pub fn update_all(&self, opt: &Opt) -> Summary { + // self.systems.iter().collect(); + let mut systems: Vec<_> = vec![]; + for sys in &self.systems { + systems.push(self.update(&sys, opt)); } + Summary { systems } } fn update(&self, sys: &System, opt: &Opt) -> Result<()> { @@ -159,20 +142,26 @@ impl Updater { impl System { pub fn fetch(&self, opt: &Opt) -> Result<()> { if let Some(fetch) = &self.fetch { - fetch.clone().prepare(opt).execute(opt)?; + let cmd = fetch.clone().prepare(opt); + cmd.execute(opt) + .map_err(|err| MyError::new(ErrorKind::Fetch, err, cmd))?; } Ok(()) } pub fn compile(&self, opt: &Opt) -> Result<()> { if let Some(compile) = &self.compile { - compile.clone().prepare(opt).execute(opt)?; + let cmd = compile.clone().prepare(opt); + cmd.execute(opt) + .map_err(|err| MyError::new(ErrorKind::Compile, err, cmd))?; } Ok(()) } pub fn install(&self, opt: &Opt) -> Result<()> { - self.install.clone().prepare(opt).execute(opt)?; + let cmd = self.install.clone().prepare(opt); + cmd.execute(opt) + .map_err(|err| MyError::new(ErrorKind::Install, err, cmd))?; Ok(()) } } @@ -182,7 +171,6 @@ impl Cmd { Cmd { exe: "".into(), params: vec![], - yes: None, current_dir: None, env: HashMap::new(), } @@ -190,11 +178,6 @@ impl Cmd { fn prepare(self, opt: &Opt) -> ActualCmd { let mut params = self.params; - if opt.yes { - if let Some(y) = &self.yes { - params.push(y.to_owned()); - } - }; let mut env = self.env; if !env.contains_key("PATH") { @@ -211,45 +194,26 @@ impl Cmd { } impl ActualCmd { - fn execute(&self, opt: &Opt) -> Result { + fn execute(&self, opt: &Opt) -> io::Result { let mut cmd = Command::new(&self.exe); cmd.args(&self.params).env_clear().envs(&self.env); if let Some(cdir) = &self.current_dir { - cmd.current_dir(std::fs::canonicalize(cdir).unwrap()); + cmd.current_dir(std::fs::canonicalize(cdir)?); } - let confirm = if opt.yes { "\n" } else { "[Y/n]? " }; - - print!("Will execute {}{}", self, confirm); - stdout().flush().unwrap(); + print!("Executing: {}", self); + stdout().flush()?; if opt.quiet { - // Maybe we should only hide with the quiet option and not with yes? // FIXME: stdin does not work with sudo? cmd.stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); } - let mut user_continue = String::new(); - if !opt.yes { - std::io::stdin() - .read_line(&mut user_continue) - .expect("Unable to read user’s input"); - } - - if user_continue.to_lowercase().starts_with('n') { - eprintln!( - "user_continue? {} {}", - user_continue, - user_continue.to_lowercase().starts_with('n') - ); - return Ok(false); - } - - Ok(cmd.status().unwrap().success()) + Ok(cmd.status()?) } } diff --git a/src/errors.rs b/src/errors.rs index 804de72..9e89991 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,13 +1,17 @@ -use std::error; -use std::fmt; +use crate::*; + +use std::error::Error; +use std::fmt::{Display, Formatter, Result}; +use std::io; /// An error that can occur in this crate. /// /// Generally, this error corresponds to problems with underlying process. #[derive(Debug)] -pub struct Error { - source: Vec, +pub struct MyError { + source: io::Error, kind: ErrorKind, + cmd: ActualCmd, } #[derive(Debug)] @@ -19,9 +23,9 @@ pub enum ErrorKind { Install, // TODO: merge into "Update" or "Command" type of error? => Have this as an other level of error? } -impl Error { - pub(crate) fn new(source: Vec, kind: ErrorKind) -> Error { - Error { source, kind } +impl MyError { + pub(crate) fn new(kind: ErrorKind, source: io::Error, cmd: ActualCmd) -> MyError { + MyError { source, kind, cmd } } /// Return the kind of this error. @@ -30,29 +34,38 @@ impl Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - if self.source.is_empty() { - None - } else { - Some(&self.source[0]) - } +impl Error for MyError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) } } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use std::error::Error; - +impl Display for MyError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self.kind { ErrorKind::Config => write!( f, "Could not read configuration file: {}", self.source().unwrap() ), - ErrorKind::Fetch => write!(f, "Could not install: {}", self.source().unwrap()), - ErrorKind::Compile => write!(f, "Could not install: {}", self.source().unwrap()), - ErrorKind::Install => write!(f, "Could not install: {}", self.source().unwrap()), + ErrorKind::Fetch => write!( + f, + "Could not fetch with command `{}`: {}", + self.cmd, + self.source().unwrap() + ), + ErrorKind::Compile => write!( + f, + "Could not compile with command `{}`: {}", + self.cmd, + self.source().unwrap() + ), + ErrorKind::Install => write!( + f, + "Could not install with command `{}`: {}", + self.cmd, + self.source().unwrap() + ), } } } diff --git a/src/lib.rs b/src/lib.rs index 5c72b97..688ee36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,11 @@ use command::*; use errors::*; use clap::Parser; +use std::fmt::Display; use std::path::PathBuf; use std::result; -pub type Result = result::Result; +pub type Result = result::Result; #[derive(Parser)] pub struct Opt { @@ -23,9 +24,6 @@ pub struct Opt { //#[arg(default_value_t = PathBuf::from("examples/debian.yml"))] pub config_file: PathBuf, - #[arg(short, long)] - pub yes: bool, - #[arg(short, long)] pub quiet: bool, // TODO: use clap_verbosity_flag instead @@ -33,8 +31,44 @@ pub struct Opt { pub steps: Vec, } -pub fn run(opt: &Opt) { +// enum State { +// /// All steps asked were successful +// Ok, +// Err(MyError), +// } + +// impl Display for State { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// match self { +// State::Ok => write!(f, "Ok"), +// State::Err(e) => write!(f, "Error: {}", e), +// // State::Fetch(e) => write!(f, "Fetch error: {}", e), +// // State::Compile(e) => write!(f, "Compile error: {}", e), +// // State::Install(e) => write!(f, "Install error: {}", e), +// } +// } +// } + +pub struct Summary { + // TODO: Go back to vectors to keep order? + systems: Vec>, +} + +impl Display for Summary { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Summary:")?; + for system in &self.systems { + // TODO: also print version before/after, update time + // writeln!(f, "\t{}\t{}", system.0, system.1)?; + + writeln!(f, "\t{:?}", system)?; + } + Ok(()) + } +} + +pub fn run(opt: &Opt) -> Summary { let updater = Updater::from_config(opt).unwrap(); - updater.update_all(opt).unwrap(); + updater.update_all(opt) } diff --git a/src/main.rs b/src/main.rs index 626c084..3a9601d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,8 @@ use system_updater::*; fn main() { let mut opt = Opt::parse(); - if opt.quiet { - opt.yes = true; - } - run(&opt); + let summary = run(&opt); + + println!("{}", summary); }