...
This commit is contained in:
303
src/process/mgmt.rs
Normal file
303
src/process/mgmt.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
use std::process::Command;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
|
||||
// Define a custom error type for process operations
|
||||
#[derive(Debug)]
|
||||
pub enum ProcessError {
|
||||
CommandExecutionFailed(io::Error),
|
||||
CommandFailed(String),
|
||||
NoProcessFound(String),
|
||||
MultipleProcessesFound(String, usize),
|
||||
}
|
||||
|
||||
// Implement Display for ProcessError
|
||||
impl fmt::Display for ProcessError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ProcessError::CommandExecutionFailed(e) => write!(f, "Failed to execute command: {}", e),
|
||||
ProcessError::CommandFailed(e) => write!(f, "{}", e),
|
||||
ProcessError::NoProcessFound(pattern) => write!(f, "No processes found matching '{}'", pattern),
|
||||
ProcessError::MultipleProcessesFound(pattern, count) =>
|
||||
write!(f, "Multiple processes ({}) found matching '{}'", count, pattern),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Error trait for ProcessError
|
||||
impl Error for ProcessError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
ProcessError::CommandExecutionFailed(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define a struct to represent process information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: i64,
|
||||
pub name: String,
|
||||
pub memory: f64,
|
||||
pub cpu: f64,
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a command exists in PATH.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `cmd` - The command to check
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Option<String>` - The full path to the command if found, None otherwise
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* match which("git") {
|
||||
* Some(path) => println!("Git is installed at: {}", path),
|
||||
* None => println!("Git is not installed"),
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn which(cmd: &str) -> Option<String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let which_cmd = "where";
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
let which_cmd = "which";
|
||||
|
||||
let output = Command::new(which_cmd)
|
||||
.arg(cmd)
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if out.status.success() {
|
||||
let path = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill processes matching a pattern.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `pattern` - The pattern to match against process names
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - A success message indicating processes were killed or none were found
|
||||
* * `Err(ProcessError)` - An error if the kill operation failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* // Kill all processes with "server" in their name
|
||||
* let result = kill("server")?;
|
||||
* println!("{}", result);
|
||||
* ```
|
||||
*/
|
||||
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||
// Platform specific implementation
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// On Windows, use taskkill with wildcard support
|
||||
let mut args = vec!["/F"]; // Force kill
|
||||
|
||||
if pattern.contains('*') {
|
||||
// If it contains wildcards, use filter
|
||||
args.extend(&["/FI", &format!("IMAGENAME eq {}", pattern)]);
|
||||
} else {
|
||||
// Otherwise use image name directly
|
||||
args.extend(&["/IM", pattern]);
|
||||
}
|
||||
|
||||
let output = Command::new("taskkill")
|
||||
.args(&args)
|
||||
.output()
|
||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||
|
||||
if output.status.success() {
|
||||
Ok("Successfully killed processes".to_string())
|
||||
} else {
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
if error.is_empty() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if stdout.contains("No tasks") {
|
||||
Ok("No matching processes found".to_string())
|
||||
} else {
|
||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", stdout)))
|
||||
}
|
||||
} else {
|
||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
{
|
||||
// On Unix-like systems, use pkill which has built-in pattern matching
|
||||
let output = Command::new("pkill")
|
||||
.arg("-f") // Match against full process name/args
|
||||
.arg(pattern)
|
||||
.output()
|
||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||
|
||||
// pkill returns 0 if processes were killed, 1 if none matched
|
||||
if output.status.success() {
|
||||
Ok("Successfully killed processes".to_string())
|
||||
} else if output.status.code() == Some(1) {
|
||||
Ok("No matching processes found".to_string())
|
||||
} else {
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List processes matching a pattern (or all if pattern is empty).
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `pattern` - The pattern to match against process names (empty string for all processes)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(Vec<ProcessInfo>)` - A vector of process information for matching processes
|
||||
* * `Err(ProcessError)` - An error if the list operation failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* // List all processes
|
||||
* let processes = process_list("")?;
|
||||
*
|
||||
* // List processes with "server" in their name
|
||||
* let processes = process_list("server")?;
|
||||
* for proc in processes {
|
||||
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
let mut processes = Vec::new();
|
||||
|
||||
// Platform specific implementations
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Windows implementation using wmic
|
||||
let output = Command::new("wmic")
|
||||
.args(&["process", "list", "brief"])
|
||||
.output()
|
||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
// Parse output (assuming format: Handle Name Priority)
|
||||
for line in stdout.lines().skip(1) { // Skip header
|
||||
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||
if parts.len() >= 2 {
|
||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||
let name = parts[1].to_string();
|
||||
|
||||
// Filter by pattern if provided
|
||||
if !pattern.is_empty() && !name.contains(pattern) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processes.push(ProcessInfo {
|
||||
pid,
|
||||
name,
|
||||
memory: 0.0, // Placeholder
|
||||
cpu: 0.0, // Placeholder
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
{
|
||||
// Unix implementation using ps
|
||||
let output = Command::new("ps")
|
||||
.args(&["-eo", "pid,comm"])
|
||||
.output()
|
||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
// Parse output (assuming format: PID COMMAND)
|
||||
for line in stdout.lines().skip(1) { // Skip header
|
||||
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||
if parts.len() >= 2 {
|
||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||
let name = parts[1].to_string();
|
||||
|
||||
// Filter by pattern if provided
|
||||
if !pattern.is_empty() && !name.contains(pattern) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processes.push(ProcessInfo {
|
||||
pid,
|
||||
name,
|
||||
memory: 0.0, // Placeholder
|
||||
cpu: 0.0, // Placeholder
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(processes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single process matching the pattern (error if 0 or more than 1 match).
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `pattern` - The pattern to match against process names
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(ProcessInfo)` - Information about the matching process
|
||||
* * `Err(ProcessError)` - An error if no process or multiple processes match
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* let process = process_get("unique-server-name")?;
|
||||
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
||||
* ```
|
||||
*/
|
||||
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
||||
let processes = process_list(pattern)?;
|
||||
|
||||
match processes.len() {
|
||||
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
||||
1 => Ok(processes[0].clone()),
|
||||
_ => Err(ProcessError::MultipleProcessesFound(pattern.to_string(), processes.len())),
|
||||
}
|
||||
}
|
2
src/process/mod.rs
Normal file
2
src/process/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
use run::*;
|
||||
use mgmt::*;
|
493
src/process/run.rs
Normal file
493
src/process/run.rs
Normal file
@@ -0,0 +1,493 @@
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Output, Stdio};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
|
||||
use super::text;
|
||||
|
||||
// Define a custom error type for run operations
|
||||
#[derive(Debug)]
|
||||
pub enum RunError {
|
||||
EmptyCommand,
|
||||
CommandExecutionFailed(io::Error),
|
||||
CommandFailed(String),
|
||||
ScriptPreparationFailed(String),
|
||||
ChildProcessError(String),
|
||||
TempDirCreationFailed(io::Error),
|
||||
FileCreationFailed(io::Error),
|
||||
FileWriteFailed(io::Error),
|
||||
PermissionError(io::Error),
|
||||
}
|
||||
|
||||
// Implement Display for RunError
|
||||
impl fmt::Display for RunError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RunError::EmptyCommand => write!(f, "Empty command"),
|
||||
RunError::CommandExecutionFailed(e) => write!(f, "Failed to execute command: {}", e),
|
||||
RunError::CommandFailed(e) => write!(f, "{}", e),
|
||||
RunError::ScriptPreparationFailed(e) => write!(f, "{}", e),
|
||||
RunError::ChildProcessError(e) => write!(f, "{}", e),
|
||||
RunError::TempDirCreationFailed(e) => write!(f, "Failed to create temporary directory: {}", e),
|
||||
RunError::FileCreationFailed(e) => write!(f, "Failed to create script file: {}", e),
|
||||
RunError::FileWriteFailed(e) => write!(f, "Failed to write to script file: {}", e),
|
||||
RunError::PermissionError(e) => write!(f, "Failed to set file permissions: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Error trait for RunError
|
||||
impl Error for RunError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
RunError::CommandExecutionFailed(e) => Some(e),
|
||||
RunError::TempDirCreationFailed(e) => Some(e),
|
||||
RunError::FileCreationFailed(e) => Some(e),
|
||||
RunError::FileWriteFailed(e) => Some(e),
|
||||
RunError::PermissionError(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure to hold command execution results
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommandResult {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub success: bool,
|
||||
pub code: i32,
|
||||
}
|
||||
|
||||
impl CommandResult {
|
||||
/// Create a default failed result with an error message
|
||||
fn error(message: &str) -> Self {
|
||||
Self {
|
||||
stdout: String::new(),
|
||||
stderr: message.to_string(),
|
||||
success: false,
|
||||
code: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare a script file and return the path and interpreter
|
||||
fn prepare_script_file(script_content: &str) -> Result<(PathBuf, String, tempfile::TempDir), RunError> {
|
||||
// Dedent the script
|
||||
let dedented = text::dedent(script_content);
|
||||
|
||||
// Create a temporary directory
|
||||
let temp_dir = tempfile::tempdir()
|
||||
.map_err(RunError::TempDirCreationFailed)?;
|
||||
|
||||
// Determine script extension and interpreter
|
||||
#[cfg(target_os = "windows")]
|
||||
let (ext, interpreter) = (".bat", "cmd.exe".to_string());
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
let (ext, interpreter) = (".sh", "/bin/sh".to_string());
|
||||
|
||||
// Create the script file
|
||||
let script_path = temp_dir.path().join(format!("script{}", ext));
|
||||
let mut file = File::create(&script_path)
|
||||
.map_err(RunError::FileCreationFailed)?;
|
||||
|
||||
// Write the script content
|
||||
file.write_all(dedented.as_bytes())
|
||||
.map_err(RunError::FileWriteFailed)?;
|
||||
|
||||
// Make the script executable (Unix only)
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&script_path)
|
||||
.map_err(|e| RunError::PermissionError(e))?
|
||||
.permissions();
|
||||
perms.set_mode(0o755); // rwxr-xr-x
|
||||
fs::set_permissions(&script_path, perms)
|
||||
.map_err(RunError::PermissionError)?;
|
||||
}
|
||||
|
||||
Ok((script_path, interpreter, temp_dir))
|
||||
}
|
||||
|
||||
/// Capture output from Child's stdio streams with optional printing
|
||||
fn handle_child_output(mut child: Child, silent: bool) -> Result<CommandResult, RunError> {
|
||||
// Prepare to read stdout & stderr line-by-line
|
||||
let stdout = child.stdout.take();
|
||||
let stderr = child.stderr.take();
|
||||
|
||||
// Buffers for captured output
|
||||
let mut captured_stdout = String::new();
|
||||
let mut captured_stderr = String::new();
|
||||
|
||||
// Process stdout
|
||||
let stdout_handle = if let Some(out) = stdout {
|
||||
let reader = BufReader::new(out);
|
||||
let silent_clone = silent;
|
||||
// Spawn a thread to capture and optionally print stdout
|
||||
Some(std::thread::spawn(move || {
|
||||
let mut local_buffer = String::new();
|
||||
for line in reader.lines() {
|
||||
if let Ok(l) = line {
|
||||
// Print the line if not silent and flush immediately
|
||||
if !silent_clone {
|
||||
println!("{}", l);
|
||||
std::io::stdout().flush().unwrap_or(());
|
||||
}
|
||||
// Store it in our captured buffer
|
||||
local_buffer.push_str(&l);
|
||||
local_buffer.push('\n');
|
||||
}
|
||||
}
|
||||
local_buffer
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Process stderr
|
||||
let stderr_handle = if let Some(err) = stderr {
|
||||
let reader = BufReader::new(err);
|
||||
let silent_clone = silent;
|
||||
// Spawn a thread to capture and optionally print stderr
|
||||
Some(std::thread::spawn(move || {
|
||||
let mut local_buffer = String::new();
|
||||
for line in reader.lines() {
|
||||
if let Ok(l) = line {
|
||||
// Print the line if not silent and flush immediately
|
||||
if !silent_clone {
|
||||
eprintln!("{}", l);
|
||||
std::io::stderr().flush().unwrap_or(());
|
||||
}
|
||||
// Store it in our captured buffer
|
||||
local_buffer.push_str(&l);
|
||||
local_buffer.push('\n');
|
||||
}
|
||||
}
|
||||
local_buffer
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Wait for the child process to exit
|
||||
let status = child.wait()
|
||||
.map_err(|e| RunError::ChildProcessError(format!("Failed to wait on child process: {}", e)))?;
|
||||
|
||||
// Join our stdout thread if it exists
|
||||
if let Some(handle) = stdout_handle {
|
||||
captured_stdout = handle.join().unwrap_or_default();
|
||||
} else {
|
||||
captured_stdout = "Failed to capture stdout".to_string();
|
||||
}
|
||||
|
||||
// Join our stderr thread if it exists
|
||||
if let Some(handle) = stderr_handle {
|
||||
captured_stderr = handle.join().unwrap_or_default();
|
||||
} else {
|
||||
captured_stderr = "Failed to capture stderr".to_string();
|
||||
}
|
||||
|
||||
// Return the command result
|
||||
Ok(CommandResult {
|
||||
stdout: captured_stdout,
|
||||
stderr: captured_stderr,
|
||||
success: status.success(),
|
||||
code: status.code().unwrap_or(-1),
|
||||
})
|
||||
}
|
||||
|
||||
/// Processes Output structure from Command::output() into CommandResult
|
||||
fn process_command_output(output: Result<Output, std::io::Error>) -> Result<CommandResult, RunError> {
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
|
||||
|
||||
Ok(CommandResult {
|
||||
stdout,
|
||||
stderr,
|
||||
success: out.status.success(),
|
||||
code: out.status.code().unwrap_or(-1),
|
||||
})
|
||||
},
|
||||
Err(e) => Err(RunError::CommandExecutionFailed(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common logic for running a command with optional silent mode.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `command` - The command + args as a single string (e.g., "ls -la")
|
||||
* * `silent` - If `true`, don't print stdout/stderr as it arrives (capture only)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the command execution
|
||||
* * `Err(RunError)` - An error if the command execution failed
|
||||
*/
|
||||
fn run_command_internal(command: &str, silent: bool) -> Result<CommandResult, RunError> {
|
||||
let mut parts = command.split_whitespace();
|
||||
let cmd = match parts.next() {
|
||||
Some(c) => c,
|
||||
None => return Err(RunError::EmptyCommand),
|
||||
};
|
||||
|
||||
let args: Vec<&str> = parts.collect();
|
||||
|
||||
// Spawn the child process with piped stdout & stderr
|
||||
let child = Command::new(cmd)
|
||||
.args(&args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(RunError::CommandExecutionFailed)?;
|
||||
|
||||
handle_child_output(child, silent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a script with the given interpreter and path.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `interpreter` - The interpreter to use (e.g., "/bin/sh")
|
||||
* * `script_path` - The path to the script file
|
||||
* * `silent` - If `true`, don't print stdout/stderr as it arrives (capture only)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the script execution
|
||||
* * `Err(RunError)` - An error if the script execution failed
|
||||
*/
|
||||
fn execute_script_internal(interpreter: &str, script_path: &Path, silent: bool) -> Result<CommandResult, RunError> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let command_args = vec!["/c", script_path.to_str().unwrap_or("")];
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
let command_args = vec![script_path.to_str().unwrap_or("")];
|
||||
|
||||
if silent {
|
||||
// For silent execution, use output() which captures but doesn't display
|
||||
let output = Command::new(interpreter)
|
||||
.args(&command_args)
|
||||
.output();
|
||||
|
||||
process_command_output(output)
|
||||
} else {
|
||||
// For normal execution, spawn and handle the output streams
|
||||
let child = Command::new(interpreter)
|
||||
.args(&command_args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(RunError::CommandExecutionFailed)?;
|
||||
|
||||
handle_child_output(child, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single command with arguments, showing live stdout and stderr.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `command` - The command + args as a single string (e.g., "ls -la")
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the command execution
|
||||
* * `Err(RunError)` - An error if the command execution failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* let result = run_command("ls -la")?;
|
||||
* println!("Command exited with code: {}", result.code);
|
||||
* ```
|
||||
*/
|
||||
pub fn run_command(command: &str) -> Result<CommandResult, RunError> {
|
||||
run_command_internal(command, /* silent = */ false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single command with arguments silently.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `command` - The command + args as a single string (e.g., "ls -la")
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the command execution
|
||||
* * `Err(RunError)` - An error if the command execution failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* let result = run_command_silent("ls -la")?;
|
||||
* println!("Command output: {}", result.stdout);
|
||||
* ```
|
||||
*/
|
||||
pub fn run_command_silent(command: &str) -> Result<CommandResult, RunError> {
|
||||
run_command_internal(command, /* silent = */ true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a multiline script with optional silent mode.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `script` - The script content as a string
|
||||
* * `silent` - If `true`, don't print stdout/stderr as it arrives (capture only)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the script execution
|
||||
* * `Err(RunError)` - An error if the script execution failed
|
||||
*/
|
||||
fn run_script_internal(script: &str, silent: bool) -> Result<CommandResult, RunError> {
|
||||
let (script_path, interpreter, _temp_dir) = prepare_script_file(script)?;
|
||||
// _temp_dir is kept in scope until the end of this function to ensure
|
||||
// it's not dropped prematurely, which would clean up the directory
|
||||
execute_script_internal(&interpreter, &script_path, silent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a multiline script by saving it to a temporary file and executing.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `script` - The script content as a string
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the script execution
|
||||
* * `Err(RunError)` - An error if the script execution failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* let script = r#"
|
||||
* echo "Hello, world!"
|
||||
* ls -la
|
||||
* "#;
|
||||
* let result = run_script(script)?;
|
||||
* println!("Script exited with code: {}", result.code);
|
||||
* ```
|
||||
*/
|
||||
pub fn run_script(script: &str) -> Result<CommandResult, RunError> {
|
||||
run_script_internal(script, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a multiline script silently by saving it to a temporary file and executing.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `script` - The script content as a string
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the script execution
|
||||
* * `Err(RunError)` - An error if the script execution failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* let script = r#"
|
||||
* echo "Hello, world!"
|
||||
* ls -la
|
||||
* "#;
|
||||
* let result = run_script_silent(script)?;
|
||||
* println!("Script output: {}", result.stdout);
|
||||
* ```
|
||||
*/
|
||||
pub fn run_script_silent(script: &str) -> Result<CommandResult, RunError> {
|
||||
run_script_internal(script, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command or multiline script with arguments.
|
||||
* Shows stdout/stderr as it arrives.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `command` - The command or script to run
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the execution
|
||||
* * `Err(RunError)` - An error if the execution failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* // Run a single command
|
||||
* let result = run("ls -la")?;
|
||||
*
|
||||
* // Run a multiline script
|
||||
* let result = run(r#"
|
||||
* echo "Hello, world!"
|
||||
* ls -la
|
||||
* "#)?;
|
||||
* ```
|
||||
*/
|
||||
pub fn run(command: &str) -> Result<CommandResult, RunError> {
|
||||
let trimmed = command.trim();
|
||||
|
||||
// Check if this is a multiline script
|
||||
if trimmed.contains('\n') {
|
||||
// This is a multiline script, write to a temporary file and execute
|
||||
run_script(trimmed)
|
||||
} else {
|
||||
// This is a single command with arguments
|
||||
run_command(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command or multiline script with arguments silently.
|
||||
* Doesn't show stdout/stderr as it arrives.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `command` - The command or script to run
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(CommandResult)` - The result of the execution
|
||||
* * `Err(RunError)` - An error if the execution failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* // Run a single command silently
|
||||
* let result = run_silent("ls -la")?;
|
||||
*
|
||||
* // Run a multiline script silently
|
||||
* let result = run_silent(r#"
|
||||
* echo "Hello, world!"
|
||||
* ls -la
|
||||
* "#)?;
|
||||
* ```
|
||||
*/
|
||||
pub fn run_silent(command: &str) -> Result<CommandResult, RunError> {
|
||||
let trimmed = command.trim();
|
||||
|
||||
// Check if this is a multiline script
|
||||
if trimmed.contains('\n') {
|
||||
// This is a multiline script, write to a temporary file and execute
|
||||
run_script_silent(trimmed)
|
||||
} else {
|
||||
// This is a single command with arguments
|
||||
run_command_silent(trimmed)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user