use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use indexmap::IndexMap;
use vantage_core::{Result, error};
use crate::rhai_engine::CompiledScript;
#[derive(Clone, Debug)]
pub struct CmdSpec {
pub script: Arc<str>,
pub detail: Option<Arc<str>>,
pub command: Option<String>,
pub env: IndexMap<String, String>,
}
impl CmdSpec {
pub fn new(script: impl Into<Arc<str>>) -> Self {
Self {
script: script.into(),
detail: None,
command: None,
env: IndexMap::new(),
}
}
pub fn with_detail(mut self, detail: impl Into<Arc<str>>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn with_command(mut self, command: impl Into<String>) -> Self {
self.command = Some(command.into());
self
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.insert(key.into(), value.into());
self
}
}
#[derive(Clone)]
pub struct Cmd {
command: Arc<str>,
env: Arc<IndexMap<String, String>>,
pass_path: bool,
base_dir: Option<Arc<Path>>,
scripts: Arc<IndexMap<String, CmdSpec>>,
compiled: Arc<Mutex<HashMap<String, Arc<CompiledScript>>>>,
compile_counts: Arc<Mutex<HashMap<String, usize>>>,
}
impl std::fmt::Debug for Cmd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cmd")
.field("command", &self.command)
.field("env", &self.env)
.field("pass_path", &self.pass_path)
.field("base_dir", &self.base_dir)
.field("scripts", &self.scripts)
.finish()
}
}
impl Cmd {
pub fn new(command: impl Into<Arc<str>>) -> Self {
Self {
command: command.into(),
env: Arc::new(IndexMap::new()),
pass_path: true,
base_dir: None,
scripts: Arc::new(IndexMap::new()),
compiled: Arc::new(Mutex::new(HashMap::new())),
compile_counts: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
Arc::make_mut(&mut self.env).insert(key.into(), value.into());
self
}
pub fn with_pass_path(mut self, pass_path: bool) -> Self {
self.pass_path = pass_path;
self
}
pub fn with_base_dir(mut self, base_dir: impl Into<PathBuf>) -> Self {
self.base_dir = Some(Arc::from(base_dir.into()));
self
}
pub fn with_script(self, name: impl Into<String>, script: impl Into<Arc<str>>) -> Self {
self.with_table(name, CmdSpec::new(script))
}
pub fn with_table(mut self, name: impl Into<String>, spec: CmdSpec) -> Self {
Arc::make_mut(&mut self.scripts).insert(name.into(), spec);
self
}
pub fn command(&self) -> &str {
&self.command
}
pub(crate) fn pass_path(&self) -> bool {
self.pass_path
}
pub(crate) fn base_dir(&self) -> Option<Arc<Path>> {
self.base_dir.clone()
}
pub(crate) fn spec_for(&self, name: &str) -> Result<&CmdSpec> {
self.scripts.get(name).ok_or_else(|| {
error!(
"no command script registered for table",
table = name.to_string()
)
})
}
pub(crate) fn effective_command(&self, spec: &CmdSpec) -> String {
spec.command
.clone()
.unwrap_or_else(|| self.command.to_string())
}
pub(crate) fn effective_env(&self, spec: &CmdSpec) -> IndexMap<String, String> {
let mut env = (*self.env).clone();
for (k, v) in &spec.env {
env.insert(k.clone(), v.clone());
}
env
}
pub(crate) fn compiled_list_script(&self, name: &str) -> Result<Arc<CompiledScript>> {
let spec = self.spec_for(name)?.clone();
self.compiled_for(name.to_string(), &spec, &spec.script)
}
pub(crate) fn compiled_detail_script(&self, name: &str) -> Result<Option<Arc<CompiledScript>>> {
let spec = self.spec_for(name)?.clone();
let Some(detail) = spec.detail.clone() else {
return Ok(None);
};
Ok(Some(self.compiled_for(
format!("{name}::detail"),
&spec,
&detail,
)?))
}
fn compiled_for(
&self,
cache_key: String,
spec: &CmdSpec,
script: &str,
) -> Result<Arc<CompiledScript>> {
let mut cache = self.compiled.lock().unwrap();
if let Some(existing) = cache.get(&cache_key) {
return Ok(existing.clone());
}
let command = self.effective_command(spec);
let env = self.effective_env(spec);
let compiled = Arc::new(CompiledScript::compile(
command,
env,
self.pass_path(),
self.base_dir(),
script,
)?);
*self
.compile_counts
.lock()
.unwrap()
.entry(cache_key.clone())
.or_insert(0) += 1;
cache.insert(cache_key, compiled.clone());
Ok(compiled)
}
pub(crate) fn has_detail_script(&self, name: &str) -> bool {
self.spec_for(name)
.map(|s| s.detail.is_some())
.unwrap_or(false)
}
pub fn compile_count(&self, name: &str) -> usize {
self.compile_counts
.lock()
.unwrap()
.get(name)
.copied()
.unwrap_or(0)
}
pub fn vista_factory(&self) -> crate::vista::factory::CmdVistaFactory {
crate::vista::factory::CmdVistaFactory::new(self.clone())
}
}