Skip to main content

systemprompt_cloud/docker/
command.rs

1//! The process-execution seam [`super::DockerCli`] runs every command through.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use std::io;
7use std::path::{Path, PathBuf};
8use std::process::{Command, ExitStatus, Output, Stdio};
9
10#[derive(Debug, Clone)]
11pub struct CommandSpec {
12    pub program: String,
13    pub args: Vec<String>,
14    pub current_dir: Option<PathBuf>,
15}
16
17impl CommandSpec {
18    pub fn docker<I, S>(args: I) -> Self
19    where
20        I: IntoIterator<Item = S>,
21        S: Into<String>,
22    {
23        Self {
24            program: "docker".to_owned(),
25            args: args.into_iter().map(Into::into).collect(),
26            current_dir: None,
27        }
28    }
29
30    pub fn git<I, S>(repo: &Path, args: I) -> Self
31    where
32        I: IntoIterator<Item = S>,
33        S: Into<String>,
34    {
35        Self {
36            program: "git".to_owned(),
37            args: args.into_iter().map(Into::into).collect(),
38            current_dir: Some(repo.to_path_buf()),
39        }
40    }
41
42    #[must_use]
43    pub fn rendered(&self) -> String {
44        format!("{} {}", self.program, self.args.join(" "))
45    }
46}
47
48pub trait CommandRunner: Send + Sync {
49    fn output(&self, spec: &CommandSpec) -> io::Result<Output>;
50    fn status(&self, spec: &CommandSpec) -> io::Result<ExitStatus>;
51    fn status_with_stdin(&self, spec: &CommandSpec, stdin: &[u8]) -> io::Result<ExitStatus>;
52}
53
54#[derive(Debug, Clone, Copy, Default)]
55pub struct SystemCommandRunner;
56
57impl SystemCommandRunner {
58    fn command(spec: &CommandSpec) -> Command {
59        let mut command = Command::new(&spec.program);
60        command.args(&spec.args);
61        if let Some(dir) = &spec.current_dir {
62            command.current_dir(dir);
63        }
64        command
65    }
66}
67
68impl CommandRunner for SystemCommandRunner {
69    fn output(&self, spec: &CommandSpec) -> io::Result<Output> {
70        Self::command(spec).output()
71    }
72
73    fn status(&self, spec: &CommandSpec) -> io::Result<ExitStatus> {
74        Self::command(spec).status()
75    }
76
77    fn status_with_stdin(&self, spec: &CommandSpec, stdin: &[u8]) -> io::Result<ExitStatus> {
78        let mut command = Self::command(spec);
79        command.stdin(Stdio::piped());
80        let mut child = command.spawn()?;
81        if let Some(mut handle) = child.stdin.take() {
82            io::Write::write_all(&mut handle, stdin)?;
83        }
84        child.wait()
85    }
86}