1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::{
fmt::Display,
process::{ExitStatus, Output},
};
use thiserror::Error;
pub trait OkExt: Sized {
type Success;
/// Returns a `Success`-type if the [Command] was successful, returns [CommandFailed] otherwise
///
/// For `Result<T, std::io::Error>` the type of `Success` is `T`, for other types it is `Self`
///
/// Use this to early return when a call to another command failed
/// ```no_run
/// # use std::process::Command;
/// # use humane_commands::ok::OkExt as _;
/// let x = Command::new("echo").arg("test").output().cmd_ok().unwrap().stdout;
/// assert_eq!(String::from_utf8_lossy(&x), "test")
/// ```
///
/// The returned Err will contain the full stderr of the [Command] if possible, if you do not want this, call `ok_no_msg` instead
fn cmd_ok(self) -> Result<Self::Success, CommandFailed> {
self.cmd_ok_no_msg()
}
/// Returns a `Success`-type if the [Command] was succesfull, returns [CommandFailed] otherwise
///
/// For `Result<T, std::io::Error>` the type of `Success` is `T`, for other types it is `Self`
/// ```no_run
/// # use std::process::Command;
/// # use humane_commands::ok::OkExt;
/// Command::new("foo").output().cmd_ok_no_msg().unwrap();
/// ```
///
/// The returned Err will not contain any output from the [Command]
fn cmd_ok_no_msg(self) -> Result<Self::Success, CommandFailed>;
}
#[derive(Debug, Error)]
pub enum CommandFailed {
Status {
status: ExitStatus,
msg: Option<String>,
},
IO(std::io::Error),
}
impl Display for CommandFailed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Status { status, msg } => {
f.write_str("Failed with code ")?;
status.fmt(f)?;
if let Some(ref msg) = msg {
f.write_str(" ")?;
f.write_str(msg)?;
}
Ok(())
}
Self::IO(err) => err.fmt(f),
}
}
}
impl OkExt for Output {
type Success = Self;
fn cmd_ok(self) -> Result<Self, CommandFailed> {
if self.status.success() {
Ok(self)
} else {
Err(CommandFailed::Status {
status: self.status,
msg: Some(String::from_utf8_lossy(&self.stderr).to_string()),
})
}
}
fn cmd_ok_no_msg(self) -> Result<Self, CommandFailed> {
if self.status.success() {
Ok(self)
} else {
Err(CommandFailed::Status {
status: self.status,
msg: None,
})
}
}
}
impl OkExt for ExitStatus {
type Success = Self;
fn cmd_ok_no_msg(self) -> Result<Self, CommandFailed> {
if self.success() {
Ok(self)
} else {
Err(CommandFailed::Status {
status: self,
msg: None,
})
}
}
}
impl OkExt for Result<ExitStatus, std::io::Error> {
type Success = ExitStatus;
fn cmd_ok_no_msg(self) -> Result<Self::Success, CommandFailed> {
self.map_err(CommandFailed::IO)?.cmd_ok()
}
}
impl OkExt for Result<Output, std::io::Error> {
type Success = Output;
fn cmd_ok_no_msg(self) -> Result<Self::Success, CommandFailed> {
self.map_err(CommandFailed::IO)?.cmd_ok_no_msg()
}
fn cmd_ok(self) -> Result<Self::Success, CommandFailed> {
self.map_err(CommandFailed::IO)?.cmd_ok()
}
}