ascim 1.0.0

Convert images to ASCII art (CLI + library).
Documentation
use std::env;

const DEFAULT_MAX_WIDTH: usize = 64;
const DEFAULT_MAX_HEIGHT: usize = 48;
const DEFAULT_CHARACTER_RATIO: f64 = 2.0;
const DEFAULT_EDGE_THRESHOLD: f64 = 4.0;

pub struct Args {
    pub file_path: String,
    pub max_width: usize,
    pub max_height: usize,
    pub character_ratio: f64,
    pub edge_threshold: f64,
}

impl Args {
    pub fn parse() -> Self {
        let args: Vec<String> = env::args().collect();

        if args.len() < 2 {
            Self::print_help(&args[0]);
            return Args {
                file_path: String::new(),
                max_width: DEFAULT_MAX_WIDTH,
                max_height: DEFAULT_MAX_HEIGHT,
                character_ratio: DEFAULT_CHARACTER_RATIO,
                edge_threshold: DEFAULT_EDGE_THRESHOLD,
            };
        }

        if args[1] == "-h" || args[1] == "--help" {
            Self::print_help(&args[0]);
            return Args {
                file_path: String::new(),
                max_width: DEFAULT_MAX_WIDTH,
                max_height: DEFAULT_MAX_HEIGHT,
                character_ratio: DEFAULT_CHARACTER_RATIO,
                edge_threshold: DEFAULT_EDGE_THRESHOLD,
            };
        }

        let mut parsed_args = Args {
            file_path: args[1].clone(),
            max_width: Self::get_terminal_width().unwrap_or(DEFAULT_MAX_WIDTH),
            max_height: Self::get_terminal_height().unwrap_or(DEFAULT_MAX_HEIGHT),
            character_ratio: DEFAULT_CHARACTER_RATIO,
            edge_threshold: DEFAULT_EDGE_THRESHOLD,
        };

        let mut i = 2;
        while i < args.len() {
            match args[i].as_str() {
                "-mw" if i + 1 < args.len() => {
                    if let Ok(width) = args[i + 1].parse() {
                        parsed_args.max_width = width;
                    }
                    i += 2;
                }
                "-mh" if i + 1 < args.len() => {
                    if let Ok(height) = args[i + 1].parse() {
                        parsed_args.max_height = height;
                    }
                    i += 2;
                }
                "-et" if i + 1 < args.len() => {
                    if let Ok(threshold) = args[i + 1].parse() {
                        parsed_args.edge_threshold = threshold;
                    }
                    i += 2;
                }
                "-cr" if i + 1 < args.len() => {
                    if let Ok(ratio) = args[i + 1].parse() {
                        parsed_args.character_ratio = ratio;
                    }
                    i += 2;
                }
                _ => i += 1,
            }
        }

        parsed_args
    }

    fn print_help(exec_name: &str) {
        println!("USAGE:");
        println!("\t{} <path/to/image> [OPTIONS]\n", exec_name);
        println!("ARGUMENTS:");
        println!("\t<path/to/image>\t\tPath to image file\n");
        println!("OPTIONS:");
        println!(
            "\t-mw <width>\t\tMaximum width in characters (default: terminal width OR {})",
            DEFAULT_MAX_WIDTH
        );
        println!(
            "\t-mh <height>\t\tMaximum height in characters (default: terminal height OR {})",
            DEFAULT_MAX_HEIGHT
        );
        println!(
            "\t-et <threshold>\t\tEdge detection threshold, range: 0.0 - 4.0 (default: {:.1}, disabled)",
            DEFAULT_EDGE_THRESHOLD
        );
        println!(
            "\t-cr <ratio>\t\tHeight-to-width ratio for characters (default: {:.1})",
            DEFAULT_CHARACTER_RATIO
        );
    }

    fn get_terminal_width() -> Option<usize> {
        if let Some((w, _)) = term_size::dimensions() {
            Some(w)
        } else {
            None
        }
    }

    fn get_terminal_height() -> Option<usize> {
        if let Some((_, h)) = term_size::dimensions() {
            Some(h)
        } else {
            None
        }
    }
}