Rename SpecificPackageManager trait PackageManager, add some unit tests
This commit is contained in:
parent
1245db813b
commit
d2ef322761
|
@ -1,21 +1,52 @@
|
|||
use crate::utils::cmd::Cmd;
|
||||
use crate::utils::cmd::{Cmd, CmdOutput};
|
||||
|
||||
use super::{PackageError, PackageList, SpecificPackageManager};
|
||||
use super::{PackageError, PackageList, PackageManager};
|
||||
|
||||
pub fn apt() -> Cmd {
|
||||
Cmd::new("apt").env("DEBIAN_FRONTEND", "noninteractive")
|
||||
/// This structure is mostly used as an indirection for testing purposes.
|
||||
/// But you can use it to build your own apt commands.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct AptCmd(pub Cmd);
|
||||
|
||||
impl AptCmd {
|
||||
pub fn new() -> AptCmd {
|
||||
AptCmd(
|
||||
Cmd::new("apt").env("DEBIAN_FRONTEND", "noninteractive")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update() -> AptCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("update");
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn install(list: PackageList) -> AptCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("install").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn remove(list: PackageList) -> AptCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("remove").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<CmdOutput, std::io::Error> {
|
||||
self.0.run()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebianPackageManager;
|
||||
pub struct AptManager;
|
||||
|
||||
impl SpecificPackageManager for DebianPackageManager {
|
||||
impl PackageManager for AptManager {
|
||||
fn name(&self) -> &'static str {
|
||||
"apt"
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), PackageError> {
|
||||
let res = apt().arg("update").run()?;
|
||||
let res = AptCmd::update().run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
|
@ -24,7 +55,7 @@ impl SpecificPackageManager for DebianPackageManager {
|
|||
}
|
||||
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = apt().arg("install").args(list.list()).run()?;
|
||||
let res = AptCmd::install(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
|
@ -33,7 +64,7 @@ impl SpecificPackageManager for DebianPackageManager {
|
|||
}
|
||||
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = apt().arg("remove").args(list.list()).run()?;
|
||||
let res = AptCmd::remove(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
|
@ -41,3 +72,100 @@ impl SpecificPackageManager for DebianPackageManager {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::IntoPackageList;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
pub fn update_cmd() {
|
||||
let cmd = AptCmd::update();
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("update".to_string()),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd() {
|
||||
let cmd = AptCmd::install(vec!("sl").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("install", "sl").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd_multi() {
|
||||
let cmd = AptCmd::install(vec!("sl", "hello").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("install", "sl", "hello").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd() {
|
||||
let cmd = AptCmd::remove(vec!("sl").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("remove", "sl").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd_multi() {
|
||||
let cmd = AptCmd::remove(vec!("sl", "hello").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("remove", "sl", "hello").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use super::{PackageArgs, IntoPackageList, PackageList, PackageState, PackageModule, PackageStatus, PackageError, SpecificPackageManager};
|
||||
use super::apt::DebianPackageManager;
|
||||
use super::pacman::ArchlinuxPackageManager;
|
||||
use super::{PackageArgs, IntoPackageList, PackageList, PackageState, PackageModule, PackageStatus, PackageError, PackageManager};
|
||||
use super::{apt::AptManager, pacman::PacmanManager};
|
||||
use std::boxed::Box;
|
||||
|
||||
use crate::Module;
|
||||
|
@ -50,7 +49,7 @@ impl<Packages, Manager> PackageArgsBuilder<Packages, NoState, Manager> {
|
|||
}
|
||||
|
||||
impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
||||
pub fn with(self, manager: impl SpecificPackageManager + 'static) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
pub fn with(self, manager: impl PackageManager + 'static) -> PackageArgsBuilder<Packages, State, Box<dyn PackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
|
||||
PackageArgsBuilder {
|
||||
|
@ -60,21 +59,21 @@ impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_apt(self) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
pub fn with_apt(self) -> PackageArgsBuilder<Packages, State, Box<dyn PackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(DebianPackageManager),
|
||||
manager: Box::new(AptManager),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_pacman(self) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
pub fn with_pacman(self) -> PackageArgsBuilder<Packages, State, Box<dyn PackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(ArchlinuxPackageManager),
|
||||
manager: Box::new(PacmanManager),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,14 +81,14 @@ impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
|||
|
||||
// We can only RUN the args when a package manager has been set. Alternatively,
|
||||
// when called inside a playbook, the package manager can be populated automatically from facts.
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn PackageManager>> {
|
||||
pub fn run(self) -> Result<PackageStatus, PackageError> {
|
||||
self.build().run()
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
||||
pub fn build(self) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn PackageManager>> {
|
||||
pub fn build(self) -> PackageModule<Box<dyn PackageManager>> {
|
||||
let Self { name, state, manager, .. } = self;
|
||||
|
||||
PackageModule {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct PackageList {
|
||||
pub list: Vec<String>,
|
||||
}
|
||||
|
|
|
@ -10,35 +10,35 @@ pub mod list;
|
|||
pub use list::{PackageList, IntoPackageList};
|
||||
|
||||
pub mod pacman;
|
||||
use pacman::ArchlinuxPackageManager;
|
||||
use pacman::PacmanManager;
|
||||
pub mod apt;
|
||||
use apt::DebianPackageManager;
|
||||
use apt::AptManager;
|
||||
|
||||
pub mod builder;
|
||||
pub use builder::{PackageArgsBuilder, NoManager};
|
||||
|
||||
pub type PackageStatus = ();
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct PackageArgs {
|
||||
name: PackageList,
|
||||
state: PackageState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub enum PackageState {
|
||||
Present,
|
||||
Absent,
|
||||
Latest,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct PackageModule<Manager> {
|
||||
manager: Manager,
|
||||
args: PackageArgs,
|
||||
}
|
||||
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn PackageManager>> {
|
||||
fn serialize_args(&self) -> serde_json::Value {
|
||||
serde_json::to_value(&self.args).unwrap()
|
||||
}
|
||||
|
@ -62,13 +62,13 @@ impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPac
|
|||
}
|
||||
}
|
||||
|
||||
impl ModuleSetup<PackageModule<Box<dyn SpecificPackageManager>>, PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
||||
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
impl ModuleSetup<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
||||
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn PackageManager>> {
|
||||
let Self { args, .. } = self;
|
||||
|
||||
let manager: Box<dyn SpecificPackageManager> = match facts.os.family() {
|
||||
OsFamily::Debian => Box::new(DebianPackageManager),
|
||||
OsFamily::Archlinux => Box::new(ArchlinuxPackageManager),
|
||||
let manager: Box<dyn PackageManager> = match facts.os.family() {
|
||||
OsFamily::Debian => Box::new(AptManager),
|
||||
OsFamily::Archlinux => Box::new(PacmanManager),
|
||||
};
|
||||
|
||||
PackageModule {
|
||||
|
@ -97,9 +97,82 @@ impl From<std::io::Error> for PackageError {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait SpecificPackageManager: std::fmt::Debug {
|
||||
pub trait PackageManager: std::fmt::Debug {
|
||||
fn name(&self) -> &'static str;
|
||||
fn update(&self) -> Result<(), PackageError>;
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ensure_present() {
|
||||
let pkg = PackageModule::new()
|
||||
.name("hello")
|
||||
.state(PackageState::Present)
|
||||
.with_apt()
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
pkg.serialize_args(),
|
||||
serde_json::json!({
|
||||
"name": vec!("hello"),
|
||||
"state": PackageState::Present,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_present_multi() {
|
||||
let pkg = PackageModule::new()
|
||||
.name(&[ "hello", "sl" ])
|
||||
.state(PackageState::Present)
|
||||
.with_apt()
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
pkg.serialize_args(),
|
||||
serde_json::json!({
|
||||
"name": vec!("hello", "sl"),
|
||||
"state": PackageState::Present,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_absent() {
|
||||
let pkg = PackageModule::new()
|
||||
.name("hello")
|
||||
.state(PackageState::Absent)
|
||||
.with_apt()
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
pkg.serialize_args(),
|
||||
serde_json::json!({
|
||||
"name": vec!("hello"),
|
||||
"state": PackageState::Absent,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_absent_multi() {
|
||||
let pkg = PackageModule::new()
|
||||
.name(&[ "hello", "sl" ])
|
||||
.state(PackageState::Absent)
|
||||
.with_apt()
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
pkg.serialize_args(),
|
||||
serde_json::json!({
|
||||
"name": vec!("hello", "sl"),
|
||||
"state": PackageState::Absent,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,17 +1,53 @@
|
|||
use crate::utils::cmd::Cmd;
|
||||
use crate::utils::cmd::{Cmd, CmdOutput};
|
||||
|
||||
use super::{PackageError, PackageList, PackageManager};
|
||||
|
||||
/// This structure is mostly used as an indirection for testing purposes.
|
||||
/// But you can use it to build your own pacman commands.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct PacmanCmd(pub Cmd);
|
||||
|
||||
impl PacmanCmd {
|
||||
pub fn new() -> PacmanCmd {
|
||||
PacmanCmd(
|
||||
Cmd::new("pacman").arg("--noconfirm").arg("--needed")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update() -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-Sy");
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn install(list: PackageList) -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-S").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn remove(list: PackageList) -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-R").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<CmdOutput, std::io::Error> {
|
||||
self.0.run()
|
||||
}
|
||||
}
|
||||
|
||||
use super::{PackageError, PackageList, SpecificPackageManager};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArchlinuxPackageManager;
|
||||
pub struct PacmanManager;
|
||||
|
||||
impl SpecificPackageManager for ArchlinuxPackageManager {
|
||||
impl PackageManager for PacmanManager {
|
||||
fn name(&self) -> &'static str {
|
||||
"pacman"
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-Sy").run()?;
|
||||
let res = PacmanCmd::update().run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
|
@ -21,7 +57,7 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
|||
}
|
||||
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-S").args(list.list()).run()?;
|
||||
let res = PacmanCmd::install(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
|
@ -31,7 +67,7 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
|||
}
|
||||
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-R").args(list.list()).run()?;
|
||||
let res = PacmanCmd::remove(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
|
@ -40,3 +76,100 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::IntoPackageList;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
pub fn update_cmd() {
|
||||
let cmd = PacmanCmd::update();
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.push("-Sy");
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd() {
|
||||
let cmd = PacmanCmd::install(vec!("sl").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-S", "sl" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd_multi() {
|
||||
let cmd = PacmanCmd::install(vec!("sl", "hello").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-S", "sl", "hello" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd() {
|
||||
let cmd = PacmanCmd::remove(vec!("sl").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-R", "sl" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd_multi() {
|
||||
let cmd = PacmanCmd::remove(vec!("sl", "hello").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-R", "sl", "hello" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@ use duct::cmd as duct_cmd;
|
|||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct Cmd {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
stdin: Option<String>,
|
||||
chdir: Option<PathBuf>,
|
||||
env: HashMap<String, String>,
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
pub stdin: Option<String>,
|
||||
pub chdir: Option<PathBuf>,
|
||||
pub env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
|
|
Loading…
Reference in New Issue
Block a user