insa 1.0.0

Insa renders imaxes to text - it's a terminal bitmap graphics library
Documentation
use insa::Insa;
use std::{
    io::{self, Read},
    process::exit,
};

fn main() {
    let mut args = std::env::args();
    let myname = args.next().unwrap_or("insa".to_owned());
    let myversion = env!["CARGO_PKG_VERSION"];
    let mut stretch = None;
    let mut style = None;
    let mut options_done = false;
    let mut images = vec![];

    let usage = move |error| {
        let output = format!("Insa {myversion}\nUsage: {myname} \
                [--style blocks(default)|simple-on-dark|simple-on-bright|plain] [--stretch X,Y] [--help] \
                files... (- = stdin)\
                \n - it converts raster bitmap images to text / ansi sequences");
        if let Some(error) = &error {
            eprintln!("{error}\n\n{output}");
            exit(1);
        } else {
            println!("{output}");
            exit(0);
        }
    };

    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--help" => {
                usage(None);
            }
            "--version" => {
                println!("Insa {myversion}");
                return;
            }
            "--stretch" => {
                stretch = args.next();
                if stretch.is_none() {
                    usage(Some(format!("Option {arg} requires an argument")));
                }
            }
            "--style" => {
                style = args.next();
                if style.is_none() {
                    usage(Some(format!("Option {arg} requires an argument")));
                }
            }
            "--" => options_done = true,
            _ if !options_done && arg.starts_with("--") => {
                usage(Some(format!("Unknown option {arg}")));
            }
            _ => images.push(arg),
        }
    }

    if images.is_empty() {
        usage(Some("Give Insa some images to play with".into()));
    }

    let insa = match style.as_ref().map(|s| s.as_str()).unwrap_or("blocks") {
        "blocks" => Insa::blocks(),
        "simple-on-dark" => Insa::simple_on_dark(),
        "simple-on-bright" => Insa::simple_on_bright(),
        "plain" => Insa::plain(),
        "none" => Insa::default(),
        unknown => return usage(Some(format!("Unknown style {unknown}"))),
    };

    let announce = images.len() > 1;

    for img in images {
        if announce {
            eprintln!("{img}");
        }
        let mut img = match img.as_str() {
            "-" => {
                let mut img = vec![];
                io::stdin()
                    .read_to_end(&mut img)
                    .expect("reading image from stdin");
                image::load_from_memory(&img[..])
            }
            _ => image::open(img),
        }
        .expect("opening the image");

        if let Some(stretch) = &stretch {
            let (w, h) = stretch
                .split_once(",")
                .expect("x,y dimensions for stretch separated by colon");
            let w = w.parse().expect("x dimension for stretch");
            let h = h.parse().expect("y dimension for stretch");
            img = img.resize(w, h, image::imageops::FilterType::Gaussian);
        }

        //img = img.blur(1.0);

        for ((c, r), s) in insa.convert(&img) {
            if c == 0 && r != 0 {
                println!("\x1b[0m");
            }
            print!("{s}");
        }
        println!("\x1b[0m");
    }
}