mod archive;
mod clean;
pub mod dto;
pub mod mtime;
mod vcs;
use crate::{
build_tool_manager::BuildToolManager,
build_tools::{BuildStatus, BuildTool},
};
use anyhow::format_err;
use std::{
fmt,
path::{Path, PathBuf},
};
use time::{Duration, OffsetDateTime};
use tracing::{trace, warn};
use self::{mtime::dir_mtime, vcs::VersionControlSystem};
#[derive(Debug)]
pub struct Project {
name: String,
path: PathBuf,
build_tools: Vec<Box<dyn BuildTool>>,
vcs: Option<VersionControlSystem>,
mtime: OffsetDateTime,
}
impl Project {
pub fn from_dir(
path: &Path,
project_filter: &ProjectFilter,
build_tool_manager: &BuildToolManager,
) -> anyhow::Result<Option<Project>> {
let build_tools = build_tool_manager.probe(path);
if build_tools.is_empty() {
return Ok(None);
}
if let StatusFilter::ExceptClean = project_filter.status {
if build_tools
.iter()
.all(|tool| matches!(tool.status(), Ok(BuildStatus::Clean)))
{
return Ok(None);
}
}
let project_name = match project_name_from(path, &build_tools) {
Ok(name) => name,
Err(e) => {
warn!("Failed to determine project name for {path:?}: {e}");
return Ok(None);
}
};
let mtime = dir_mtime(path)
.ok_or_else(|| format_err!("BUG: build tool recognized but no files?!"))?;
let now = OffsetDateTime::now_utc();
if (now - mtime) < project_filter.min_stale {
trace!(
?path,
%mtime,
min_stale=%project_filter.min_stale,
"Project skipped due to recent mtime",
);
return Ok(None);
}
let vcs = VersionControlSystem::try_from(path)?;
Ok(Some(Project {
name: project_name,
path: path.to_owned(),
build_tools,
vcs,
mtime,
}))
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn path(&self) -> &Path {
self.path.as_path()
}
pub fn build_tools(&self) -> &[Box<dyn BuildTool>] {
&self.build_tools
}
pub fn vcs(&self) -> Option<&VersionControlSystem> {
self.vcs.as_ref()
}
pub fn mtime(&self) -> OffsetDateTime {
self.mtime
}
pub fn freeable_bytes(&self) -> u64 {
self.build_tools
.iter()
.map(|x| match x.status() {
Ok(BuildStatus::Built { freeable_bytes }) => freeable_bytes,
_ => 0,
})
.sum::<u64>()
}
}
fn project_name_from(path: &Path, build_tools: &[Box<dyn BuildTool>]) -> anyhow::Result<String> {
for tool in build_tools {
match tool.project_name() {
Some(Ok(name)) => return Ok(name),
Some(Err(e)) => return Err(e),
None => continue,
}
}
let dirname = path.components().last().ok_or_else(|| {
format_err!(
"Could not determine project name: Could not determine the directory name of {path:?}"
)
})?;
Ok(dirname.as_os_str().to_string_lossy().to_string())
}
impl fmt::Display for Project {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let path = &self.path;
let tools = self
.build_tools
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
let vcs = self
.vcs
.as_ref()
.map(|x| format!("{x:?}"))
.unwrap_or_else(|| "none".to_owned());
write!(f, "{} ({}; VCS: {})", path.display(), tools, vcs)
}
}
#[derive(Debug)]
pub struct ProjectFilter {
pub min_stale: Duration,
pub status: StatusFilter,
}
#[derive(Debug)]
pub enum StatusFilter {
Any,
ExceptClean,
}