This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
pub mod config_store;
|
||||
pub mod local_terminal;
|
||||
pub mod pty;
|
||||
pub mod wallet_vault;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LocalCommand {
|
||||
pub command: String,
|
||||
pub cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl LocalCommand {
|
||||
pub fn new(command: impl Into<String>) -> Self {
|
||||
Self {
|
||||
command: command.into(),
|
||||
cwd: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
|
||||
self.cwd = Some(cwd.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct LocalCommandOutput {
|
||||
pub exit_code: i32,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum LocalTerminalError {
|
||||
InvalidCommand(String),
|
||||
Io(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LocalTerminal;
|
||||
|
||||
impl LocalTerminal {
|
||||
pub fn run(&self, command: LocalCommand) -> Result<LocalCommandOutput, LocalTerminalError> {
|
||||
if command.command.trim().is_empty() {
|
||||
return Err(LocalTerminalError::InvalidCommand(
|
||||
"command is required".to_string(),
|
||||
));
|
||||
}
|
||||
let mut child = Command::new(default_shell());
|
||||
child.arg("-lc").arg(&command.command);
|
||||
if let Some(cwd) = command.cwd {
|
||||
child.current_dir(cwd);
|
||||
}
|
||||
let output = child
|
||||
.output()
|
||||
.map_err(|err| LocalTerminalError::Io(err.to_string()))?;
|
||||
Ok(LocalCommandOutput {
|
||||
exit_code: output.status.code().unwrap_or(-1),
|
||||
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn default_shell() -> &'static str {
|
||||
if cfg!(windows) {
|
||||
"cmd"
|
||||
} else {
|
||||
"/bin/sh"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn local_terminal_runs_shell_command_and_captures_stdout() {
|
||||
let output = LocalTerminal::default()
|
||||
.run(LocalCommand::new("printf 'rabby-local-terminal'"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output.exit_code, 0);
|
||||
assert_eq!(output.stdout, "rabby-local-terminal");
|
||||
assert!(output.stderr.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_terminal_reports_non_zero_exit() {
|
||||
let output = LocalTerminal::default()
|
||||
.run(LocalCommand::new("printf error >&2; exit 7"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output.exit_code, 7);
|
||||
assert_eq!(output.stderr, "error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_terminal_rejects_blank_commands() {
|
||||
let err = LocalTerminal::default()
|
||||
.run(LocalCommand::new(" "))
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
LocalTerminalError::InvalidCommand("command is required".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_terminal_runs_from_working_directory() {
|
||||
let dir = std::env::temp_dir();
|
||||
let output = LocalTerminal::default()
|
||||
.run(LocalCommand::new("pwd").with_cwd(&dir))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output.exit_code, 0);
|
||||
assert_eq!(output.stdout.trim(), dir.display().to_string());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user