1use alloc::string::{String, ToString};
4use std::io::{Write, stderr};
5
6use ts_ansi::style::*;
7
8pub trait ActionResult {
10 fn bind_action(self, action: Action) -> Self;
12
13 fn error_action(self, action: &mut Action) -> Self;
15}
16
17impl<T, E> ActionResult for Result<T, E> {
18 fn bind_action(self, mut action: Action) -> Self {
19 match &self {
20 Ok(_) => action.report_success(),
21 Err(_) => action.report_fail(),
22 }
23 self
24 }
25
26 fn error_action(self, action: &mut Action) -> Self {
27 if self.is_err() {
28 action.report_fail();
29 }
30 self
31 }
32}
33impl<T> ActionResult for Option<T> {
34 fn bind_action(self, mut action: Action) -> Self {
35 match &self {
36 Some(_) => action.report_success(),
37 None => action.report_fail(),
38 }
39 self
40 }
41
42 fn error_action(self, action: &mut Action) -> Self {
43 if self.is_none() {
44 action.report_fail();
45 }
46 self
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[non_exhaustive]
53enum ActionState {
54 InProgress,
56 Success,
58 Fail,
60}
61
62#[derive(Debug, Clone)]
64pub struct Action {
65 state: ActionState,
67 actioning_verb: String,
69 actioned_verb: String,
71 detail: String,
73 should_erase: bool,
75}
76
77impl Action {
78 pub fn new<S1: ToString, S2: ToString, S3: ToString>(
85 actioning_verb: S1,
86 actioned_verb: S2,
87 detail: S3,
88 ) -> Self {
89 let mut progress = Self {
90 state: ActionState::InProgress,
91 actioning_verb: actioning_verb.to_string(),
92 actioned_verb: actioned_verb.to_string(),
93 detail: detail.to_string(),
94 should_erase: false,
95 };
96
97 progress.print();
98 progress
99 }
100
101 pub fn report_fail(&mut self) {
103 self.state = ActionState::Fail;
104 self.print();
105 }
106
107 pub fn report_success(&mut self) {
109 self.state = ActionState::Success;
110 self.print();
111 }
112
113 pub fn print(&mut self) {
117 #![expect(
118 unused_must_use,
119 reason = "displaying output is a non-critical part of the program, so this should not
120 panic, additionally, I don't want to have to think about the errors when calling this"
121 )]
122
123 let mut stderr = stderr().lock();
124
125 if self.should_erase {
126 stderr.write_all(ERASE_LINE_UP.as_bytes());
127 }
128
129 let actioning = &self.actioning_verb;
130 let actioned = &self.actioned_verb;
131 let detail = &self.detail;
132
133 match self.state {
134 ActionState::InProgress => {
135 writeln!(stderr, "{CYAN}{BOLD}{actioning}{RESET} {detail}");
136 }
137 ActionState::Success => {
138 writeln!(stderr, "{GREEN}{BOLD}{actioned}{RESET} {detail}");
139 }
140 ActionState::Fail => {
141 writeln!(
142 stderr,
143 "{RED}{BOLD}{actioning}{RESET} {detail} {RED}{BOLD}failed{RESET}"
144 );
145 }
146 };
147
148 stderr.flush();
149
150 self.should_erase = true;
151 }
152
153 pub fn dont_erase(&mut self) {
155 self.should_erase = false;
156 }
157}