From e5557d3f0841bac66d59bd081f71d55c692c0269 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Sun, 28 Apr 2024 14:38:39 +0100
Subject: [PATCH] Add numeric file sort option to atlas builder

---
 CHANGELOG.md          |  6 ++++++
 Cargo.lock            |  2 +-
 Cargo.toml            |  2 +-
 src/commands/atlas.rs | 47 +++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c561f9b..5c435b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,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.8.2] - 2024-04-28
+
+### Added
+
+- Added numeric file name sort option to atlas builder
+
 ## [0.8.1] - 2024-04-28
 
 ### Added
diff --git a/Cargo.lock b/Cargo.lock
index 32f24aa..df714a9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -233,7 +233,7 @@ dependencies = [
 
 [[package]]
 name = "crunch-cli"
-version = "0.8.1"
+version = "0.8.2"
 dependencies = [
  "anyhow",
  "clap",
diff --git a/Cargo.toml b/Cargo.toml
index 173db32..2eabda7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "crunch-cli"
-version = "0.8.1"
+version = "0.8.2"
 edition = "2021"
 
 homepage = "https://microhacks.lcr.app/crunch/"
diff --git a/src/commands/atlas.rs b/src/commands/atlas.rs
index 2ef6004..4611367 100644
--- a/src/commands/atlas.rs
+++ b/src/commands/atlas.rs
@@ -4,6 +4,7 @@ use etagere::{AllocId, AtlasAllocator, Rectangle, Size};
 use image::{image_dimensions, GenericImage, Rgba, RgbaImage};
 use rayon::prelude::{IntoParallelIterator, ParallelIterator};
 use serde::{Deserialize, Serialize};
+use std::cmp::Ordering;
 use std::collections::HashMap;
 use std::fmt::Display;
 use std::fs::File;
@@ -16,7 +17,7 @@ fn default_max_size() -> usize {
 /// Given a set of images, create a single atlas image and metadata file containing all of the original
 /// set
 #[derive(Parser, Serialize, Deserialize, Clone, Debug)]
-#[clap(author, version = "0.8.1")]
+#[clap(author, version = "0.8.2")]
 pub struct Atlas {
 	/// A pattern evaluating to one or more image files
 	#[serde(default)]
@@ -36,6 +37,13 @@ pub struct Atlas {
 	/// be padded to the correct size before specifying an area
 	#[clap(short = 'a', long = "area")]
 	pub area: Option<usize>,
+	/// Perform a numeric sort on the names of files being combined.
+	///
+	/// With this flag enabled, the files "104", "5", "2045" would be ordered as "5", "104", "2045"
+	/// Without this flag, those same files would be ordered by OS determination, typically ""104", "2045", "5"
+	#[serde(default)]
+	#[clap(short = 'n')]
+	pub numeric: bool,
 }
 
 #[derive(Copy, Clone, Hash, Eq, PartialEq)]
@@ -108,9 +116,44 @@ impl Atlas {
 		let pattern = glob::glob(self.glob.as_str())?;
 		let mut builder = AtlasBuilder::new((self.max_frame_width, self.max_frame_height));
 
+		let mut pattern = pattern.filter_map(Result::ok).collect::<Vec<PathBuf>>();
+
+		if self.numeric {
+			pattern.sort_by(|a, b| {
+				let a_num: isize = match a
+					.file_stem()
+					.and_then(|s| s.to_str())
+					.and_then(|s| s.parse().ok())
+				{
+					Some(v) => v,
+					_ => return Ordering::Equal,
+				};
+				let b_num: isize = match b
+					.file_stem()
+					.and_then(|s| s.to_str())
+					.and_then(|s| s.parse().ok())
+				{
+					Some(v) => v,
+					_ => return Ordering::Equal,
+				};
+				a_num.cmp(&b_num)
+			});
+		} else {
+			pattern.sort_by(|a, b| {
+				let a_num = match a.file_stem().and_then(|s| s.to_str()) {
+					Some(v) => v,
+					_ => return Ordering::Equal,
+				};
+				let b_num = match b.file_stem().and_then(|s| s.to_str()) {
+					Some(v) => v,
+					_ => return Ordering::Equal,
+				};
+				a_num.cmp(b_num)
+			})
+		}
+
 		let page_content_map: HashMap<usize, Vec<(PathBuf, Rectangle)>> = pattern
 			.into_iter()
-			.filter_map(Result::ok)
 			.flat_map(|path| {
 				if let Some(fixed) = &self.area {
 					Ok((*fixed as u32, *fixed as u32, path))
-- 
GitLab