greentic_dev/delegate/
component.rs1use std::ffi::OsString;
2
3use anyhow::{Context, Result, anyhow, bail};
4use which::which;
5
6use crate::config::{self, GreenticConfig};
7use crate::util::process::{self, CommandOutput, CommandSpec, StreamMode};
8
9const TOOL_NAME: &str = "greentic-component";
10
11pub struct ComponentDelegate {
12 program: OsString,
13}
14
15impl ComponentDelegate {
16 pub fn from_config(config: &GreenticConfig) -> Result<Self> {
17 let resolved = resolve_program(config)?;
18 Ok(Self {
19 program: resolved.program,
20 })
21 }
22
23 pub fn run_passthrough(&self, args: &[String]) -> Result<()> {
24 let argv: Vec<OsString> = args.iter().map(OsString::from).collect();
25 let label = args.first().map(|s| s.as_str()).unwrap_or("<component>");
26 let output = self.exec(argv, false)?;
27 self.ensure_success(label, false, &output)
28 }
29
30 fn exec(&self, args: Vec<OsString>, capture: bool) -> Result<CommandOutput> {
31 let mut spec = CommandSpec::new(self.program.clone());
32 spec.args = args;
33 if capture {
34 spec.stdout = StreamMode::Capture;
35 spec.stderr = StreamMode::Capture;
36 } else {
37 spec.stdout = StreamMode::Inherit;
38 spec.stderr = StreamMode::Inherit;
39 }
40 process::run(spec)
41 .with_context(|| format!("failed to spawn `{}`", self.program.to_string_lossy()))
42 }
43
44 fn ensure_success(&self, label: &str, capture: bool, output: &CommandOutput) -> Result<()> {
45 if output.status.success() {
46 return Ok(());
47 }
48
49 if capture
50 && let Some(stderr) = output.stderr.as_ref()
51 && !stderr.is_empty()
52 {
53 eprintln!("{}", String::from_utf8_lossy(stderr));
54 }
55 let code = output.status.code().unwrap_or_default();
56 bail!(
57 "`{}` {label} failed with exit code {code}",
58 self.program.to_string_lossy()
59 );
60 }
61}
62
63struct ResolvedProgram {
64 program: OsString,
65}
66
67fn resolve_program(config: &GreenticConfig) -> Result<ResolvedProgram> {
68 if let Some(custom) = config.tools.greentic_component.path.as_ref() {
69 if !custom.exists() {
70 bail!(
71 "configured greentic-component path `{}` does not exist",
72 custom.display()
73 );
74 }
75 return Ok(ResolvedProgram {
76 program: custom.as_os_str().to_os_string(),
77 });
78 }
79
80 match which(TOOL_NAME) {
81 Ok(path) => Ok(ResolvedProgram {
82 program: path.into_os_string(),
83 }),
84 Err(error) => {
85 let config_hint = config::config_path()
86 .map(|path| path.display().to_string())
87 .unwrap_or_else(|| "$XDG_CONFIG_HOME/greentic-dev/config.toml".to_string());
88 Err(anyhow!(
89 "failed to locate `{TOOL_NAME}` on PATH ({error}). Install it via `cargo install \
90 greentic-component` or set [tools.greentic-component].path in {config_hint}."
91 ))
92 }
93 }
94}