Print summary infos at the end
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<String>,
 | 
			
		||||
    yes: Option<String>,
 | 
			
		||||
    current_dir: Option<PathBuf>,
 | 
			
		||||
    env: HashMap<String, String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
struct ActualCmd {
 | 
			
		||||
pub(crate) struct ActualCmd {
 | 
			
		||||
    exe: String,
 | 
			
		||||
    params: Vec<String>,
 | 
			
		||||
    current_dir: Option<PathBuf>,
 | 
			
		||||
@@ -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<bool> {
 | 
			
		||||
    fn execute(&self, opt: &Opt) -> io::Result<ExitStatus> {
 | 
			
		||||
        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()?)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Error>,
 | 
			
		||||
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<Error>, 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()
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								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<T> = result::Result<T, Error>;
 | 
			
		||||
pub type Result<T> = result::Result<T, MyError>;
 | 
			
		||||
 | 
			
		||||
#[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<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<Result<()>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user