diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d13e278696a439ce66fbfe4af7fbff693489ee..e0ee4e819ed33f21dd20a34e12788e98ed67eb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - Unreleased +### Added +- Support for GlobRef definitions in a `pipeline.toml` file + - Takes a `pattern` glob expression instead of an `input_path` + - Takes a directory path as `output_dir` instead of a file path as `output_file`, and will construct the an `output_file` by appending the file name of a matched file to the output directory + ## [0.3.0] - 2022-07-08 ### Added - Added `flip` command, for flipping an image along one or both axes. diff --git a/Cargo.lock b/Cargo.lock index 469dd3bf6365abfb584f9504433741886f1d0d76..9b2200f444326575a8df0ede0fac5239ef54094f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,13 +183,14 @@ dependencies = [ [[package]] name = "crunch" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "clap", "deltae", "env_logger", "glam", + "glob", "image", "lab", "log", @@ -315,6 +316,12 @@ version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "half" version = "1.8.2" diff --git a/Cargo.toml b/Cargo.toml index 4d76282808d4c901704bfe7b7f6c3e774582a386..a9b05e19cb0a761cc14a08dcbaab759dd42cc72d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crunch" -version = "0.3.0" +version = "0.4.0" edition = "2021" [dependencies] @@ -18,3 +18,4 @@ thiserror = "1.0.30" serde = { version = "1.0.131", features = ["derive"] } toml = "0.5.9" serde_json = "1.0.81" +glob = "0.3.0" diff --git a/rustfmt.toml b/rustfmt.toml index ddac46a818a71283d5b1d79d0d01761aaceb5b84..d62aed71624f22a7ec26ece40fb3e1db30dffa50 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ hard_tabs = true -group_imports = "StdExternalCrate" +#group_imports = "StdExternalCrate" use_field_init_shorthand = true use_try_shorthand = true \ No newline at end of file diff --git a/src/cli_args.rs b/src/cli_args.rs index 159e911552aa02ceebb1f30ba7131c516863fd6f..f178bbbfd11564a5fc6ae8d5772dc7398d5f1cef 100644 --- a/src/cli_args.rs +++ b/src/cli_args.rs @@ -12,7 +12,7 @@ use crate::{load_image, Format}; #[derive(Parser, Debug, Clone)] #[clap(name = "Crunch")] #[clap(author = "Louis Capitanchik <louis@microhacks.co.uk>")] -#[clap(version = "0.3.0")] +#[clap(version = "0.4.0")] #[clap(about, long_about = None)] pub struct Args { /// The path to the spritesheet file diff --git a/src/commands/pipeline.rs b/src/commands/pipeline.rs index b439d3afe263de5c0f65db791755327f43417090..90e860f8fa8cb2eec3804728357e563d8c76cb99 100644 --- a/src/commands/pipeline.rs +++ b/src/commands/pipeline.rs @@ -1,5 +1,6 @@ +use glob::{glob_with, MatchOptions}; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -27,6 +28,11 @@ pub enum PipelineType { output_path: String, reference: String, }, + GlobRef { + pattern: String, + output_dir: String, + reference: String, + }, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -56,15 +62,165 @@ pub fn execute_pipeline<IN: ToString, OUT: ToString>( serde_json::from_slice(&file_contents)? }; + get_targets(&pipeline_data).for_each(|(input_path, output_path, actions)| { + let mut file = match load_image(&input_path, None) { + Ok(image) => image, + Err(e) => { + log::error!("Error loading {}; {:?}", &input_path, e); + return; + } + }; + + let mut count = 1; + for step in actions { + match step { + CrunchCommand::Extrude { + tile_size, + space_y, + space_x, + pad_y, + pad_x, + extrude, + } => { + file = match commands::extrude( + file, tile_size, pad_x, pad_y, space_x, space_y, extrude, + ) { + Ok(f) => f, + Err(e) => { + log::error!( + "Failed to extrude {} at step {}; {}", + input_path, + count, + e + ); + return; + } + }; + } + CrunchCommand::Remap { palette_file } => { + let palette_data = match load_image(&palette_file, None) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to load {} at step {}; {:?}", input_path, count, e); + return; + } + }; + + let image_palette = match commands::palette(&file) { + Ok(ip) => ip, + Err(e) => { + log::error!( + "Failed to extract palette from {} at step {}; {}", + input_path, + count, + e + ); + return; + } + }; + + let target_palette = match commands::palette(&palette_data) { + Ok(tp) => tp, + Err(e) => { + log::error!( + "Failed to extract palette from {} at step {}; {}", + &palette_file, + count, + e + ); + return; + } + }; + + let mappings = commands::calculate_mapping(&image_palette, &target_palette); + file = match commands::remap_image(file, mappings) { + Ok(f) => f, + Err(e) => { + log::error!("Failed to remap {} at step {}; {}", input_path, count, e); + return; + } + }; + } + CrunchCommand::Scale { factor } => { + file = match commands::rescale(&file, factor) { + Ok(f) => f, + Err(e) => { + log::error!("Failed to scale {} at step {}; {}", input_path, count, e); + return; + } + }; + } + CrunchCommand::Rotate { amount } => { + file = match commands::rotate(&file, amount) { + Ok(f) => f, + Err(e) => { + log::error!( + "Failed to rotate {} by {:?} step(s); {}", + input_path, + amount, + e + ); + return; + } + }; + } + CrunchCommand::Flip { direction } => { + file = match commands::flip(&file, direction) { + Ok(f) => f, + Err(e) => { + log::error!( + "Failed to flip {} in the following direction: {:?}; {}", + input_path, + direction, + e + ); + return; + } + }; + } + CrunchCommand::Palette { .. } | CrunchCommand::Pipeline => continue, + } + + count += 1; + } + + let mut outer_target_path = PathBuf::from(&output_path); + outer_target_path.pop(); + + if let Err(e) = std::fs::create_dir(&outer_target_path) { + match e.kind() { + std::io::ErrorKind::AlreadyExists => { /* This is fine */ } + _ => log::error!( + "Failed to create containing directory {}; {}", + outer_target_path.to_string_lossy(), + e + ), + } + } + + match file.save(&output_path) { + Ok(_) => {} + Err(e) => { + log::error!("Failed to save to {}; {}", output_path, e); + } + } + }); + + Ok(()) +} + +fn get_targets( + pipeline_data: &PipelineFile, +) -> impl ParallelIterator<Item = (String, String, Vec<CrunchCommand>)> + '_ { pipeline_data .pipelines .par_iter() - .filter_map(|pipe| match pipe { + .flat_map(|pipe| match pipe { PipelineType::Pipeline { input_path, output_path, actions, - } => Some((input_path, output_path, actions.clone())), + } => vec![(input_path.clone(), output_path.clone(), actions.clone())], PipelineType::Ref { input_path, output_path, @@ -72,166 +228,54 @@ pub fn execute_pipeline<IN: ToString, OUT: ToString>( } => pipeline_data .refs .get(reference.as_str()) - .map(|value| (input_path, output_path, value.actions.clone())), - }) - .for_each(|(input_path, output_path, actions)| { - let mut file = match load_image(input_path, None) { - Ok(image) => image, - Err(e) => { - log::error!("Error loading {}; {:?}", input_path, e); - return; - } - }; - - let mut count = 1; - for step in actions { - match step { - CrunchCommand::Extrude { - tile_size, - space_y, - space_x, - pad_y, - pad_x, - extrude, - } => { - file = match commands::extrude( - file, tile_size, pad_x, pad_y, space_x, space_y, extrude, - ) { - Ok(f) => f, - Err(e) => { - log::error!( - "Failed to extrude {} at step {}; {}", - input_path, - count, - e - ); - return; - } - }; - } - CrunchCommand::Remap { palette_file } => { - let palette_data = match load_image(&palette_file, None) { - Ok(p) => p, - Err(e) => { - log::error!( - "Failed to load {} at step {}; {:?}", - input_path, - count, - e - ); - return; - } - }; - - let image_palette = match commands::palette(&file) { - Ok(ip) => ip, - Err(e) => { - log::error!( - "Failed to extract palette from {} at step {}; {}", - input_path, - count, - e - ); - return; - } - }; - - let target_palette = match commands::palette(&palette_data) { - Ok(tp) => tp, - Err(e) => { - log::error!( - "Failed to extract palette from {} at step {}; {}", - &palette_file, - count, - e - ); - return; - } - }; - - let mappings = commands::calculate_mapping(&image_palette, &target_palette); - file = match commands::remap_image(file, mappings) { - Ok(f) => f, - Err(e) => { - log::error!( - "Failed to remap {} at step {}; {}", - input_path, - count, - e - ); - return; - } - }; - } - CrunchCommand::Scale { factor } => { - file = match commands::rescale(&file, factor) { - Ok(f) => f, - Err(e) => { - log::error!( - "Failed to scale {} at step {}; {}", - input_path, - count, - e - ); - return; - } - }; - } - CrunchCommand::Rotate { amount } => { - file = match commands::rotate(&file, amount) { - Ok(f) => f, - Err(e) => { - log::error!( - "Failed to rotate {} by {:?} step(s); {}", - input_path, - amount, - e - ); - return; - } - }; - } - CrunchCommand::Flip { direction } => { - file = match commands::flip(&file, direction) { - Ok(f) => f, - Err(e) => { - log::error!( - "Failed to flip {} in the following direction: {:?}; {}", - input_path, - direction, - e - ); - return; - } - }; + .iter() + .map(|value| { + ( + input_path.clone(), + output_path.clone(), + (*value).actions.clone(), + ) + }) + .collect(), + PipelineType::GlobRef { + pattern, + output_dir, + reference, + } => pipeline_data + .refs + .get(reference.as_str()) + .iter() + .map(|value| (*value).actions.clone()) + .flat_map(|actions| { + let mut paths = Vec::new(); + for entry in glob_with( + pattern.as_str(), + MatchOptions { + case_sensitive: true, + ..Default::default() + }, + ) + .unwrap() + { + paths.push((actions.clone(), entry)); } - CrunchCommand::Palette { .. } | CrunchCommand::Pipeline => continue, - } + paths + }) + .filter_map(|(actions, inner)| inner.ok().map(|p| (actions, p))) + .filter_map(|(actions, path)| { + if let Some(filename) = path.file_name().and_then(|osstr| osstr.to_str()) { + let output_path = Path::new(output_dir.as_str()); + let output_path = output_path.join(filename); - count += 1; - } - - let mut outer_target_path = PathBuf::from(output_path); - outer_target_path.pop(); - - if let Err(e) = std::fs::create_dir(&outer_target_path) { - match e.kind() { - std::io::ErrorKind::AlreadyExists => { /* This is fine */ } - _ => log::error!( - "Failed to create containing directory {}; {}", - outer_target_path.to_string_lossy(), - e - ), - } - } - - match file.save(output_path) { - Ok(_) => {} - Err(e) => { - log::error!("Failed to save to {}; {}", output_path, e); - } - } - }); - - Ok(()) + Some(( + format!("{}", path.display()), + format!("{}", output_path.display()), + actions, + )) + } else { + None + } + }) + .collect(), + }) }