clawless_core/event/process/outcome.rs
1//! The way in which the run of an external program ended
2
3use std::fmt::{self, Display};
4use std::process::ExitStatus;
5
6/// The way in which the run of an external program ended
7///
8/// Every run that starts also ends, and a consumer that shows a running program
9/// needs to know how. The variants separate the three ends that a consumer
10/// treats differently: the program ran to its end, cancellation stopped it, or
11/// Clawless could not collect a result for it.
12///
13/// A program that ended without success is [`Exited`], not a failure. The check
14/// mode of a formatter ends with a non-zero code when it finds a file to
15/// format, and that is an answer and not a fault.
16///
17/// # Examples
18///
19/// ```
20/// use clawless_core::event::process::Outcome;
21///
22/// assert_eq!(Outcome::Cancelled.to_string(), "was cancelled before it ended");
23/// ```
24///
25/// [`Exited`]: Outcome::Exited
26// r[impl process.event.outcome]
27#[derive(Clone, Eq, PartialEq, Debug)]
28pub enum Outcome {
29 /// Cancellation stopped the program before it ended
30 ///
31 /// Clawless killed the program, so it has no exit status. The output that
32 /// reached the consumer before this point is everything the program wrote.
33 // r[impl process.event.outcome.cancelled]
34 Cancelled,
35
36 /// The program ran to its end
37 ///
38 /// The status says how it ended. A program that a signal stopped carries no
39 /// exit code, which [`ExitStatus::code`] reports as `None`.
40 // r[impl process.event.outcome.exited]
41 Exited(ExitStatus),
42
43 /// The run started and produced no result
44 ///
45 /// A stream of the program could not be read, the end of the program could
46 /// not be waited for, or the events of the run could no longer be reported.
47 /// The program itself may well have done its work.
48 // r[impl process.event.outcome.incomplete]
49 Incomplete,
50}
51
52/// States how the run ended, as the end of a sentence about the command
53///
54/// The text follows the command in [`ProcessEvent`], which is why it starts
55/// with a verb and not with a subject.
56///
57/// [`ProcessEvent`]: super::ProcessEvent
58impl Display for Outcome {
59 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::Cancelled => formatter.write_str("was cancelled before it ended"),
62 Self::Exited(status) => match status.code() {
63 Some(code) => write!(formatter, "exited with code {code}"),
64 None => formatter.write_str("ended without an exit code"),
65 },
66 Self::Incomplete => formatter.write_str("ended without a result"),
67 }
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 // An assertion in a test panics by design. A `# Panics` section on every test
74 // would repeat that and give the reader no information.
75 #![allow(clippy::missing_panics_doc)]
76
77 use super::*;
78
79 #[test]
80 fn display_with_cancelled_names_the_cancellation() {
81 let outcome = Outcome::Cancelled;
82
83 let text = outcome.to_string();
84
85 assert_eq!(text, "was cancelled before it ended");
86 }
87
88 // r[verify process.event.outcome.incomplete]
89 #[test]
90 fn display_with_incomplete_names_the_missing_result() {
91 let outcome = Outcome::Incomplete;
92
93 let text = outcome.to_string();
94
95 assert_eq!(text, "ended without a result");
96 }
97
98 #[test]
99 fn trait_send() {
100 fn assert_send<T: Send>() {}
101 assert_send::<Outcome>();
102 }
103
104 #[test]
105 fn trait_sync() {
106 fn assert_sync<T: Sync>() {}
107 assert_sync::<Outcome>();
108 }
109
110 #[test]
111 fn trait_unpin() {
112 fn assert_unpin<T: Unpin>() {}
113 assert_unpin::<Outcome>();
114 }
115}