Print summary infos at the end

This commit is contained in:
Zykino 2023-01-28 19:20:37 +01:00
parent 088cd97b19
commit 206c8c7bf8
5 changed files with 101 additions and 92 deletions

View File

@ -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

View File

@ -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 users 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()?)
}
}

View File

@ -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()
),
}
}
}

View File

@ -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)
}

View File

@ -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);
}