diff options
-rw-r--r-- | squash/src/cli.rs | 182 | ||||
-rw-r--r-- | squash/src/main.rs | 13 |
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), |