From 715d8ab626b7cd477202322566db23a62b3456e6 Mon Sep 17 00:00:00 2001 From: lashman Date: Fri, 6 Mar 2026 01:41:23 +0200 Subject: [PATCH] Add ProcessingJob type with source management and output path resolution All 6 pipeline tests passing. --- pixstrip-core/src/pipeline.rs | 84 ++++++++++++++++++++++++++- pixstrip-core/tests/pipeline_tests.rs | 61 +++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 pixstrip-core/tests/pipeline_tests.rs diff --git a/pixstrip-core/src/pipeline.rs b/pixstrip-core/src/pipeline.rs index a5b88f7..d13c85a 100644 --- a/pixstrip-core/src/pipeline.rs +++ b/pixstrip-core/src/pipeline.rs @@ -1 +1,83 @@ -// Processing pipeline +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; + +use crate::operations::*; +use crate::types::{ImageFormat, ImageSource}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProcessingJob { + pub input_dir: PathBuf, + pub output_dir: PathBuf, + #[serde(skip)] + pub sources: Vec, + pub resize: Option, + pub rotation: Option, + pub flip: Option, + pub convert: Option, + pub compress: Option, + pub metadata: Option, + pub watermark: Option, + pub rename: Option, + pub preserve_directory_structure: bool, +} + +impl ProcessingJob { + pub fn new(input_dir: impl AsRef, output_dir: impl AsRef) -> Self { + Self { + input_dir: input_dir.as_ref().to_path_buf(), + output_dir: output_dir.as_ref().to_path_buf(), + sources: Vec::new(), + resize: None, + rotation: None, + flip: None, + convert: None, + compress: None, + metadata: None, + watermark: None, + rename: None, + preserve_directory_structure: false, + } + } + + pub fn add_source(&mut self, path: impl AsRef) { + self.sources.push(ImageSource::from_path(path)); + } + + pub fn operation_count(&self) -> usize { + let mut count = 0; + if self.resize.is_some() { count += 1; } + if self.rotation.is_some() { count += 1; } + if self.flip.is_some() { count += 1; } + if self.convert.is_some() { count += 1; } + if self.compress.is_some() { count += 1; } + if self.metadata.is_some() { count += 1; } + if self.watermark.is_some() { count += 1; } + if self.rename.is_some() { count += 1; } + count + } + + pub fn output_path_for( + &self, + source: &ImageSource, + output_format: Option, + ) -> PathBuf { + let stem = source + .path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("output"); + + let ext = output_format + .map(|f| f.extension()) + .or_else(|| { + source + .path + .extension() + .and_then(|e| e.to_str()) + }) + .unwrap_or("bin"); + + self.output_dir.join(format!("{}.{}", stem, ext)) + } +} diff --git a/pixstrip-core/tests/pipeline_tests.rs b/pixstrip-core/tests/pipeline_tests.rs new file mode 100644 index 0000000..27f00b2 --- /dev/null +++ b/pixstrip-core/tests/pipeline_tests.rs @@ -0,0 +1,61 @@ +use pixstrip_core::pipeline::*; +use pixstrip_core::operations::*; +use pixstrip_core::types::*; + +#[test] +fn processing_job_default_has_no_operations() { + let job = ProcessingJob::new("/tmp/input/", "/tmp/output/"); + assert!(job.resize.is_none()); + assert!(job.convert.is_none()); + assert!(job.compress.is_none()); + assert!(job.metadata.is_none()); + assert!(job.watermark.is_none()); + assert!(job.rename.is_none()); + assert!(job.sources.is_empty()); +} + +#[test] +fn processing_job_add_sources() { + let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/"); + job.add_source("/tmp/input/photo.jpg"); + job.add_source("/tmp/input/image.png"); + assert_eq!(job.sources.len(), 2); + assert_eq!(job.sources[0].original_format, Some(ImageFormat::Jpeg)); + assert_eq!(job.sources[1].original_format, Some(ImageFormat::Png)); +} + +#[test] +fn processing_job_with_resize() { + let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/"); + job.resize = Some(ResizeConfig::ByWidth(1200)); + assert!(job.resize.is_some()); +} + +#[test] +fn processing_job_operation_count() { + let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/"); + assert_eq!(job.operation_count(), 0); + job.resize = Some(ResizeConfig::ByWidth(1200)); + assert_eq!(job.operation_count(), 1); + job.compress = Some(CompressConfig::Preset(QualityPreset::High)); + assert_eq!(job.operation_count(), 2); + job.metadata = Some(MetadataConfig::StripAll); + assert_eq!(job.operation_count(), 3); +} + +#[test] +fn processing_job_output_path() { + let job = ProcessingJob::new("/tmp/input/", "/tmp/output/"); + let source = ImageSource::from_path("/tmp/input/photo.jpg"); + let output = job.output_path_for(&source, None); + assert_eq!(output.to_str().unwrap(), "/tmp/output/photo.jpg"); +} + +#[test] +fn processing_job_output_path_with_format_change() { + let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/"); + job.convert = Some(ConvertConfig::SingleFormat(ImageFormat::WebP)); + let source = ImageSource::from_path("/tmp/input/photo.jpg"); + let output = job.output_path_for(&source, Some(ImageFormat::WebP)); + assert_eq!(output.to_str().unwrap(), "/tmp/output/photo.webp"); +}