use crate::{
formatter::{Formatter, FormatterName},
get_path_meta, FileMeta,
};
use anyhow::Result;
use log::{debug, error, warn};
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use std::collections::BTreeMap;
use std::fs::{create_dir_all, read_to_string, File};
use std::io::Write;
use std::path::{Path, PathBuf};
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct FormatterInfo {
pub command: PathBuf,
pub command_resolved: PathBuf,
pub options: Vec<String>,
pub work_dir: PathBuf,
pub command_meta: FileMeta,
pub command_resolved_meta: FileMeta,
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct CacheManifest {
pub formatters: BTreeMap<FormatterName, FormatterInfo>,
pub matches: BTreeMap<FormatterName, BTreeMap<PathBuf, FileMeta>>,
}
impl Clone for CacheManifest {
fn clone(&self) -> Self {
Self {
formatters: self.formatters.clone(),
matches: self.matches.clone(),
}
}
}
impl CacheManifest {
pub fn try_load(cache_dir: &Path, treefmt_toml: &Path) -> Result<Self> {
let manifest_path = get_manifest_path(cache_dir, treefmt_toml);
debug!("cache: loading from {}", manifest_path.display());
let content = read_to_string(&manifest_path)?;
let manifest = toml::from_str(&content)?;
Ok(manifest)
}
#[must_use]
pub fn load(cache_dir: &Path, treefmt_toml: &Path) -> Self {
match Self::try_load(cache_dir, treefmt_toml) {
Ok(manifest) => manifest,
Err(err) => {
warn!("cache: failed to load the manifest due to: {}", err);
Self::default()
}
}
}
pub fn try_write(self, cache_dir: &Path, treefmt_toml: &Path) -> Result<()> {
let manifest_path = get_manifest_path(cache_dir, treefmt_toml);
debug!("cache: writing to {}", manifest_path.display());
create_dir_all(manifest_path.parent().unwrap())?;
let mut f = File::create(manifest_path)?;
f.write_all(
format!(
"# DO NOT HAND EDIT THIS FILE\n\n{}",
toml::to_string_pretty(&self)?
)
.as_bytes(),
)?;
Ok(())
}
pub fn write(self, cache_dir: &Path, treefmt_toml: &Path) {
if let Err(err) = self.try_write(cache_dir, treefmt_toml) {
warn!("cache: failed to write to disk: {}", err);
};
}
pub fn update_formatters(&mut self, formatters: BTreeMap<FormatterName, Formatter>) {
let mut old_formatters = std::mem::take(&mut self.formatters);
for (name, fmt) in formatters {
match load_formatter_info(fmt) {
Ok(new_fmt_info) => {
if let Some(old_fmt_info) = old_formatters.remove(&name) {
if old_fmt_info != new_fmt_info {
self.matches.remove(&name);
}
}
self.formatters.insert(name, new_fmt_info);
}
Err(err) => {
error!("cache: failed to load the formatter info {}", err)
}
}
}
for name in old_formatters.keys() {
self.matches.remove(name);
}
}
#[must_use]
pub fn filter_matches(
&self,
matches: BTreeMap<FormatterName, BTreeMap<PathBuf, FileMeta>>,
) -> BTreeMap<FormatterName, BTreeMap<PathBuf, FileMeta>> {
matches
.into_iter()
.fold(BTreeMap::new(), |mut sum, (key, path_infos)| {
let new_path_infos = match self.matches.get(&key) {
Some(prev_paths) => {
path_infos
.into_iter()
.fold(BTreeMap::new(), |mut sum, (path, meta)| {
let prev_meta = prev_paths
.get(&path)
.unwrap_or(&FileMeta { mtime: -1, size: 0 });
if prev_meta != &meta {
sum.insert(path, meta);
}
sum
})
}
None => path_infos,
};
sum.insert(key, new_path_infos);
sum
})
}
pub fn add_results(&mut self, matches: BTreeMap<FormatterName, BTreeMap<PathBuf, FileMeta>>) {
for (name, path_infos) in matches {
if let Some(old_path_infos) = self.matches.get_mut(&name) {
old_path_infos.extend(path_infos);
} else {
self.matches.insert(name, path_infos);
}
}
}
}
fn load_formatter_info(fmt: Formatter) -> Result<FormatterInfo> {
let command = fmt.command;
let command_meta = get_path_meta(&command)?;
let command_resolved = std::fs::canonicalize(command.clone())?;
let command_resolved_meta = get_path_meta(&command_resolved)?;
let options = fmt.options;
let work_dir = fmt.work_dir;
Ok(FormatterInfo {
command,
command_meta,
command_resolved,
command_resolved_meta,
options,
work_dir,
})
}
fn get_manifest_path(cache_dir: &Path, treefmt_toml: &Path) -> PathBuf {
assert!(cache_dir.is_absolute());
assert!(treefmt_toml.is_absolute());
let path_bytes = treefmt_toml.to_string_lossy();
let treefmt_hash = Sha1::digest(path_bytes.as_bytes());
let filename = format!("{:x}.toml", treefmt_hash);
cache_dir.join(filename)
}