diff --git a/Cargo.lock b/Cargo.lock index 934a3b7e..9231eb43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1536,6 +1536,7 @@ dependencies = [ "serde_yaml", "serial_test", "shlex", + "tempfile", "test-generator", "thiserror", "topologic", diff --git a/cpclib-bndbuild/Cargo.toml b/cpclib-bndbuild/Cargo.toml index 78900b7e..3acdbc70 100644 --- a/cpclib-bndbuild/Cargo.toml +++ b/cpclib-bndbuild/Cargo.toml @@ -46,6 +46,7 @@ build-deps = "0.1.4" [dev-dependencies] test-generator = "0.3.1" serial_test = "3.1.1" +tempfile = {workspace = true} [[bin]] name = "bndbuild" diff --git a/cpclib-bndbuild/src/executor.rs b/cpclib-bndbuild/src/executor.rs index 144b7c37..4bb3371b 100644 --- a/cpclib-bndbuild/src/executor.rs +++ b/cpclib-bndbuild/src/executor.rs @@ -4,6 +4,7 @@ use cpclib_common::lazy_static::lazy_static; use crate::runners::basm::BasmRunner; use crate::runners::bndbuild::BndBuildRunner; +use crate::runners::cp::CpRunner; use crate::runners::disc::DiscManagerRunner; use crate::runners::echo::EchoRunner; use crate::runners::imgconverter::ImgConverterRunner; @@ -16,6 +17,7 @@ use crate::task::Task; lazy_static! { pub static ref BASM_RUNNER: BasmRunner = BasmRunner::default(); pub static ref BNDBUILD_RUNNER: BndBuildRunner = BndBuildRunner::default(); + pub static ref CP_RUNNER: CpRunner = CpRunner::default(); pub static ref DISC_RUNNER: DiscManagerRunner = DiscManagerRunner::default(); pub static ref ECHO_RUNNER: EchoRunner = EchoRunner::default(); pub static ref EXTERN_RUNNER: ExternRunner = ExternRunner::default(); @@ -25,18 +27,17 @@ lazy_static! { } pub fn execute(task: &Task) -> Result<(), String> { - let (runner, args) = match task { - Task::Basm(_) => (BASM_RUNNER.deref() as &dyn Runner, task.args()), - Task::BndBuild(_) => (BNDBUILD_RUNNER.deref() as &dyn Runner, task.args()), - Task::Disc(_) => (DISC_RUNNER.deref() as &dyn Runner, task.args()), - Task::Echo(_) => (ECHO_RUNNER.deref() as &dyn Runner, task.args()), - Task::Extern(_) => (EXTERN_RUNNER.deref() as &dyn Runner, task.args()), - Task::ImgConverter(_) => (IMGCONV_RUNNER.deref() as &dyn Runner, task.args()), - Task::Rm(_) => (RM_RUNNER.deref() as &dyn Runner, task.args()), - Task::Xfer(_) => (XFER_RUNNER.deref() as &dyn Runner, task.args()) - }; - - runner.run(args).or_else(|e| { + match task { + Task::Basm(_) => BASM_RUNNER.run(task.args()), + Task::BndBuild(_) => BNDBUILD_RUNNER.run(task.args()), + Task::Cp(_) => CP_RUNNER.run(task.args()), + Task::Disc(_) => DISC_RUNNER.run(task.args()), + Task::Echo(_) => ECHO_RUNNER.run(task.args()), + Task::Extern(_) => EXTERN_RUNNER.run(task.args()), + Task::ImgConverter(_) => IMGCONV_RUNNER.run(task.args()), + Task::Rm(_) => RM_RUNNER.run(task.args()), + Task::Xfer(_) => XFER_RUNNER.run(task.args()), + }.or_else(|e| { if task.ignore_errors() { println!("\t\tError ignored. {}", e); Ok(()) diff --git a/cpclib-bndbuild/src/runners/basm.rs b/cpclib-bndbuild/src/runners/basm.rs index 117e3c70..df53d1ca 100644 --- a/cpclib-bndbuild/src/runners/basm.rs +++ b/cpclib-bndbuild/src/runners/basm.rs @@ -1,7 +1,7 @@ use cpclib_common::clap::{self, Arg, ArgAction, Command}; use super::{Runner, RunnerWithClap}; -use crate::built_info; +use crate::{built_info, task::BASM_CMDS}; pub struct BasmRunner { command: clap::Command @@ -53,7 +53,7 @@ impl RunnerWithClap for BasmRunner { } impl Runner for BasmRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { let matches = self.get_matches(itr)?; if matches.get_flag("version") { @@ -79,6 +79,6 @@ impl Runner for BasmRunner { } fn get_command(&self) -> &str { - "basm" + &BASM_CMDS[0] } } diff --git a/cpclib-bndbuild/src/runners/bndbuild.rs b/cpclib-bndbuild/src/runners/bndbuild.rs index db8dfcca..ee41a691 100644 --- a/cpclib-bndbuild/src/runners/bndbuild.rs +++ b/cpclib-bndbuild/src/runners/bndbuild.rs @@ -1,7 +1,7 @@ use cpclib_common::clap::{self, Command}; use super::{Runner, RunnerWithClap}; -use crate::built_info; +use crate::{built_info, task::BNDBUILD_CMDS}; pub struct BndBuildRunner { command: clap::Command @@ -28,7 +28,7 @@ impl RunnerWithClap for BndBuildRunner { } impl Runner for BndBuildRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { // backup of cwd let cwd = std::env::current_dir().unwrap(); @@ -43,6 +43,6 @@ impl Runner for BndBuildRunner { } fn get_command(&self) -> &str { - "bndbuild" + &BNDBUILD_CMDS[0] } } diff --git a/cpclib-bndbuild/src/runners/cp.rs b/cpclib-bndbuild/src/runners/cp.rs new file mode 100644 index 00000000..7b3cd7dc --- /dev/null +++ b/cpclib-bndbuild/src/runners/cp.rs @@ -0,0 +1,128 @@ +use std::path::Path; + +use cpclib_common::{clap::{self, Arg, ArgAction}, itertools::Itertools}; + +use super::Runner; +use crate::{built_info, expand_glob, task::CP_CMDS}; + +#[derive(Default)] +pub struct CpRunner {} + +impl CpRunner { + pub fn print_help(&self) { + clap::Command::new("cp") + .before_help("Copy files.") + .disable_help_flag(true) + .after_help(format!( + "Inner command of {} {}", + built_info::PKG_NAME, + built_info::PKG_VERSION + )) + .arg( + Arg::new("arguments") + .action(ArgAction::Append) + .help("Files to copy. Last one being the destination") + ) + .print_long_help() + .unwrap(); + } +} + +impl Runner for CpRunner { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { + let mut errors = String::new(); + + let fnames = itr + .iter() + .map(|s| s.as_ref()) + .map(expand_glob) + .flatten() + .collect_vec(); + let files = fnames.iter() + .map( |fname| Path::new(fname)) + .collect_vec(); + let dest = files.last(); + + let copy = |from: &Path, to: &Path, error: &mut String| { + std::fs::copy(from, to) + .map_err(|e| error.push_str(&format!("Error when copying {} to {}. {}.\n", from.display(), to.display(), e.to_string()))); + }; + + match files.len() { + 0 => { + errors.push_str("No source and destination provided\n"); + }, + + 1 => { + errors.push_str("No source or destination provided\n"); + }, + + 2 => { + let dest = dest.unwrap(); + let src = files.first().unwrap(); + let dest = if dest.is_dir() { + dest.join(src.file_name().unwrap()) + } else { + dest.to_path_buf() + }; + copy(src, &dest, &mut errors); + }, + + _ => { + let dest = dest.unwrap(); + if ! dest.is_dir() { + errors.push_str(&format!("{} must be a directory.", dest.display())) + } else { + let files = &files[..files.len()-1]; + for src in files.iter() { + let dst = dest.join(src.file_name().unwrap()); + copy(src, &dst, &mut errors); + } + } + } + }; + + + if errors.is_empty() { + Ok(()) + } + else { + Err(errors) + } + } + + fn get_command(&self) -> &'static str { + &CP_CMDS[0] + } +} + + +#[cfg(test)] +mod test { + use std::io::Write; + + use crate::runners::{cp::CpRunner, Runner}; + + #[test] + + fn test_copy_successful() { + // prepare the files for the test + let mut src = tempfile::NamedTempFile::new().unwrap(); + let mut dst = tempfile::NamedTempFile::new().unwrap(); + + src.as_file_mut().write("test".as_bytes()).unwrap(); + + let src = src.into_temp_path(); + let dst = dst.into_temp_path(); + std::fs::remove_file(&dst).unwrap(); + + assert!(src.exists()); + assert!(!dst.exists()); + + // Run the test + let cp = CpRunner::default(); + cp.inner_run(&[src.display().to_string(), dst.display().to_string()]).unwrap(); + assert!(dst.exists()); + + } +} \ No newline at end of file diff --git a/cpclib-bndbuild/src/runners/disc.rs b/cpclib-bndbuild/src/runners/disc.rs index 7e4521cb..b81349f4 100644 --- a/cpclib-bndbuild/src/runners/disc.rs +++ b/cpclib-bndbuild/src/runners/disc.rs @@ -2,7 +2,7 @@ use cpclib_common::clap::{self, Command}; use cpclib_disc::dsk_manager_build_arg_parser; use super::{Runner, RunnerWithClap}; -use crate::built_info; +use crate::{built_info, task::DISC_CMDS}; pub struct DiscManagerRunner { command: clap::Command @@ -29,12 +29,12 @@ impl RunnerWithClap for DiscManagerRunner { } impl Runner for DiscManagerRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { let matches = self.get_matches(itr)?; cpclib_disc::dsk_manager_handle(&matches).map_err(|e| e.to_string()) } fn get_command(&self) -> &str { - "disc" + &DISC_CMDS[0] } } diff --git a/cpclib-bndbuild/src/runners/echo.rs b/cpclib-bndbuild/src/runners/echo.rs index c7250ad6..d0d758b0 100644 --- a/cpclib-bndbuild/src/runners/echo.rs +++ b/cpclib-bndbuild/src/runners/echo.rs @@ -1,18 +1,20 @@ use cpclib_common::itertools::Itertools; +use crate::task::ECHO_CMDS; + use super::Runner; #[derive(Default)] pub struct EchoRunner {} impl Runner for EchoRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { - let txt = itr.iter().join(" "); + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { + let txt = itr.iter().map(|s| s.as_ref()).join(" "); println!("{txt}"); Ok(()) } fn get_command(&self) -> &str { - "echo" + &ECHO_CMDS[0] } } diff --git a/cpclib-bndbuild/src/runners/extern.rs b/cpclib-bndbuild/src/runners/extern.rs index 8bb2068b..2abc6f53 100644 --- a/cpclib-bndbuild/src/runners/extern.rs +++ b/cpclib-bndbuild/src/runners/extern.rs @@ -1,10 +1,18 @@ +use std::fmt::Debug; + +use cpclib_common::itertools::Itertools; + +use crate::task::EXTERN_CMDS; + use super::Runner; #[derive(Default)] pub struct ExternRunner {} impl ExternRunner {} impl Runner for ExternRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { + let itr = itr.iter().map(|s| s.as_ref()).collect_vec(); + // WARNING // Deactivated because if makes fail normal progam on Linux // however, it was maybe mandatory for Windows @@ -45,6 +53,6 @@ impl Runner for ExternRunner { } fn get_command(&self) -> &str { - "extern" + &EXTERN_CMDS[0] } } diff --git a/cpclib-bndbuild/src/runners/imgconverter.rs b/cpclib-bndbuild/src/runners/imgconverter.rs index 3251f1c1..b38a2602 100644 --- a/cpclib-bndbuild/src/runners/imgconverter.rs +++ b/cpclib-bndbuild/src/runners/imgconverter.rs @@ -1,7 +1,7 @@ use cpclib_common::clap::{Arg, ArgAction, Command}; use super::{Runner, RunnerWithClap}; -use crate::built_info; +use crate::{built_info, task::IMG2CPC_CMDS}; pub struct ImgConverterRunner { command: Command @@ -39,7 +39,7 @@ impl RunnerWithClap for ImgConverterRunner { } impl Runner for ImgConverterRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { let args = self.get_clap_command().clone(); let matches = self.get_matches(itr)?; @@ -52,6 +52,6 @@ impl Runner for ImgConverterRunner { } fn get_command(&self) -> &str { - "img2cpc" + &IMG2CPC_CMDS[0] } } diff --git a/cpclib-bndbuild/src/runners/mod.rs b/cpclib-bndbuild/src/runners/mod.rs index 64e54282..5e247eb7 100644 --- a/cpclib-bndbuild/src/runners/mod.rs +++ b/cpclib-bndbuild/src/runners/mod.rs @@ -1,9 +1,12 @@ +use std::fmt::Debug; + use cpclib_common::clap::{ArgMatches, Command}; use glob::glob; use shlex::split; pub mod basm; pub mod bndbuild; +pub mod cp; pub mod disc; pub mod echo; pub mod r#extern; @@ -45,7 +48,7 @@ pub trait Runner { } /// Implement the command specific action - fn inner_run(&self, itr: &[String]) -> Result<(), String>; + fn inner_run>(&self, itr: &[S]) -> Result<(), String>; fn get_command(&self) -> &str; } @@ -53,10 +56,10 @@ pub trait Runner { pub trait RunnerWithClap: Runner { fn get_clap_command(&self) -> &Command; - fn get_matches(&self, itr: &[String]) -> Result { + fn get_matches>(&self, itr: &[S]) -> Result { self.get_clap_command() .clone() - .try_get_matches_from(itr) + .try_get_matches_from(itr.iter().map(|s| s.as_ref())) .map_err(|e| e.to_string()) } diff --git a/cpclib-bndbuild/src/runners/rm.rs b/cpclib-bndbuild/src/runners/rm.rs index 49f7b2e5..7da851c2 100644 --- a/cpclib-bndbuild/src/runners/rm.rs +++ b/cpclib-bndbuild/src/runners/rm.rs @@ -26,12 +26,12 @@ impl RmRunner { } } impl Runner for RmRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { let mut errors = String::new(); for fname in itr .into_iter() - .map(|s| s.as_str()) + .map(|s| s.as_ref()) // .map(|s| glob(s).unwrap()) .map(expand_glob) .flatten() diff --git a/cpclib-bndbuild/src/runners/xfer.rs b/cpclib-bndbuild/src/runners/xfer.rs index a4737479..631e0e2e 100644 --- a/cpclib-bndbuild/src/runners/xfer.rs +++ b/cpclib-bndbuild/src/runners/xfer.rs @@ -1,7 +1,7 @@ use cpclib_common::clap::{self, Arg, ArgAction, Command}; use super::{Runner, RunnerWithClap}; -use crate::built_info; +use crate::{built_info, task::XFER_CMDS}; pub struct XferRunner { command: clap::Command @@ -39,7 +39,7 @@ impl RunnerWithClap for XferRunner { } impl Runner for XferRunner { - fn inner_run(&self, itr: &[String]) -> Result<(), String> { + fn inner_run>(&self, itr: &[S]) -> Result<(), String> { let matches = self.get_matches(itr)?; if matches.get_flag("version") { @@ -51,6 +51,6 @@ impl Runner for XferRunner { } fn get_command(&self) -> &str { - "xfer" + &XFER_CMDS[0] } } diff --git a/cpclib-bndbuild/src/task.rs b/cpclib-bndbuild/src/task.rs index e981a18b..67ca9d5b 100644 --- a/cpclib-bndbuild/src/task.rs +++ b/cpclib-bndbuild/src/task.rs @@ -1,10 +1,12 @@ use std::fmt::Display; +use cpclib_common::itertools::Itertools; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Task { + Cp(StandardTask), Basm(StandardTask), BndBuild(StandardTask), Disc(StandardTask), @@ -15,67 +17,37 @@ pub enum Task { Xfer(StandardTask) } +pub const BASM_CMDS: &[&'static str] = &["basm", "assemble"]; +pub const BNDBUILD_CMDS: &[&'static str] = &["bndbuild", "build"]; +pub const CP_CMDS: &[&'static str] = &["cp", "copy"]; +pub const DISC_CMDS: &[&'static str] = &["dsk", "disc"]; +pub const ECHO_CMDS: &[&'static str] = &["echo", "print"]; +pub const EXTERN_CMDS: &[&'static str] = &["extern"]; +pub const IMG2CPC_CMDS: &[&'static str] = &["img2cpc", "imgconverter"]; +pub const RM_CMDS: &[&'static str] = &["rm", "del"]; +pub const XFER_CMDS: &[&'static str] = &["xfer", "cpcwifi", "m4"]; + impl Display for Task { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Task::Basm(s) => { - write!( - f, - "{}basm {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - }, - Task::Rm(s) => write!(f, "{}rm {}", if s.ignore_error { "-" } else { "" }, s.args), - Task::Echo(s) => { - write!( - f, - "{}echo {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - }, - Task::ImgConverter(s) => { - write!( - f, - "{}img2cpc {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - }, - Task::Xfer(s) => { - write!( - f, - "{}xfer {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - }, - Task::Disc(s) => { - write!( - f, - "{}disc {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - }, - Task::Extern(s) => { - write!( - f, - "{}extern {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - }, - Task::BndBuild(s) => { - write!( - f, - "{}bndbuild {}", - if s.ignore_error { "-" } else { "" }, - s.args - ) - } - } + let (cmd, s) = match self { + Task::Basm(s) => (&BASM_CMDS[0], s), + Task::BndBuild(s) => (&BNDBUILD_CMDS[0], s), + Task::Cp(s) => (&CP_CMDS[0], s), + Task::Disc(s) => (&DISC_CMDS[0], s), + Task::Echo(s) => (&ECHO_CMDS[0], s), + Task::Extern(s) => (&EXTERN_CMDS[0], s), + Task::ImgConverter(s) => (&IMG2CPC_CMDS[0], s), + Task::Rm(s) => (&RM_CMDS[0], s), + Task::Xfer(s) => (&XFER_CMDS[0], s), + }; + + write!( + f, + "{}{} {}", + if s.ignore_error { "-" } else { "" }, + cmd, + s.args + ) } } @@ -100,17 +72,38 @@ impl<'de> Deserialize<'de> for Task { ignore_error: ignore }; - match code { - "basm" | "assemble" => Ok(Task::Basm(std)), - "bndbuild" => Ok(Task::BndBuild(std)), - "dsk" | "disc" => Ok(Task::Disc(std)), - "echo" | "print" => Ok(Task::Echo(std)), - "extern" => Ok(Task::Extern(std)), - "img2cpc" | "imgconverter" => Ok(Task::ImgConverter(std)), - "rm" | "del" => Ok(Task::Rm(std)), - "xfer" | "cpcwifi" | "m4" => Ok(Task::Xfer(std)), - _ => Err(Error::custom(format!("{code} is invalid"))) + + if BASM_CMDS.iter().contains(&code) { + Ok(Task::Basm(std)) + } + else if BNDBUILD_CMDS.iter().contains(&code) { + Ok(Task::BndBuild(std)) + } + else if CP_CMDS.iter().contains(&code) { + Ok(Task::Cp(std)) + } + else if DISC_CMDS.iter().contains(&code) { + Ok(Task::Disc(std)) + } + else if ECHO_CMDS.iter().contains(&code) { + Ok(Task::Echo(std)) + } + else if EXTERN_CMDS.iter().contains(&code) { + Ok(Task::Extern(std)) + } + else if IMG2CPC_CMDS.iter().contains(&code) { + Ok(Task::ImgConverter(std)) + } + else if RM_CMDS.iter().contains(&code) { + Ok(Task::Rm(std)) + } + else if XFER_CMDS.iter().contains(&code) { + Ok(Task::Xfer(std)) + } + else { + Err(Error::custom(format!("{code} is an invalid command"))) } + } fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -147,7 +140,8 @@ impl Task { Self::ImgConverter(StandardTask::new(args)) } - pub fn args(&self) -> &str { + + fn standard_task(&self) -> &StandardTask { match self { Task::Basm(t) | Task::Rm(t) @@ -156,11 +150,12 @@ impl Task { | Task::Xfer(t) | Task::Extern(t) | Task::Disc(t) - | Task::BndBuild(t) => &t.args + | Task::BndBuild(t) + | Task::Cp(t) => t, } } - pub fn ignore_errors(&self) -> bool { + fn standard_task_mut(&mut self) -> &mut StandardTask { match self { Task::Basm(t) | Task::Rm(t) @@ -169,22 +164,22 @@ impl Task { | Task::Xfer(t) | Task::Extern(t) | Task::Disc(t) - | Task::BndBuild(t) => t.ignore_error + | Task::BndBuild(t) + | Task::Cp(t) => t, } } - pub fn set_ignore_errors(mut self, ignore: bool) -> Self { - match self { - Task::Basm(ref mut t) - | Task::Rm(ref mut t) - | Task::Echo(ref mut t) - | Task::Xfer(ref mut t) - | Task::ImgConverter(ref mut t) - | Task::Extern(ref mut t) - | Task::Disc(ref mut t) - | Task::BndBuild(ref mut t) => t.ignore_error = ignore - } + pub fn args(&self) -> &str { + &self.standard_task().args + } + + pub fn ignore_errors(&self) -> bool { + self.standard_task().ignore_error + } + + pub fn set_ignore_errors(mut self, ignore: bool) -> Self { + self.standard_task_mut().ignore_error = ignore; self } @@ -198,7 +193,8 @@ impl Task { Task::ImgConverter(_) => false, Task::Extern(_) => false, Task::BndBuild(_) => false, - Task::Disc(_) => false // wrong for winape + Task::Disc(_) => false, + Task::Cp(_) => false } } }