use super::*;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
pub enum RotateOutputType {
#[default]
Keep,
Delete,
}
#[derive(Default)]
#[cfg_attr(feature = "cli", derive(clap::Args))]
pub struct RotateCommand {
pub files: Option<Vec<PathBuf>>,
#[cfg_attr(feature = "cli", arg(long))]
pub format: Option<String>,
#[cfg_attr(feature = "cli", arg(short, long))]
pub base: Option<PartitionExponentBase>,
#[cfg_attr(feature = "cli", arg(long, value_enum))]
pub output: RotateOutputType,
}
#[derive(Eq, PartialEq, Hash, Debug, Default)]
pub struct RotateOutput<'a> {
pub keep: Vec<&'a Path>,
pub delete: Vec<&'a Path>,
}
pub fn rotate<'a>(
files: &'a [PathBuf],
base: PartitionExponentBase,
format: &str,
) -> anyhow::Result<RotateOutput<'a>> {
if files.is_empty() {
return Ok(RotateOutput::default());
}
let mut dates = files
.iter()
.enumerate()
.map(|(i, path)| {
file_name(path)
.and_then(|file_name| {
NaiveDate::parse_and_remainder(file_name, format)
.with_context(|| format!("While parsing file name: '{file_name}'"))
})
.map(|(date, _)| (i, date))
})
.collect::<Result<Vec<_>, _>>()?;
dates.sort_by_key(|(_, d)| *d);
let day0 = &dates[0].1;
let result = unsafe {
dates
.iter()
.partition_with_get(
|index| PartitionSize::exponential(base, index),
|(_, d)| (*d - *day0).num_days().cast_unsigned(),
)
.map(|(p_i, (i, _))| (p_i, *i))
.fold(
(RotateOutput::default(), None),
|(mut result, mut current_index), (p_i, i)| {
if Some(p_i) != current_index {
current_index = Some(p_i);
result.keep.push(&*files[i])
} else {
result.delete.push(&*files[i])
}
(result, current_index)
},
)
.0
};
Ok(result)
}
fn file_name(path: &Path) -> anyhow::Result<&str> {
#[cfg(feature = "std")]
{
path.file_name()
.ok_or_else(|| anyhow::anyhow!("Expected a file, but received: {path:?}"))
.and_then(|file_name| {
file_name.to_str().ok_or_else(|| {
anyhow::anyhow!("Expected a UTF8 valid filename, but received: {path:?}")
})
})
}
#[cfg(not(feature = "std"))]
{
Ok(path
.rfind(&['/', '\\'])
.map(|index| &path[index..])
.unwrap_or(path))
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{vec, vec::Vec};
use chrono::{NaiveDate, TimeDelta};
use std::format;
use std::path::PathBuf;
#[test]
fn simple_test() {
let day0 = NaiveDate::from_ymd_opt(2020, 01, 16).unwrap();
let files = (0..60)
.map(|i| day0 + TimeDelta::days(i))
.map(|date| format!("{date}.tar"))
.map(PathBuf::from)
.collect::<Vec<_>>();
let result = rotate::rotate(
&files,
PartitionExponentBase::new(1.3f32).unwrap(),
"%Y-%m-%d",
)
.unwrap();
assert_eq!(
result.keep,
vec![0, 1, 3, 5, 8, 11, 15, 20, 27, 36, 47]
.into_iter()
.map(|i| &files[i])
.collect::<Vec<_>>()
);
assert_eq!(
result.delete,
vec![
2, 4, 6, 7, 9, 10, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 28, 29, 30,
31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59
]
.into_iter()
.map(|i| &files[i])
.collect::<Vec<_>>()
);
}
}