Initial POC

This commit is contained in:
Zykino
2023-01-07 17:08:44 +01:00
commit 7c765fd48e
8 changed files with 537 additions and 0 deletions

208
src/command.rs Normal file
View File

@ -0,0 +1,208 @@
use crate::*;
use enumset::EnumSetType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use std::process::Command;
#[derive(Debug, Serialize, Deserialize, EnumSetType)]
//#[enumset(serialize_as_list)] // TODO: use it or not?
enum UpdateSteps {
Fetch,
Compile,
Install,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Updater {
systems: Vec<System>,
steps: UpdateSteps,
nice: Option<i32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct System {
fetch: Option<Fetch>,
compile: Option<Compile>,
install: Install,
// deps or rDeps : Tree
// exclusive_with : List
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Fetch {
command: Cmd,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Compile {
command: Cmd,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Install {
command: Cmd,
}
#[derive(Debug, Serialize, Deserialize)]
struct Cmd {
exe: String,
params: Vec<String>,
current_dir: Option<PathBuf>,
env: HashMap<String, String>,
}
impl Updater {
fn new() -> Updater {
let mut up = Updater {
systems: vec![],
steps: UpdateSteps::Fetch,
nice: None,
};
up.systems.push(System {
fetch: None,
compile: None,
install: Install {
command: Cmd::new(),
},
});
up
}
/// To create a sample config from code
#[doc(hidden)]
fn write_config(&self, opt: &Opt) {
use ::std::io::Write;
use std::fs::OpenOptions;
let mut f = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&opt.config_file)
.unwrap();
f.write_all(serde_yaml::to_string(&self).unwrap().as_bytes())
.unwrap();
}
pub fn parse_config(opt: &Opt) -> Result<Updater> {
// let u = Updater::new();
// u.write_config(opt);
let file = std::fs::read_to_string(&opt.config_file).unwrap();
Ok(serde_yaml::from_str(&file).unwrap())
// TODO:
}
pub fn update_all(&self) -> Result<()> {
let mut errors = vec![];
for sys in &self.systems {
if let Err(err) = self.update(sys) {
eprintln!("Error catched {}", err);
errors.push(err);
}
}
// TODO:
if errors.len() == 0 {
Ok(())
} else {
Err(Error::new(
errors,
ErrorKind::Fetch, // TODO: Why should I choose here?
))
}
}
fn update(&self, sys: &System) -> Result<()> {
if self.steps == UpdateSteps::Fetch {
sys.fetch()?;
}
if self.steps == UpdateSteps::Compile {
sys.compile()?;
}
if self.steps == UpdateSteps::Install {
sys.install()?;
}
Ok(())
}
}
impl System {
pub fn fetch(&self) -> Result<()> {
if let Some(fetch) = &self.fetch {
fetch.command.execute()?;
}
Ok(())
}
pub fn compile(&self) -> Result<()> {
if let Some(compile) = &self.compile {
compile.command.execute()?;
}
Ok(())
}
pub fn install(&self) -> Result<()> {
self.install.command.execute()?;
Ok(())
}
}
impl Cmd {
fn new() -> Cmd {
Cmd {
exe: "".into(),
params: vec![],
current_dir: None,
env: HashMap::new(),
}
}
fn execute(&self) -> Result<()> {
let mut cmd = Command::new(&self.exe);
cmd.args(&self.params)
.env_clear()
.envs(&self.env)
// .stdout(cfg)
// .stderr(cfg)
;
if let Some(cdir) = &self.current_dir {
cmd.current_dir(std::fs::canonicalize(cdir).unwrap());
}
println!("Executing {}", self);
// TODO: Ask if ok or
if cmd.status().unwrap().success() {
println!("Youpi !");
// Other checks?
}
println!("Exécutée");
Ok(())
}
}
impl fmt::Display for Cmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let command = if self.params.is_empty() {
self.exe.clone()
} else {
format!("{} {}", &self.exe, &self.params.join(" "))
};
write!(f, "`{}`", command)?;
if let Some(cdir) = &self.current_dir {
write!(f, " in {:?}", cdir)?;
}
if !self.env.is_empty() {
writeln!(f, " with the following environment variable:")?;
writeln!(f, "{:?}", self.env)
} else {
writeln!(f, " without any environment variable.")
}
}
}

58
src/errors.rs Normal file
View File

@ -0,0 +1,58 @@
use std::error;
use std::fmt;
/// 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>,
kind: ErrorKind,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ErrorKind {
Config,
Fetch, // TODO: merge into "Update" or "Command" type of error? => Have this as an other level of error?
Compile, // TODO: merge into "Update" or "Command" type of error? => Have this as an other level of error?
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 }
}
/// Return the kind of this error.
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
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 fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::error::Error;
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()),
}
}
}

20
src/lib.rs Normal file
View File

@ -0,0 +1,20 @@
mod command;
mod errors;
use command::*;
use errors::*;
use std::path::PathBuf;
use std::result;
pub type Result<T> = result::Result<T, Error>;
pub struct Opt {
pub config_file: PathBuf,
}
pub fn run(opt: &Opt) {
let updater = Updater::parse_config(opt).unwrap();
dbg!(updater).update_all().unwrap();
}

13
src/main.rs Normal file
View File

@ -0,0 +1,13 @@
use system_updater::*;
fn main() {
let opt = Opt {
// + One config file?
// + A config subFolder and execute in alphabetical order?
// - A master config file that list the sub/real files? no if it mean parsing 2 differents formats
//
// Hardcoded for now
config_file: "examples/openSUSE.yml".into(), // Default to something like -> "~/.config/system-updater/list.yml".into(),
};
run(&opt);
}