use std::path::Path;
use std::path::PathBuf;
use crate::creation::config::CreationConfig;
use crate::creation::report::CreationReport;
use crate::error::ExtractionError;
use crate::error::Result;
use crate::formats::detect::ArchiveType;
#[derive(Debug, Default)]
pub struct ArchiveCreator {
output_path: Option<PathBuf>,
sources: Vec<PathBuf>,
config: CreationConfig,
}
impl ArchiveCreator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn output<P: AsRef<Path>>(mut self, path: P) -> Self {
self.output_path = Some(path.as_ref().to_path_buf());
self
}
#[must_use]
pub fn add_source<P: AsRef<Path>>(mut self, path: P) -> Self {
self.sources.push(path.as_ref().to_path_buf());
self
}
#[must_use]
pub fn sources<P: AsRef<Path>>(mut self, paths: &[P]) -> Self {
self.sources
.extend(paths.iter().map(|p| p.as_ref().to_path_buf()));
self
}
#[must_use]
pub fn config(mut self, config: CreationConfig) -> Self {
self.config = config;
self
}
#[must_use]
pub fn compression_level(mut self, level: u8) -> Self {
self.config.compression_level = Some(level);
self
}
#[must_use]
pub fn follow_symlinks(mut self, follow: bool) -> Self {
self.config.follow_symlinks = follow;
self
}
#[must_use]
pub fn include_hidden(mut self, include: bool) -> Self {
self.config.include_hidden = include;
self
}
#[must_use]
pub fn exclude<S: Into<String>>(mut self, pattern: S) -> Self {
self.config.exclude_patterns.push(pattern.into());
self
}
#[must_use]
pub fn strip_prefix<P: AsRef<Path>>(mut self, prefix: P) -> Self {
self.config.strip_prefix = Some(prefix.as_ref().to_path_buf());
self
}
#[must_use]
pub fn format(mut self, format: ArchiveType) -> Self {
self.config.format = Some(format);
self
}
pub fn create(self) -> Result<CreationReport> {
let output_path =
self.output_path
.ok_or_else(|| ExtractionError::InvalidConfiguration {
reason: "output path not set".to_string(),
})?;
if self.sources.is_empty() {
return Err(ExtractionError::InvalidConfiguration {
reason: "no source paths provided".to_string(),
});
}
self.config.validate()?;
crate::api::create_archive(&output_path, &self.sources, &self.config)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::formats::detect::ArchiveType;
use std::path::PathBuf;
#[test]
fn test_builder_basic() {
let creator = ArchiveCreator::new()
.output("test.tar.gz")
.add_source("src/");
assert_eq!(creator.output_path, Some(PathBuf::from("test.tar.gz")));
assert_eq!(creator.sources, vec![PathBuf::from("src/")]);
}
#[test]
fn test_builder_multiple_sources() {
let creator = ArchiveCreator::new()
.add_source("src/")
.add_source("Cargo.toml")
.add_source("README.md");
assert_eq!(creator.sources.len(), 3);
assert_eq!(creator.sources[0], PathBuf::from("src/"));
assert_eq!(creator.sources[1], PathBuf::from("Cargo.toml"));
assert_eq!(creator.sources[2], PathBuf::from("README.md"));
}
#[test]
fn test_builder_sources_array() {
let creator = ArchiveCreator::new().sources(&["src/", "Cargo.toml", "README.md"]);
assert_eq!(creator.sources.len(), 3);
}
#[test]
fn test_builder_config_methods() {
let creator = ArchiveCreator::new()
.compression_level(9)
.follow_symlinks(true)
.include_hidden(true)
.exclude("*.log")
.exclude("target/")
.strip_prefix("/base")
.format(ArchiveType::TarGz);
assert_eq!(creator.config.compression_level, Some(9));
assert!(creator.config.follow_symlinks);
assert!(creator.config.include_hidden);
assert!(
creator
.config
.exclude_patterns
.contains(&"*.log".to_string())
);
assert!(
creator
.config
.exclude_patterns
.contains(&"target/".to_string())
);
assert_eq!(creator.config.strip_prefix, Some(PathBuf::from("/base")));
assert_eq!(creator.config.format, Some(ArchiveType::TarGz));
}
#[test]
fn test_builder_no_output_error() {
let creator = ArchiveCreator::new().add_source("src/");
let result = creator.create();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ExtractionError::InvalidConfiguration { .. }
));
}
#[test]
fn test_builder_no_sources_error() {
let creator = ArchiveCreator::new().output("test.tar.gz");
let result = creator.create();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ExtractionError::InvalidConfiguration { .. }
));
}
#[test]
fn test_builder_compression_level() {
let creator = ArchiveCreator::new().compression_level(9);
assert_eq!(creator.config.compression_level, Some(9));
}
#[test]
fn test_builder_exclude_patterns() {
let creator = ArchiveCreator::new()
.exclude("*.log")
.exclude("*.tmp")
.exclude(".git");
assert!(
creator
.config
.exclude_patterns
.contains(&"*.log".to_string())
);
assert!(
creator
.config
.exclude_patterns
.contains(&"*.tmp".to_string())
);
assert!(
creator
.config
.exclude_patterns
.contains(&".git".to_string())
);
assert!(
creator
.config
.exclude_patterns
.contains(&".DS_Store".to_string())
);
}
#[test]
fn test_builder_full_config() {
let config = CreationConfig::default()
.with_follow_symlinks(true)
.with_compression_level(9);
let creator = ArchiveCreator::new()
.output("test.tar.gz")
.add_source("src/")
.config(config);
assert!(creator.config.follow_symlinks);
assert_eq!(creator.config.compression_level, Some(9));
}
#[test]
fn test_builder_default() {
let creator = ArchiveCreator::default();
assert_eq!(creator.output_path, None);
assert_eq!(creator.sources.len(), 0);
}
#[test]
fn test_builder_new() {
let creator = ArchiveCreator::new();
assert_eq!(creator.output_path, None);
assert_eq!(creator.sources.len(), 0);
}
}