about summary refs log tree commit diff
path: root/squash/src/cli.rs
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2023-10-15 19:37:27 -0500
committergennyble <gen@nyble.dev>2023-10-15 19:37:27 -0500
commit29677eba58cebd1165eadb3de869f8957824ee79 (patch)
tree9ba1720f225d753896bb78728ed822ca7386c51c /squash/src/cli.rs
parent244c33a07952f0ee22cc3641f35eb5af55f405f1 (diff)
downloadcolorsquash-29677eba58cebd1165eadb3de869f8957824ee79.tar.gz
colorsquash-29677eba58cebd1165eadb3de869f8957824ee79.zip
dd style cli
Diffstat (limited to 'squash/src/cli.rs')
-rw-r--r--squash/src/cli.rs182
1 files changed, 128 insertions, 54 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)
+}