common/
run.rs

1use crate::color::choose_color;
2use crate::output::write_error;
3use clap::Clap;
4use std::io::{Stdin, StdinLock};
5use std::{io, process};
6use termcolor::{ColorChoice, StandardStream, StandardStreamLock};
7
8pub const EXIT_CODE_OK: i32 = 0;
9pub const EXIT_CODE_IO_ERROR: i32 = 1;
10pub const EXIT_CODE_CLI_ERROR: i32 = 2;
11
12pub type Result = io::Result<i32>;
13
14pub trait Options: Clap {
15    fn color(&self) -> Option<ColorChoice>;
16}
17
18pub struct Io {
19    stdin: Stdin,
20    stdout: StandardStream,
21    stderr: StandardStream,
22}
23
24impl Io {
25    pub fn new(color: ColorChoice) -> Self {
26        Self {
27            stdin: io::stdin(),
28            stdout: StandardStream::stdout(color),
29            stderr: StandardStream::stderr(color),
30        }
31    }
32
33    pub fn stdin(&self) -> StdinLock {
34        self.stdin.lock()
35    }
36
37    pub fn stdout(&self) -> StandardStreamLock {
38        self.stdout.lock()
39    }
40
41    pub fn stderr(&self) -> StandardStreamLock {
42        self.stderr.lock()
43    }
44}
45
46pub fn exec_run<O, R>(run: R)
47where
48    O: Options,
49    R: FnOnce(&O, &Io) -> Result,
50{
51    let options = O::parse();
52    let color = choose_color(options.color());
53    let io = Io::new(color);
54
55    let exit_code = match run(&options, &io) {
56        Ok(exit_code) => exit_code,
57        Err(io_error) => {
58            write_error(&mut io.stderr(), &io_error).expect("Failed to write to stderr!");
59            EXIT_CODE_IO_ERROR
60        }
61    };
62
63    process::exit(exit_code);
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use claim::*;
70    use std::io::{Read, Write};
71
72    #[test]
73    fn io() {
74        let io = Io::new(ColorChoice::Never);
75        assert_ok!(io.stdin().read_exact(&mut []));
76        assert_ok!(io.stdout().flush());
77        assert_ok!(io.stderr().flush());
78    }
79}