use std::fs;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use serde_json::to_string_pretty;
use crate::analysis::AnalysisResult;
use crate::error::Error;
use crate::error::Result;
use crate::grouper::DuplicateGroup;
use crate::suppression::baseline_entry::BaselineEntry;
use crate::suppression::baseline_kind::BaselineKind;
pub const DEFAULT_BASELINE_FILE: &str = "dry4rust-baseline.json";
pub const FORMAT_VERSION: u32 = 1;
#[must_use]
pub fn baseline_path(root: &Path, configured: Option<&Path>) -> PathBuf {
let named = configured.unwrap_or_else(|| Path::new(DEFAULT_BASELINE_FILE));
if named.is_absolute() {
named.to_path_buf()
} else {
root.join(named)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaselineFile {
pub version: u32,
pub entries: Vec<BaselineEntry>,
}
impl BaselineFile {
#[must_use]
pub fn record(result: &AnalysisResult) -> Self {
let mut entries: Vec<BaselineEntry> = [
(BaselineKind::Exact, &result.exact_groups),
(BaselineKind::Near, &result.near_groups),
(BaselineKind::SubExact, &result.sub_exact_groups),
(BaselineKind::SubNear, &result.sub_near_groups),
]
.into_iter()
.flat_map(|(kind, groups)| groups.iter().map(move |g| BaselineEntry::of(kind, g)))
.collect();
entries.sort_by(|a, b| {
a.kind
.cmp(&b.kind)
.then_with(|| a.fingerprint.cmp(&b.fingerprint))
});
Self {
version: FORMAT_VERSION,
entries,
}
}
pub fn load(path: &Path) -> Result<Self> {
let content = fs::read_to_string(path).map_err(|e| Error::Baseline {
path: path.to_path_buf(),
problem: if e.kind() == ErrorKind::NotFound {
String::from("no such file; record one with `cargo dry4rust baseline`")
} else {
e.to_string()
},
})?;
let file: Self = from_str(&content).map_err(|e| Error::Baseline {
path: path.to_path_buf(),
problem: e.to_string(),
})?;
if file.version == FORMAT_VERSION {
Ok(file)
} else {
Err(Error::Baseline {
path: path.to_path_buf(),
problem: format!(
"written in format {}, this build reads format {FORMAT_VERSION}; \
re-record it with `cargo dry4rust baseline`",
file.version
),
})
}
}
pub fn save(&self, path: &Path) -> Result<()> {
let problem = |e: IoError| Error::Baseline {
path: path.to_path_buf(),
problem: e.to_string(),
};
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent).map_err(problem)?;
}
let json = to_string_pretty(self).map_err(|e| Error::Baseline {
path: path.to_path_buf(),
problem: e.to_string(),
})?;
fs::write(path, format!("{json}\n")).map_err(problem)
}
#[must_use]
pub const fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn admits(&self, kind: BaselineKind, group: &DuplicateGroup) -> bool {
self.entries.iter().any(|entry| entry.admits(kind, group))
}
}