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}