about summary refs log tree commit diff
path: root/squash
diff options
context:
space:
mode:
Diffstat (limited to 'squash')
-rw-r--r--squash/src/cli.rs182
-rw-r--r--squash/src/main.rs13
2 files changed, 134 insertions, 61 deletions
diff --git a/squash/src/cli.rs b/squash/src/cli.rs
index 8a127b7..03e10c9 100644
--- a/squash/src/cli.rs
+++ b/squash/src/cli.rs
@@ -1,13 +1,70 @@
+use std::cmp::Ordering;
+
 use camino::Utf8PathBuf;
 
 pub struct Cli {
 	pub color_count: u8,
+	pub tolerance: Option<f32>,
 	pub input: Utf8PathBuf,
 	pub in_type: InType,
 	pub output: Utf8PathBuf,
 	pub out_type: OutType,
 }
 
+// It's not a builder, but I think the builder/building name is useful
+// here because it's used while not all things are populated.
+#[derive(Debug, Default)]
+struct BuildingCli {
+	pub color_count: Option<u8>,
+	pub tolerance: Option<f32>,
+	pub input: Option<Utf8PathBuf>,
+	pub output: Option<Utf8PathBuf>,
+}
+
+impl BuildingCli {
+	// One minus max
+	const DEFAULT_COLORS: u8 = 255;
+
+	pub fn build_or_die(self, input: &str, output: &str) -> Cli {
+		let input: Utf8PathBuf = input.into();
+		let in_type = match input.extension() {
+			None => {
+				eprintln!("can't determine input filetype!\nSupported input types: PNG, JPG");
+				std::process::exit(1);
+			}
+			Some("png") => InType::Png,
+			Some("jpg") | Some("jpeg") => InType::Jpeg,
+			Some(ext) => {
+				eprintln!("unknown filetype '{ext}'!\nSupported input types: PNG, JPG");
+				std::process::exit(1);
+			}
+		};
+
+		let output: Utf8PathBuf = output.into();
+		let out_type = match output.extension() {
+			None => {
+				eprintln!("can't determine output filetype!");
+				std::process::exit(1);
+			}
+			Some("png") => OutType::Png,
+			Some("gif") => OutType::Gif,
+			Some(ext) => {
+				eprintln!("unknown filetype '{ext}'!\nSupport output types are: GIF, PNG");
+				std::process::exit(1);
+			}
+		};
+
+		Cli {
+			color_count: self.color_count.unwrap_or(Self::DEFAULT_COLORS),
+			tolerance: self.tolerance,
+			input,
+			in_type,
+			output,
+			out_type,
+		}
+	}
+}
+
 pub enum InType {
 	Jpeg,
 	Png,
@@ -18,68 +75,85 @@ pub enum OutType {
 	Gif,
 }
 
-// Get's the CLI arguments or dies trying
-pub fn get() -> Cli {
-	let usage = || -> ! {
-		println!("usage: squash <color count> <input> <output>");
-		std::process::exit(0);
-	};
-	let mut argv = std::env::args().skip(1);
+pub fn build() -> Cli {
+	let mut free = vec![];
+	let mut building = BuildingCli::default();
 
-	let color_count: u8 = if let Some(Ok(count)) = argv.next().map(|r| r.parse::<usize>()) {
-		if count > 256 {
-			eprintln!("max colour count must be 256 or below");
-			std::process::exit(1);
-		} else {
-			(count - 1) as u8
+	for arg in std::env::args().skip(1) {
+		// Handle the special cases we want to obey.
+		// -h/--help are standards and, even though we're playing with a
+		// dd-style syntax, we want to respect these
+		if arg == "-h" || arg == "--help" {
+			print_help()
 		}
-	} else {
-		usage()
-	};
-
-	let input: Utf8PathBuf = if let Some(path) = argv.next() {
-		path.into()
-	} else {
-		usage();
-	};
 
-	let in_type = match input.extension() {
-		None => {
-			eprintln!("can't determine input filetype!\nSupported input types: PNG, JPG");
-			std::process::exit(1);
-		}
-		Some("png") => InType::Png,
-		Some("jpg") | Some("jpeg") => InType::Jpeg,
-		Some(ext) => {
-			eprintln!("unknown filetype '{ext}'!\nSupported input types: PNG, JPG");
-			std::process::exit(1);
+		match arg.split_once('=') {
+			None => free.push(arg),
+			Some(("colors", value)) | Some(("colours", value)) | Some(("clrs", value)) => {
+				match value.parse::<usize>() {
+					Err(_) => {
+						eprintln!("color must be a whole number > 0 and <= 256");
+						std::process::exit(1);
+					}
+					Ok(count) if count == 0 || count > 256 => {
+						eprintln!("color must be a whole number >= 1 and <= 256");
+						std::process::exit(1);
+					}
+					Ok(count) => {
+						//TODO: error if this's been set already?
+						building.color_count = Some((count - 1) as u8);
+					}
+				}
+			}
+			Some(("tolerance", value)) | Some(("tol", value)) => match value.parse::<f32>() {
+				Err(_) => {
+					eprintln!("tolerance must be > 0.0 and <= 100.0");
+					std::process::exit(1);
+				}
+				Ok(tol) if tol <= 0.0 || tol > 100.0 => {
+					eprintln!("tolerance must be > 0.0 and <= 100.0");
+					std::process::exit(1);
+				}
+				Ok(tol) => {
+					//TODO: error if this's been set already
+					building.tolerance = Some(tol);
+				}
+			},
+			Some(("help", _)) => {
+				print_help();
+			}
+			Some((key, _)) => {
+				eprintln!("unrecognised key {key}");
+				std::process::exit(1);
+			}
 		}
-	};
-
-	let output: Utf8PathBuf = if let Some(path) = argv.next() {
-		path.into()
-	} else {
-		usage();
-	};
+	}
 
-	let out_type = match output.extension() {
-		None => {
-			eprintln!("can't determine output filetype!");
+	match free.len().cmp(&2) {
+		Ordering::Less => {
+			eprintln!("didn't get enough arguments! 'help=' for help");
 			std::process::exit(1);
 		}
-		Some("png") => OutType::Png,
-		Some("gif") => OutType::Gif,
-		Some(ext) => {
-			eprintln!("unknown filetype '{ext}'!\nSupport output types are: GIF, PNG");
+		Ordering::Greater => {
+			eprintln!("got too many arguments! 'help=' for help");
 			std::process::exit(1);
 		}
-	};
-
-	Cli {
-		color_count,
-		input,
-		in_type,
-		output,
-		out_type,
+		Ordering::Equal => building.build_or_die(&free[0], &free[1]),
 	}
 }
+
+fn print_help() -> ! {
+	println!("usage: squash [arguments ...] <input> <output>\n");
+	println!("<input>  path to a jpeg or png file");
+	println!("<output> path to write a png or gif file to\n");
+	println!("ARGUMENTS:");
+	println!("    colors=<int> | clrs=<int>");
+	println!("        the number of colours the final image should contain");
+	println!("        a whole number more than 0 and less than, or equal, 256 [Default 256]\n");
+	println!("    tolerance=<float> | tol=<float>");
+	println!("        how different colours should be to be added to the palette");
+	println!("        a number > 0 and <= 100\n");
+	println!("    help= | -h | --help");
+	println!("        print this message and exit");
+	std::process::exit(0)
+}
diff --git a/squash/src/main.rs b/squash/src/main.rs
index d5cd2d8..8b77b47 100644
--- a/squash/src/main.rs
+++ b/squash/src/main.rs
@@ -7,7 +7,7 @@ mod image;
 
 fn main() -> Result<(), anyhow::Error> {
 	//gen: I should use clap or at least getopt, but this is fine.
-	let cli = cli::get();
+	let cli = cli::build();
 
 	let mut image = match cli.in_type {
 		InType::Png => image::get_png(cli.input)?,
@@ -15,15 +15,14 @@ fn main() -> Result<(), anyhow::Error> {
 	};
 
 	let mut squasher = Squasher::new(cli.color_count, &image.data);
+
+	if let Some(tol) = cli.tolerance {
+		squasher.set_tolerance(tol);
+	}
+
 	let size = squasher.map_over(&mut image.data);
 	image.data.resize(size, 0);
 
-	println!(
-		"selected {} colours of max {}",
-		squasher.palette().len(),
-		cli.color_count
-	);
-
 	match cli.out_type {
 		OutType::Png => image::save_png(image, squasher, cli.output),
 		OutType::Gif => image::save_gif(image, squasher, cli.output),