specfold_core/files/store/
command.rs1use super::types::Store;
5use anyhow::{Result, anyhow};
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9pub struct CommandStore {
10 pub root: PathBuf,
13 pub put: Option<Vec<String>>,
14 pub get: Option<Vec<String>>,
15 pub exists: Option<Vec<String>>,
16}
17
18impl CommandStore {
19 fn run(&self, template: &[String], key: &str, src: &str, dst: &str) -> Result<std::process::Output> {
20 let args: Vec<String> =
21 template.iter().map(|a| a.replace("{key}", key).replace("{src}", src).replace("{dst}", dst)).collect();
22 let (prog, rest) = args.split_first().ok_or_else(|| anyhow!("empty store command"))?;
23 let local = self.root.join(prog);
24 let program = if prog.contains('/') && local.exists() { local } else { PathBuf::from(prog) };
25 Command::new(&program)
26 .args(rest)
27 .current_dir(&self.root)
28 .output()
29 .map_err(|e| anyhow!("cannot run {prog}: {e}"))
30 }
31}
32
33impl Store for CommandStore {
34 fn put(&self, src: &Path, key: &str) -> Result<()> {
35 let t = self.put.as_ref().ok_or_else(|| anyhow!("files.store.put is not configured"))?;
36 let out = self.run(t, key, &src.to_string_lossy(), "")?;
37 if !out.status.success() {
38 return Err(failed("put", t, &out));
39 }
40 Ok(())
41 }
42
43 fn get(&self, key: &str, dst: &Path) -> Result<()> {
44 let t = self.get.as_ref().ok_or_else(|| anyhow!("files.store.get is not configured"))?;
45 if let Some(p) = dst.parent() {
46 std::fs::create_dir_all(p)?;
47 }
48 let out = self.run(t, key, "", &dst.to_string_lossy())?;
49 if !out.status.success() {
50 return Err(failed("get", t, &out));
51 }
52 Ok(())
53 }
54
55 fn exists(&self, key: &str) -> Result<Option<bool>> {
56 let Some(t) = &self.exists else { return Ok(None) };
57 Ok(Some(self.run(t, key, "", "")?.status.success()))
58 }
59}
60
61fn failed(what: &str, template: &[String], out: &std::process::Output) -> anyhow::Error {
64 let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
65 let why = if err.is_empty() { format!("`{}` exited with {}", template.join(" "), out.status) } else { err };
66 anyhow::Error::new(std::io::Error::other(format!(
67 "store {what} failed: {why}; next: check the files.store.{what} command in spec.toml"
68 )))
69}