Skip to main content

Switch to using the Derive API for Clap

ID
2cb43e3
date
2024-06-17 21:26:49+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
d0b8a83
message
Switch to using the Derive API for Clap

This is the recommended approach in the Clap docs, and I'm not doing
anything complicated enough to require the Builder API.  Let's simplify!
changed files
6 files, 60 additions, 76 deletions

Changed files

Cargo.lock (17574) → Cargo.lock (18035)

diff --git a/Cargo.lock b/Cargo.lock
index e77faf7..5cfb1e6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -150,6 +150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
 dependencies = [
  "clap_builder",
+ "clap_derive",
 ]
 
 [[package]]
@@ -165,6 +166,18 @@ dependencies = [
 ]
 
 [[package]]
+name = "clap_derive"
+version = "4.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "clap_lex"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -253,6 +266,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
 name = "image"
 version = "0.25.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"

Cargo.toml (441) → Cargo.toml (478)

diff --git a/Cargo.toml b/Cargo.toml
index 334e5bc..7d0a0ef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2018"
 
 [dependencies]
 assert_cmd = "2.0.14"
-clap = "4.5.7"
+clap = { version = "4.5.7", features = ["derive"] }
 regex = "1.10.5"
 
 [dependencies.kmeans_colors]

src/cli.rs (1384) → src/cli.rs (0)

diff --git a/src/cli.rs b/src/cli.rs
deleted file mode 100644
index d1c0b87..0000000
--- a/src/cli.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use clap::{Arg, ArgAction, Command};
-use palette::Srgb;
-
-const VERSION: &str = env!("CARGO_PKG_VERSION");
-
-pub fn app() -> clap::Command {
-    Command::new("dominant_colours")
-        .version(VERSION)
-        .author("Alex Chan <alex@alexwlchan.net>")
-        .about("Find the dominant colours in an image")
-        .arg(
-            Arg::new("PATH")
-                .help("path to the image to inspect")
-                .required(true)
-                .index(1),
-        )
-        .arg(
-            Arg::new("MAX_COLOURS")
-                .long("max-colours")
-                .help("how many colours to find")
-                .value_parser(value_parser!(usize))
-                .default_value("5"),
-        )
-        .arg(
-            Arg::new("BACKGROUND_HEX")
-                .long("best-against-bg")
-                .help("find a single colour that will look best against this background")
-                .value_parser(value_parser!(Srgb<u8>)),
-        )
-        .arg(
-            Arg::new("no-palette")
-                .long("no-palette")
-                .help("Just print the hex values, not colour previews")
-                .action(ArgAction::SetTrue),
-        )
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::cli::app;
-
-    // See https://github.com/clap-rs/clap/blob/master/CHANGELOG.md#300---2021-12-31
-    #[test]
-    fn verify_app() {
-        app().debug_assert();
-    }
-}

src/get_image_colors.rs (5212) → src/get_image_colors.rs (5394)

diff --git a/src/get_image_colors.rs b/src/get_image_colors.rs
index c69ac00..19ec1b5 100644
--- a/src/get_image_colors.rs
+++ b/src/get_image_colors.rs
@@ -6,8 +6,10 @@
 //
 // It returns a Vec<Lab>, which can be passed to the k-means process.
 
+use std::ffi::OsStr;
 use std::fs::File;
 use std::io::BufReader;
+use std::path::PathBuf;
 
 use image::codecs::gif::GifDecoder;
 use image::imageops::FilterType;
@@ -15,11 +17,11 @@ use image::{AnimationDecoder, DynamicImage, Frame};
 use palette::cast::from_component_slice;
 use palette::{IntoColor, Lab, Srgba};
 
-pub fn get_image_colors(path: &str) -> Vec<Lab> {
-    let image_bytes = if path.to_lowercase().ends_with(".gif") {
-        get_bytes_for_gif(&path)
-    } else {
-        get_bytes_for_non_gif(&path)
+pub fn get_image_colors(path: &PathBuf) -> Vec<Lab> {
+    let image_bytes = match path.extension().and_then(OsStr::to_str) {
+        Some("gif") => get_bytes_for_gif(&path),
+        Some("GIF") => get_bytes_for_gif(&path),
+        _ => get_bytes_for_non_gif(&path),
     };
 
     let lab: Vec<Lab> = from_component_slice::<Srgba<u8>>(&image_bytes)
@@ -30,7 +32,7 @@ pub fn get_image_colors(path: &str) -> Vec<Lab> {
     lab
 }
 
-fn get_bytes_for_non_gif(path: &str) -> Vec<u8> {
+fn get_bytes_for_non_gif(path: &PathBuf) -> Vec<u8> {
     let img = match image::open(&path) {
         Ok(im) => im,
         Err(e) => {
@@ -59,7 +61,7 @@ fn get_bytes_for_non_gif(path: &str) -> Vec<u8> {
     resized_img.into_rgba8().into_raw()
 }
 
-fn get_bytes_for_gif(path: &str) -> Vec<u8> {
+fn get_bytes_for_gif(path: &PathBuf) -> Vec<u8> {
     let f = match File::open(path) {
         Ok(im) => im,
         Err(e) => {
@@ -132,6 +134,8 @@ fn get_bytes_for_gif(path: &str) -> Vec<u8> {
 
 #[cfg(test)]
 mod test {
+    use std::path::PathBuf;
+
     use crate::get_image_colors::get_image_colors;
 
     // This image comes from https://stacks.wellcomecollection.org/peering-through-mri-scans-of-fruit-and-veg-part-1-a2e8b07bde6f
@@ -141,11 +145,11 @@ mod test {
     // processed correctly.
     #[test]
     fn it_gets_colors_for_mri_fruit() {
-        get_image_colors("./src/tests/garlic.gif");
+        get_image_colors(&PathBuf::from("./src/tests/garlic.gif"));
     }
 
     #[test]
     fn get_colors_for_webp() {
-        get_image_colors("./src/tests/purple.webp");
+        get_image_colors(&PathBuf::from("./src/tests/purple.webp"));
     }
 }

src/main.rs (8365) → src/main.rs (8614)

diff --git a/src/main.rs b/src/main.rs
index 3031a60..18deca9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,44 +1,52 @@
 #![deny(warnings)]
 
-#[macro_use]
-extern crate clap;
+use std::path::PathBuf;
 
+use clap::Parser;
 use palette::{FromColor, Lab, Srgb};
 
-mod cli;
 mod find_dominant_colors;
 mod get_image_colors;
 mod printing;
 
-fn main() {
-    let matches = cli::app().get_matches();
+#[derive(Parser, Debug)]
+#[command(version, about = "Find the dominant colours in an image", long_about=None)]
+struct Cli {
+    /// Path to the image to inspect
+    path: PathBuf,
+
+    /// How many colours to find
+    #[arg(long = "max-colours", default_value_t = 5)]
+    max_colours: usize,
 
-    let path = matches
-        .get_one::<String>("PATH")
-        .expect("`path` is required");
+    /// Find a single colour that will look best against this background
+    #[arg(long = "best-against-bg")]
+    background: Option<Srgb<u8>>,
 
-    let max_colours: usize = *matches
-        .get_one::<usize>("MAX_COLOURS")
-        .expect("`max-colours` is required");
+    /// Just print the hex values, not colour previews
+    #[arg(long = "no-palette")]
+    no_palette: bool,
+}
 
-    let lab: Vec<Lab> = get_image_colors::get_image_colors(&path);
+fn main() {
+    let cli = Cli::parse();
 
-    let dominant_colors = find_dominant_colors::find_dominant_colors(&lab, max_colours);
+    let lab: Vec<Lab> = get_image_colors::get_image_colors(&cli.path);
 
-    let background = matches.get_one::<Srgb<u8>>("BACKGROUND_HEX");
+    let dominant_colors = find_dominant_colors::find_dominant_colors(&lab, cli.max_colours);
 
-    let selected_colors = match background {
-        Some(bg) => find_dominant_colors::choose_best_color_for_bg(dominant_colors.clone(), bg),
+    let selected_colors = match cli.background {
+        Some(bg) => find_dominant_colors::choose_best_color_for_bg(dominant_colors.clone(), &bg),
         None => dominant_colors,
     };
-
+    //
     let rgb_colors = selected_colors
         .iter()
         .map(|c| Srgb::from_color(*c).into_format())
         .collect::<Vec<Srgb<u8>>>();
 
     for c in rgb_colors {
-        printing::print_color(c, &background, matches.get_flag("no-palette"));
+        printing::print_color(c, &cli.background, cli.no_palette);
     }
 }
 

src/printing.rs (881) → src/printing.rs (880)

diff --git a/src/printing.rs b/src/printing.rs
index 8cc5441..8704966 100644
--- a/src/printing.rs
+++ b/src/printing.rs
@@ -5,7 +5,7 @@ use palette::Srgb;
 //
 // See https://alexwlchan.net/2021/04/coloured-squares/
 // See: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797?permalink_comment_id=3857871
-pub fn print_color(c: Srgb<u8>, background: &Option<&Srgb<u8>>, no_palette: bool) {
+pub fn print_color(c: Srgb<u8>, background: &Option<Srgb<u8>>, no_palette: bool) {
     let display_value = format!("#{:02x}{:02x}{:02x}", c.red, c.green, c.blue);
 
     if no_palette {