use crate::prelude::*;
use crate::state::template_refs::TemplateReferences;
use std::collections::BTreeMap;
use std::collections::HashMap as Map;
use std::hash::Hash;
use std::sync::Mutex;
#[derive(Debug, Clone)]
pub struct HeadSnapshot {
pub detached: bool,
pub branch: Option<String>,
pub commit: Option<(String, i64, i32)>,
}
#[derive(Default)]
pub struct RunCache {
environment: BTreeMap<String, String>,
project_roots: Mutex<Map<PathBuf, Arc<HashMap<String, PathBuf>>>>,
templates: Mutex<Map<PathBuf, Arc<Vec<PathBuf>>>>,
template_text: Mutex<Map<PathBuf, Arc<String>>>,
template_refs: Mutex<Map<PathBuf, Arc<TemplateReferences>>>,
submodule_roots: Mutex<Map<PathBuf, Arc<Vec<PathBuf>>>>,
version_counts: Mutex<Map<(PathBuf, u32), (u32, u32)>>,
committing: Mutex<Option<PathBuf>>,
tags: Mutex<Map<PathBuf, Arc<Vec<String>>>>,
discovered_tags: Mutex<Map<PathBuf, Arc<Vec<String>>>>,
nearest_tags: Mutex<Map<PathBuf, Arc<Option<String>>>>,
heads: Mutex<Map<PathBuf, Arc<HeadSnapshot>>>,
remotes: Mutex<Map<PathBuf, Arc<std::result::Result<Vec<String>, String>>>>,
remote_urls: Mutex<Map<(PathBuf, String), Arc<String>>>,
remote_tags: Mutex<Map<(PathBuf, String), Arc<Vec<String>>>>,
}
impl RunCache {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub fn with_variable(variable: &str, value: &str) -> Arc<Self> {
Arc::new(Self {
environment: BTreeMap::from([(variable.to_string(), value.to_string())]),
..Self::default()
})
}
pub fn environment(&self) -> &BTreeMap<String, String> {
&self.environment
}
pub fn project_roots(
&self,
scan_root: &Path,
compute: impl FnOnce() -> Result<HashMap<String, PathBuf>>,
) -> Result<Arc<HashMap<String, PathBuf>>> {
memo(&self.project_roots, scan_root.to_path_buf(), || {
compute().map(Arc::new)
})
}
pub fn templates_under(
&self,
root: &Path,
compute: impl FnOnce() -> Result<Vec<PathBuf>>,
) -> Result<Arc<Vec<PathBuf>>> {
memo(&self.templates, root.to_path_buf(), || {
compute().map(Arc::new)
})
}
pub fn template_text(
&self,
path: &Path,
compute: impl FnOnce() -> Result<String>,
) -> Result<Arc<String>> {
memo(&self.template_text, path.to_path_buf(), || {
compute().map(Arc::new)
})
}
pub fn template_references(
&self,
path: &Path,
compute: impl FnOnce() -> Result<TemplateReferences>,
) -> Result<Arc<TemplateReferences>> {
memo(&self.template_refs, path.to_path_buf(), || {
compute().map(Arc::new)
})
}
pub fn submodule_roots(
&self,
project_root: &Path,
compute: impl FnOnce() -> Vec<PathBuf>,
) -> Arc<Vec<PathBuf>> {
memo(&self.submodule_roots, project_root.to_path_buf(), || {
Ok(Arc::new(compute()))
})
.expect("an infallible closure cannot fail")
}
pub fn commit_is_being_made_in(&self, repo_root: &Path) {
*self.committing.lock().expect("the run cache is unusable after a panic") = Some(place(repo_root));
}
pub fn is_being_committed(&self, repo_root: &Path) -> bool {
self.committing
.lock()
.expect("the run cache is unusable after a panic")
.as_deref()
.is_some_and(|committing| committing == place(repo_root))
}
pub fn version_counts(
&self,
repo_root: &Path,
major: u32,
compute: impl FnOnce() -> Result<(u32, u32)>,
) -> Result<(u32, u32)> {
memo(&self.version_counts, (repo_root.to_path_buf(), major), compute)
}
pub fn tags(
&self,
repo_root: &Path,
compute: impl FnOnce() -> Result<Vec<String>>,
) -> Result<Arc<Vec<String>>> {
memo(&self.tags, repo_root.to_path_buf(), || compute().map(Arc::new))
}
pub fn discovered_tags(
&self,
path: &Path,
compute: impl FnOnce() -> Result<Vec<String>>,
) -> Result<Arc<Vec<String>>> {
memo(&self.discovered_tags, path.to_path_buf(), || {
compute().map(Arc::new)
})
}
pub fn nearest_tag(
&self,
repo_root: &Path,
compute: impl FnOnce() -> Result<Option<String>>,
) -> Result<Arc<Option<String>>> {
memo(&self.nearest_tags, repo_root.to_path_buf(), || compute().map(Arc::new))
}
pub fn head(
&self,
repo_root: &Path,
compute: impl FnOnce() -> Result<HeadSnapshot>,
) -> Result<Arc<HeadSnapshot>> {
memo(&self.heads, repo_root.to_path_buf(), || compute().map(Arc::new))
}
pub fn remotes(
&self,
repo_root: &Path,
compute: impl FnOnce() -> std::result::Result<Vec<String>, String>,
) -> Arc<std::result::Result<Vec<String>, String>> {
memo(&self.remotes, repo_root.to_path_buf(), || {
Ok(Arc::new(compute()))
})
.expect("an infallible closure cannot fail")
}
pub fn remote_url(
&self,
repo_root: &Path,
remote: &str,
compute: impl FnOnce() -> Result<String>,
) -> Result<Arc<String>> {
let key = (repo_root.to_path_buf(), remote.to_string());
memo(&self.remote_urls, key, || compute().map(Arc::new))
}
pub fn remote_tags(
&self,
repo_root: &Path,
remote: &str,
compute: impl FnOnce() -> Result<Vec<String>>,
) -> Result<Arc<Vec<String>>> {
let key = (repo_root.to_path_buf(), remote.to_string());
memo(&self.remote_tags, key, || compute().map(Arc::new))
}
pub fn forget_repo(&self, repo_root: &Path) {
let root = repo_root.to_path_buf();
drop_key(&self.tags, &root);
drop_key(&self.discovered_tags, &root);
drop_key(&self.nearest_tags, &root);
drop_key(&self.heads, &root);
drop_key(&self.remotes, &root);
if let Ok(mut map) = self.version_counts.lock() {
map.retain(|(path, _), _| path != &root);
}
if let Ok(mut map) = self.remote_urls.lock() {
map.retain(|(path, _), _| path != &root);
}
if let Ok(mut map) = self.remote_tags.lock() {
map.retain(|(path, _), _| path != &root);
}
}
pub fn note_written(&self, path: &Path) {
let is_template = path
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.ends_with(".wstemplate"));
if !is_template {
return;
}
clear(&self.templates);
clear(&self.template_text);
clear(&self.template_refs);
}
}
fn memo<K, V>(slot: &Mutex<Map<K, V>>, key: K, compute: impl FnOnce() -> Result<V>) -> Result<V>
where
K: Eq + Hash + Clone,
V: Clone,
{
if let Ok(map) = slot.lock() {
if let Some(hit) = map.get(&key) {
return Ok(hit.clone());
}
}
let value = compute()?;
if let Ok(mut map) = slot.lock() {
map.insert(key, value.clone());
}
Ok(value)
}
fn drop_key<V>(slot: &Mutex<Map<PathBuf, V>>, key: &Path) {
if let Ok(mut map) = slot.lock() {
map.remove(key);
}
}
fn clear<K, V>(slot: &Mutex<Map<K, V>>) {
if let Ok(mut map) = slot.lock() {
map.clear();
}
}
fn place(path: &Path) -> PathBuf {
crate::platform::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}