termimage/
options.rs

1//! This module contains the configuration of the application.
2//!
3//! All options are passed individually to each function and are not bundled together.
4//!
5//! # Examples
6//!
7//! ```no_run
8//! # use termimage::Options;
9//! let options = Options::parse();
10//! println!("Image to display: {}", options.image.0);
11//! ```
12
13
14use clap::{Arg, AppSettings};
15use std::path::PathBuf;
16use std::str::FromStr;
17use term_size;
18use std::fs;
19
20
21/// Supported ANSI output formats
22#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
23pub enum AnsiOutputFormat {
24    /// Truecolor ANSI 24-bit colour
25    Truecolor,
26    /// Dumb ANSI 3-bit colour, for black backgrounds
27    SimpleBlack,
28    /// Dumb ANSI 3-bit colour, for white backgrounds
29    SimpleWhite,
30}
31
32
33/// Representation of the application's all configurable values.
34#[derive(Debug, Clone, Hash, PartialEq, Eq)]
35pub struct Options {
36    /// Image file to display.
37    ///
38    /// This tuple contains the plaintext name (user-friendly) and a normalised path (programmer-friendly).
39    pub image: (String, PathBuf),
40    /// Output size. Default: detected from terminal size or no default.
41    pub size: (u32, u32),
42    /// Whether to preserve the image's aspect ratio when resizing. Default: `true`.
43    pub preserve_aspect: bool,
44    /// Whether to output ANSI escapes and in which format. Default: `None` on Windooze when not writing to a file.
45    pub ansi_out: Option<AnsiOutputFormat>,
46}
47
48impl Options {
49    /// Parse `env`-wide command-line arguments into an `Options` instance
50    pub fn parse() -> Options {
51        let szarg_def;
52        let mut szarg = Arg::from_usage("-s --size [size] 'Output image resolution'").validator(Options::size_validator);
53        let have_dimms = if let Some((w, h)) = term_size::dimensions() {
54            szarg_def = format!("{}x{}", w, h - 1);
55            szarg = szarg.default_value(&szarg_def);
56            true
57        } else {
58            szarg = szarg.required(true);
59            false
60        };
61
62        let matches = app_from_crate!("\n")
63            .setting(AppSettings::ColoredHelp)
64            .arg(Arg::from_usage("<IMAGE> 'Image file to display'").validator(Options::image_file_validator))
65            .arg(szarg)
66            .arg(Arg::from_usage("-f --force 'Don't preserve the image's aspect ratio'"))
67            .arg(Arg::from_usage("-a --ansi [ANSI] 'Force output ANSI escapes'").possible_values(&["truecolor", "simple-black", "simple-white"]))
68            .get_matches();
69
70        let image = matches.value_of("IMAGE").unwrap();
71        Options {
72            image: (image.to_string(), fs::canonicalize(image).unwrap()),
73            size: Options::parse_size(matches.value_of("size").unwrap()).unwrap(),
74            preserve_aspect: !matches.is_present("force"),
75            ansi_out: if cfg!(not(target_os = "windows")) || !have_dimms || matches.is_present("ansi") {
76                match matches.value_of("ansi").unwrap_or("truecolor") {
77                    "truecolor" => Some(AnsiOutputFormat::Truecolor),
78                    "simple-black" => Some(AnsiOutputFormat::SimpleBlack),
79                    "simple-white" => Some(AnsiOutputFormat::SimpleWhite),
80                    _ => unreachable!(),
81                }
82            } else {
83                None
84            },
85        }
86    }
87
88    fn parse_size(s: &str) -> Option<(u32, u32)> {
89        let mut parts = s.splitn(2, |c| c == 'x' || c == 'X');
90        Some((u32::from_str(parts.next()?).ok()?, u32::from_str(parts.next()?).ok()?))
91    }
92
93    fn image_file_validator(s: String) -> Result<(), String> {
94        fs::canonicalize(&s).map(|_| ()).map_err(|_| format!("Image file \"{}\" not found", s))
95    }
96
97    fn size_validator(s: String) -> Result<(), String> {
98        match Options::parse_size(&s) {
99            None => Err(format!("\"{}\" is not a valid size (in format \"NNNxMMM\")", s)),
100            Some((0, _)) | Some((_, 0)) => Err(format!("Can't resize image to size 0")),
101            Some(_) => Ok(()),
102        }
103    }
104}