Split process.rs into shell and paths
(now that the bulk of process code is gone)
This commit is contained in:
parent
72cda2b0b0
commit
bb6a5ae891
|
@ -15,8 +15,8 @@
|
||||||
|
|
||||||
use std::{path::PathBuf, time::Duration};
|
use std::{path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use crate::process::Shell;
|
|
||||||
use crate::run::OnBusyUpdate;
|
use crate::run::OnBusyUpdate;
|
||||||
|
use crate::Shell;
|
||||||
|
|
||||||
/// Arguments to the watcher
|
/// Arguments to the watcher
|
||||||
#[derive(Builder, Clone, Debug)]
|
#[derive(Builder, Clone, Debug)]
|
||||||
|
|
|
@ -21,10 +21,11 @@ mod gitignore;
|
||||||
mod ignore;
|
mod ignore;
|
||||||
mod notification_filter;
|
mod notification_filter;
|
||||||
pub mod pathop;
|
pub mod pathop;
|
||||||
mod process;
|
mod paths;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
|
mod shell;
|
||||||
mod signal;
|
mod signal;
|
||||||
mod watcher;
|
mod watcher;
|
||||||
|
|
||||||
pub use process::Shell;
|
|
||||||
pub use run::{run, watch, Handler};
|
pub use run::{run, watch, Handler};
|
||||||
|
pub use shell::Shell;
|
||||||
|
|
|
@ -1,147 +1,9 @@
|
||||||
#![allow(unsafe_code)]
|
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::pathop::PathOp;
|
use crate::pathop::PathOp;
|
||||||
use command_group::{CommandGroup, GroupChild};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::Command,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Shell to use to run commands.
|
|
||||||
///
|
|
||||||
/// `Cmd` and `Powershell` are special-cased because they have different calling
|
|
||||||
/// conventions. Also `Cmd` is only available in Windows, while `Powershell` is
|
|
||||||
/// also available on unices (provided the end-user has it installed, of course).
|
|
||||||
///
|
|
||||||
/// See [`Config.cmd`][crate::config::Config] for the semantics of `None` vs the
|
|
||||||
/// other options.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Shell {
|
|
||||||
/// Use no shell, and execute the command directly.
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// Use the given string as a unix shell invocation.
|
|
||||||
///
|
|
||||||
/// This means two things:
|
|
||||||
/// - the program is invoked with `-c` followed by the command, and
|
|
||||||
/// - the string will be split on space, and the resulting vec used as
|
|
||||||
/// execvp(3) arguments: first is the shell program, rest are additional
|
|
||||||
/// arguments (which come before the `-c` mentioned above). This is a very
|
|
||||||
/// simplistic approach deliberately: it will not support quoted
|
|
||||||
/// arguments, for example. Use [`Shell::None`] with a custom command vec
|
|
||||||
/// if you want that.
|
|
||||||
Unix(String),
|
|
||||||
|
|
||||||
/// Use the Windows CMD.EXE shell.
|
|
||||||
///
|
|
||||||
/// This is invoked with `/C` followed by the command.
|
|
||||||
#[cfg(windows)]
|
|
||||||
Cmd,
|
|
||||||
|
|
||||||
/// Use Powershell, on Windows or elsewhere.
|
|
||||||
///
|
|
||||||
/// This is invoked with `-Command` followed by the command.
|
|
||||||
///
|
|
||||||
/// This is preferred over `Unix("pwsh")`, though that will also work
|
|
||||||
/// on unices due to Powershell supporting the `-c` short option.
|
|
||||||
Powershell,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Shell {
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Powershell
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Unix("sh".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Shell {
|
|
||||||
/// Obtain a [`Command`] given the cmd vec from [`Config`][crate::config::Config].
|
|
||||||
///
|
|
||||||
/// Behaves as described in the enum documentation.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - Panics if `cmd` is empty.
|
|
||||||
/// - Panics if the string in the `Unix` variant is empty or only whitespace.
|
|
||||||
pub fn to_command(&self, cmd: &[String]) -> Command {
|
|
||||||
assert!(!cmd.is_empty(), "cmd was empty");
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Shell::None => {
|
|
||||||
// UNWRAP: checked by assert
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let (first, rest) = cmd.split_first().unwrap();
|
|
||||||
let mut c = Command::new(first);
|
|
||||||
c.args(rest);
|
|
||||||
c
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
Shell::Cmd => {
|
|
||||||
let mut c = Command::new("cmd.exe");
|
|
||||||
c.arg("/C").arg(cmd.join(" "));
|
|
||||||
c
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell::Powershell if cfg!(windows) => {
|
|
||||||
let mut c = Command::new("powershell.exe");
|
|
||||||
c.arg("-Command").arg(cmd.join(" "));
|
|
||||||
c
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell::Powershell => {
|
|
||||||
let mut c = Command::new("pwsh");
|
|
||||||
c.arg("-Command").arg(cmd.join(" "));
|
|
||||||
c
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell::Unix(name) => {
|
|
||||||
assert!(!name.is_empty(), "shell program was empty");
|
|
||||||
let sh = name.split_ascii_whitespace().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// UNWRAP: checked by assert
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let (shprog, shopts) = sh.split_first().unwrap();
|
|
||||||
|
|
||||||
let mut c = Command::new(shprog);
|
|
||||||
c.args(shopts);
|
|
||||||
c.arg("-c").arg(cmd.join(" "));
|
|
||||||
c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(
|
|
||||||
cmd: &[String],
|
|
||||||
updated_paths: &[PathOp],
|
|
||||||
shell: Shell,
|
|
||||||
environment: bool,
|
|
||||||
) -> Result<GroupChild> {
|
|
||||||
let mut command = shell.to_command(&cmd);
|
|
||||||
debug!("Assembled command {:?}", command);
|
|
||||||
|
|
||||||
let command_envs = if !environment {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
collect_path_env_vars(updated_paths)
|
|
||||||
};
|
|
||||||
|
|
||||||
for (name, val) in &command_envs {
|
|
||||||
command.env(name, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
let child = command.group_spawn()?;
|
|
||||||
Ok(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect `PathOp` details into op-categories to pass onto the exec'd command as env-vars
|
/// Collect `PathOp` details into op-categories to pass onto the exec'd command as env-vars
|
||||||
///
|
///
|
||||||
/// `WRITTEN` -> `notify::ops::WRITE`, `notify::ops::CLOSE_WRITE`
|
/// `WRITTEN` -> `notify::ops::WRITE`, `notify::ops::CLOSE_WRITE`
|
||||||
|
@ -149,7 +11,7 @@ pub fn spawn(
|
||||||
/// `REMOVED` -> `notify::ops::REMOVE`
|
/// `REMOVED` -> `notify::ops::REMOVE`
|
||||||
/// `CREATED` -> `notify::ops::CREATE`
|
/// `CREATED` -> `notify::ops::CREATE`
|
||||||
/// `RENAMED` -> `notify::ops::RENAME`
|
/// `RENAMED` -> `notify::ops::RENAME`
|
||||||
fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
|
pub fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
const ENV_SEP: &str = ":";
|
const ENV_SEP: &str = ":";
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
@ -205,7 +67,7 @@ fn collect_path_env_vars(pathops: &[PathOp]) -> Vec<(String, String)> {
|
||||||
vars
|
vars
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
pub fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
||||||
match paths.len() {
|
match paths.len() {
|
||||||
0 => return None,
|
0 => return None,
|
||||||
1 => return paths[0].to_str().map(ToString::to_string),
|
1 => return paths[0].to_str().map(ToString::to_string),
|
||||||
|
@ -238,48 +100,16 @@ fn get_longest_common_path(paths: &[PathBuf]) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Shell;
|
|
||||||
use crate::pathop::PathOp;
|
use crate::pathop::PathOp;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::collect_path_env_vars;
|
use super::collect_path_env_vars;
|
||||||
use super::get_longest_common_path;
|
use super::get_longest_common_path;
|
||||||
use super::spawn;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_default() {
|
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], Shell::default(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_none() {
|
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], Shell::None, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_alternate() {
|
|
||||||
let _ = spawn(
|
|
||||||
&["echo".into(), "hi".into()],
|
|
||||||
&[],
|
|
||||||
Shell::Unix("bash".into()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_alternate_shopts() {
|
|
||||||
let _ = spawn(
|
|
||||||
&["echo".into(), "hi".into()],
|
|
||||||
&[],
|
|
||||||
Shell::Unix("bash -o errexit".into()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
fn longest_common_path_should_return_correct_value() {
|
fn longest_common_path_should_return_correct_value() {
|
||||||
let single_path = vec![PathBuf::from("/tmp/random/")];
|
let single_path = vec![PathBuf::from("/tmp/random/")];
|
||||||
let single_result =
|
let single_result =
|
||||||
|
@ -315,6 +145,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
fn pathops_collect_to_env_vars() {
|
fn pathops_collect_to_env_vars() {
|
||||||
let pathops = vec![
|
let pathops = vec![
|
||||||
PathOp::new(
|
PathOp::new(
|
||||||
|
@ -348,34 +179,3 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[cfg(target_family = "windows")]
|
|
||||||
mod tests {
|
|
||||||
use super::{spawn, Shell};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_default() {
|
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], Shell::default(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_cmd() {
|
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], Shell::Cmd, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_powershell() {
|
|
||||||
let _ = spawn(&["echo".into(), "hi".into()], &[], Shell::Powershell, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shell_bash() {
|
|
||||||
let _ = spawn(
|
|
||||||
&["echo".into(), "hi".into()],
|
|
||||||
&[],
|
|
||||||
Shell::Unix("bash".into()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Shell to use to run commands.
|
||||||
|
///
|
||||||
|
/// `Cmd` and `Powershell` are special-cased because they have different calling
|
||||||
|
/// conventions. Also `Cmd` is only available in Windows, while `Powershell` is
|
||||||
|
/// also available on unices (provided the end-user has it installed, of course).
|
||||||
|
///
|
||||||
|
/// See [`Config.cmd`][crate::config::Config] for the semantics of `None` vs the
|
||||||
|
/// other options.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Shell {
|
||||||
|
/// Use no shell, and execute the command directly.
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Use the given string as a unix shell invocation.
|
||||||
|
///
|
||||||
|
/// This means two things:
|
||||||
|
/// - the program is invoked with `-c` followed by the command, and
|
||||||
|
/// - the string will be split on space, and the resulting vec used as
|
||||||
|
/// execvp(3) arguments: first is the shell program, rest are additional
|
||||||
|
/// arguments (which come before the `-c` mentioned above). This is a very
|
||||||
|
/// simplistic approach deliberately: it will not support quoted
|
||||||
|
/// arguments, for example. Use [`Shell::None`] with a custom command vec
|
||||||
|
/// if you want that.
|
||||||
|
Unix(String),
|
||||||
|
|
||||||
|
/// Use the Windows CMD.EXE shell.
|
||||||
|
///
|
||||||
|
/// This is invoked with `/C` followed by the command.
|
||||||
|
#[cfg(windows)]
|
||||||
|
Cmd,
|
||||||
|
|
||||||
|
/// Use Powershell, on Windows or elsewhere.
|
||||||
|
///
|
||||||
|
/// This is invoked with `-Command` followed by the command.
|
||||||
|
///
|
||||||
|
/// This is preferred over `Unix("pwsh")`, though that will also work
|
||||||
|
/// on unices due to Powershell supporting the `-c` short option.
|
||||||
|
Powershell,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Shell {
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Powershell
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Unix("sh".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shell {
|
||||||
|
/// Obtain a [`Command`] given the cmd vec from [`Config`][crate::config::Config].
|
||||||
|
///
|
||||||
|
/// Behaves as described in the enum documentation.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - Panics if `cmd` is empty.
|
||||||
|
/// - Panics if the string in the `Unix` variant is empty or only whitespace.
|
||||||
|
pub fn to_command(&self, cmd: &[String]) -> Command {
|
||||||
|
assert!(!cmd.is_empty(), "cmd was empty");
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Shell::None => {
|
||||||
|
// UNWRAP: checked by assert
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let (first, rest) = cmd.split_first().unwrap();
|
||||||
|
let mut c = Command::new(first);
|
||||||
|
c.args(rest);
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
Shell::Cmd => {
|
||||||
|
let mut c = Command::new("cmd.exe");
|
||||||
|
c.arg("/C").arg(cmd.join(" "));
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell::Powershell if cfg!(windows) => {
|
||||||
|
let mut c = Command::new("powershell.exe");
|
||||||
|
c.arg("-Command").arg(cmd.join(" "));
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell::Powershell => {
|
||||||
|
let mut c = Command::new("pwsh");
|
||||||
|
c.arg("-Command").arg(cmd.join(" "));
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell::Unix(name) => {
|
||||||
|
assert!(!name.is_empty(), "shell program was empty");
|
||||||
|
let sh = name.split_ascii_whitespace().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// UNWRAP: checked by assert
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let (shprog, shopts) = sh.split_first().unwrap();
|
||||||
|
|
||||||
|
let mut c = Command::new(shprog);
|
||||||
|
c.args(shopts);
|
||||||
|
c.arg("-c").arg(cmd.join(" "));
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Shell;
|
||||||
|
use command_group::CommandGroup;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn unix_shell_default() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::default()
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn unix_shell_none() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::None
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn unix_shell_alternate() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::Unix("bash".into())
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::Unix("bash -o errexit".into())
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn windows_shell_default() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::default()
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn windows_shell_cmd() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::Cmd
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())(&["echo".into(), "hi".into()], &[], Shell::Cmd, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn windows_shell_powershell() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::Powershell
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn windows_shell_bash() -> Result<(), std::io::Error> {
|
||||||
|
assert!(Shell::Unix("bash")
|
||||||
|
.to_command(&["echo".into(), "hi".into()])
|
||||||
|
.group_status()?
|
||||||
|
.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue