|
| 1 | +// Decision: expose `python` command in CLI via host command execution. |
| 2 | +// Prefer `monty` backend when available to match product requirement. |
| 3 | +// Fallback to `python3` to keep CLI usable where monty isn't installed. |
| 4 | + |
| 5 | +use bashkit::{async_trait, Builtin, BuiltinContext, ExecResult}; |
| 6 | +use std::env; |
| 7 | +use std::os::unix::fs::PermissionsExt; |
| 8 | +use std::path::PathBuf; |
| 9 | +use std::process::Command; |
| 10 | + |
| 11 | +pub struct PythonBuiltin; |
| 12 | + |
| 13 | +impl PythonBuiltin { |
| 14 | + pub fn new() -> Self { |
| 15 | + Self |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +#[async_trait] |
| 20 | +impl Builtin for PythonBuiltin { |
| 21 | + async fn execute(&self, ctx: BuiltinContext<'_>) -> bashkit::Result<ExecResult> { |
| 22 | + let (program, mut args) = if command_exists("monty") { |
| 23 | + ("monty", vec!["python".to_string()]) |
| 24 | + } else { |
| 25 | + ("python3", Vec::new()) |
| 26 | + }; |
| 27 | + |
| 28 | + args.extend(ctx.args.iter().cloned()); |
| 29 | + |
| 30 | + let output = match Command::new(program).args(&args).output() { |
| 31 | + Ok(output) => output, |
| 32 | + Err(err) => { |
| 33 | + return Ok(ExecResult::err( |
| 34 | + format!("python: failed to start {program}: {err}\n"), |
| 35 | + 1, |
| 36 | + )); |
| 37 | + } |
| 38 | + }; |
| 39 | + |
| 40 | + Ok(ExecResult { |
| 41 | + stdout: String::from_utf8_lossy(&output.stdout).to_string(), |
| 42 | + stderr: String::from_utf8_lossy(&output.stderr).to_string(), |
| 43 | + exit_code: output.status.code().unwrap_or(1), |
| 44 | + ..Default::default() |
| 45 | + }) |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +fn command_exists(cmd: &str) -> bool { |
| 50 | + let Some(path) = env::var_os("PATH") else { |
| 51 | + return false; |
| 52 | + }; |
| 53 | + |
| 54 | + env::split_paths(&path).any(|dir| is_executable_file(dir.join(cmd))) |
| 55 | +} |
| 56 | + |
| 57 | +fn is_executable_file(path: PathBuf) -> bool { |
| 58 | + let Ok(metadata) = std::fs::metadata(path) else { |
| 59 | + return false; |
| 60 | + }; |
| 61 | + metadata.is_file() && (metadata.permissions().mode() & 0o111 != 0) |
| 62 | +} |
0 commit comments