command_stream/zx/
output.rs1use std::fmt;
4use std::path::Path;
5use std::time::Duration;
6
7use regex::Regex;
8use serde::de::DeserializeOwned;
9
10use super::error::{exit_code_info, ERROR_DETAILS_LIMIT};
11use super::error::{format_error_details, format_error_message, format_exit_message};
12
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct ErrorInfo {
16 pub message: String,
18 pub errno: Option<i64>,
20 pub code: Option<String>,
22}
23
24impl ErrorInfo {
25 pub fn new(message: impl Into<String>) -> Self {
27 Self {
28 message: message.into(),
29 errno: None,
30 code: None,
31 }
32 }
33
34 pub fn from_io(err: &std::io::Error) -> Self {
36 let raw = err.raw_os_error();
37 let code = match err.kind() {
38 std::io::ErrorKind::NotFound => Some("ENOENT"),
39 std::io::ErrorKind::PermissionDenied => Some("EACCES"),
40 std::io::ErrorKind::AlreadyExists => Some("EEXIST"),
41 std::io::ErrorKind::BrokenPipe => Some("EPIPE"),
42 std::io::ErrorKind::InvalidInput => Some("EINVAL"),
43 _ => None,
44 };
45 Self {
46 message: err.to_string(),
47 errno: raw.map(|e| -i64::from(e)),
48 code: code.map(str::to_string),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Default, PartialEq)]
60pub struct ProcessOutput {
61 pub stdout: String,
63 pub stderr: String,
65 pub stdall: String,
67 pub exit_code: Option<i32>,
69 pub signal: Option<String>,
71 pub duration: Duration,
73 pub error: Option<ErrorInfo>,
75 pub from: String,
77}
78
79impl ProcessOutput {
80 pub fn new(
82 exit_code: Option<i32>,
83 signal: Option<&str>,
84 stdout: impl Into<String>,
85 stderr: impl Into<String>,
86 stdall: impl Into<String>,
87 ) -> Self {
88 Self {
89 stdout: stdout.into(),
90 stderr: stderr.into(),
91 stdall: stdall.into(),
92 exit_code,
93 signal: signal.map(str::to_string),
94 ..Default::default()
95 }
96 }
97
98 pub fn from_error(error: ErrorInfo, from: impl Into<String>) -> Self {
100 Self {
101 error: Some(error),
102 from: from.into(),
103 ..Default::default()
104 }
105 }
106
107 pub fn with_from(mut self, from: impl Into<String>) -> Self {
109 self.from = from.into();
110 self
111 }
112
113 pub fn with_duration(mut self, duration: Duration) -> Self {
115 self.duration = duration;
116 self
117 }
118
119 pub fn ok(&self) -> bool {
121 self.error.is_none() && self.exit_code == Some(0)
122 }
123
124 pub fn text(&self) -> String {
126 self.stdall.clone()
127 }
128
129 pub fn text_hex(&self) -> String {
131 self.stdall.bytes().map(|b| format!("{b:02x}")).collect()
132 }
133
134 pub fn value_of(&self) -> &str {
136 self.stdall.trim()
137 }
138
139 pub fn buffer(&self) -> Vec<u8> {
141 self.stdall.as_bytes().to_vec()
142 }
143
144 pub fn json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
146 serde_json::from_str(&self.stdall)
147 }
148
149 pub fn lines(&self) -> Vec<String> {
151 static NEWLINE: once_cell::sync::Lazy<Regex> =
152 once_cell::sync::Lazy::new(|| Regex::new(r"\r?\n").expect("valid regex"));
153 finish_lines(NEWLINE.split(&self.stdall).map(str::to_string).collect())
154 }
155
156 pub fn lines_with(&self, delimiter: &str) -> Vec<String> {
158 finish_lines(self.stdall.split(delimiter).map(str::to_string).collect())
159 }
160
161 pub fn exit_code_info(&self) -> Option<&'static str> {
163 self.exit_code.and_then(exit_code_info)
164 }
165
166 pub fn message(&self) -> String {
168 if let Some(err) = &self.error {
169 return format_error_message(&err.message, err.errno, err.code.as_deref(), &self.from);
170 }
171 let details = if self.stderr.trim().is_empty() {
172 format_error_details(&self.lines(), ERROR_DETAILS_LIMIT)
173 } else {
174 String::new()
175 };
176 format_exit_message(
177 self.exit_code,
178 self.signal.as_deref(),
179 &self.stderr,
180 &self.from,
181 &details,
182 )
183 }
184}
185
186fn finish_lines(mut pieces: Vec<String>) -> Vec<String> {
187 if pieces.last().is_some_and(|l| l.is_empty()) {
188 pieces.pop();
189 }
190 pieces
191}
192
193impl fmt::Display for ProcessOutput {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 f.write_str(&self.stdall)
196 }
197}
198
199impl std::error::Error for ProcessOutput {}
200
201impl AsRef<Path> for ProcessOutput {
202 fn as_ref(&self) -> &Path {
204 Path::new(self.stdall.trim())
205 }
206}
207
208impl IntoIterator for &ProcessOutput {
209 type Item = String;
210 type IntoIter = std::vec::IntoIter<String>;
211
212 fn into_iter(self) -> Self::IntoIter {
213 self.lines().into_iter()
214 }
215}