use std::path::Path;
use std::process::{Command, Output};
use crate::app::CommandResult;
use crate::error::XorcistError;
#[derive(Debug, Clone)]
pub struct JjRunner {
work_dir: Option<std::path::PathBuf>,
}
impl JjRunner {
pub fn new() -> Self {
Self { work_dir: None }
}
pub fn with_work_dir(mut self, dir: &Path) -> Self {
self.work_dir = Some(dir.to_path_buf());
self
}
pub fn run_capture(&self, args: &[&str]) -> Result<String, XorcistError> {
let output = self.execute(args)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(XorcistError::JjError(stderr.trim().to_string()));
}
String::from_utf8(output.stdout).map_err(|_| XorcistError::InvalidUtf8)
}
fn execute(&self, args: &[&str]) -> Result<Output, XorcistError> {
let mut cmd = Command::new("jj");
cmd.args(args);
if let Some(dir) = &self.work_dir {
cmd.current_dir(dir);
}
cmd.output().map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
XorcistError::JjNotFound
} else {
XorcistError::Io(e)
}
})
}
pub fn is_available(&self) -> bool {
Command::new("jj")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn execute_new(&self, parent: &str) -> Result<CommandResult, XorcistError> {
self.run_command(&["new", parent])
}
pub fn execute_new_with_message(
&self,
parent: &str,
message: &str,
) -> Result<CommandResult, XorcistError> {
self.run_command(&["new", parent, "-m", message])
}
pub fn execute_edit(&self, revision: &str) -> Result<CommandResult, XorcistError> {
self.run_command(&["edit", revision])
}
pub fn execute_describe(
&self,
revision: &str,
message: &str,
) -> Result<CommandResult, XorcistError> {
self.run_command(&["describe", revision, "-m", message])
}
pub fn execute_bookmark_set(
&self,
name: &str,
revision: &str,
) -> Result<CommandResult, XorcistError> {
self.run_command(&["bookmark", "set", name, "-r", revision])
}
pub fn execute_abandon(&self, revision: &str) -> Result<CommandResult, XorcistError> {
self.run_command(&["abandon", revision])
}
pub fn execute_squash(&self, revision: &str) -> Result<CommandResult, XorcistError> {
self.run_command(&["squash", "-r", revision])
}
pub fn execute_git_fetch(&self) -> Result<CommandResult, XorcistError> {
self.run_command(&["git", "fetch"])
}
pub fn execute_git_push(&self) -> Result<CommandResult, XorcistError> {
self.run_command(&["git", "push"])
}
pub fn execute_undo(&self) -> Result<CommandResult, XorcistError> {
self.run_command(&["undo"])
}
fn run_command(&self, args: &[&str]) -> Result<CommandResult, XorcistError> {
let output = self.execute(args)?;
let success = output.status.success();
let message = if success {
String::from_utf8_lossy(&output.stdout).trim().to_string()
} else {
String::from_utf8_lossy(&output.stderr).trim().to_string()
};
Ok(CommandResult { success, message })
}
}
impl Default for JjRunner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runner_creation() {
let runner = JjRunner::new();
assert!(runner.work_dir.is_none());
}
#[test]
fn test_runner_with_work_dir() {
let runner = JjRunner::new().with_work_dir(Path::new("/tmp"));
assert_eq!(runner.work_dir, Some(std::path::PathBuf::from("/tmp")));
}
}