1use gleam_core::{
2 build::Telemetry,
3 error::{Error, StandardIoAction},
4};
5use hexpm::version::Version;
6use std::{
7 io::{IsTerminal, Write},
8 time::{Duration, Instant},
9};
10use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
11
12#[derive(Debug, Default, Clone)]
13pub struct Reporter;
14
15impl Reporter {
16 pub fn new() -> Self {
17 Self
18 }
19}
20
21impl Telemetry for Reporter {
22 fn compiled_package(&self, duration: Duration) {
23 print_compiled(duration);
24 }
25
26 fn compiling_package(&self, name: &str) {
27 print_compiling(name);
28 }
29
30 fn checked_package(&self, duration: Duration) {
31 print_checked(duration);
32 }
33
34 fn checking_package(&self, name: &str) {
35 print_checking(name);
36 }
37
38 fn downloading_package(&self, name: &str) {
39 print_downloading(name)
40 }
41
42 fn packages_downloaded(&self, start: Instant, count: usize) {
43 print_packages_downloaded(start, count)
44 }
45
46 fn resolving_package_versions(&self) {
47 print_resolving_versions()
48 }
49
50 fn running(&self, name: &str) {
51 print_running(name);
52 }
53
54 fn waiting_for_build_directory_lock(&self) {
55 print_waiting_for_build_directory_lock()
56 }
57}
58
59pub fn ask(question: &str) -> Result<String, Error> {
60 print!("{question}: ");
61 std::io::stdout().flush().expect("ask stdout flush");
62 let mut answer = String::new();
63 let _ = std::io::stdin()
64 .read_line(&mut answer)
65 .map_err(|e| Error::StandardIo {
66 action: StandardIoAction::Read,
67 err: Some(e.kind()),
68 })?;
69 Ok(answer.trim().to_string())
70}
71
72pub fn confirm(question: &str) -> Result<bool, Error> {
73 let answer = ask(&format!("{question} [y/n]"))?;
74 match answer.as_str() {
75 "y" | "yes" | "Y" | "YES" => Ok(true),
76 _ => Ok(false),
77 }
78}
79
80pub fn confirm_with_text(response: &str) -> Result<bool, Error> {
81 let answer = ask(&format!("Type '{response}' to continue"))?;
82 Ok(response == answer)
83}
84
85pub fn ask_password(question: &str) -> Result<String, Error> {
86 let prompt = format!("{question} (will not be printed as you type): ");
87 rpassword::prompt_password(prompt)
88 .map_err(|e| Error::StandardIo {
89 action: StandardIoAction::Read,
90 err: Some(e.kind()),
91 })
92 .map(|s| s.trim().to_string())
93}
94
95pub fn print_publishing(name: &str, version: &Version) {
96 print_colourful_prefix("Publishing", &format!("{name} v{version}"))
97}
98
99pub fn print_published(duration: Duration) {
100 print_colourful_prefix("Published", &format!("in {}", seconds(duration)))
101}
102
103pub fn print_retired(package: &str, version: &str) {
104 print_colourful_prefix("Retired", &format!("{package} {version}"))
105}
106
107pub fn print_unretired(package: &str, version: &str) {
108 print_colourful_prefix("Unretired", &format!("{package} {version}"))
109}
110
111pub fn print_publishing_documentation() {
112 print_colourful_prefix("Publishing", "documentation");
113}
114
115fn print_downloading(text: &str) {
116 print_colourful_prefix("Downloading", text)
117}
118
119fn print_waiting_for_build_directory_lock() {
120 print_colourful_prefix("Waiting", "for build directory lock")
121}
122
123fn print_resolving_versions() {
124 print_colourful_prefix("Resolving", "versions")
125}
126
127fn print_compiling(text: &str) {
128 print_colourful_prefix("Compiling", text)
129}
130
131pub(crate) fn print_exported(text: &str) {
132 print_colourful_prefix("Exported", text)
133}
134
135pub(crate) fn print_checking(text: &str) {
136 print_colourful_prefix("Checking", text)
137}
138
139pub(crate) fn print_compiled(duration: Duration) {
140 print_colourful_prefix("Compiled", &format!("in {}", seconds(duration)))
141}
142
143pub(crate) fn print_checked(duration: Duration) {
144 print_colourful_prefix("Checked", &format!("in {}", seconds(duration)))
145}
146
147pub(crate) fn print_running(text: &str) {
148 print_colourful_prefix("Running", text)
149}
150
151pub(crate) fn print_added(text: &str) {
152 print_colourful_prefix("Added", text)
153}
154
155pub(crate) fn print_removed(text: &str) {
156 print_colourful_prefix("Removed", text)
157}
158
159pub(crate) fn print_generating_documentation() {
160 print_colourful_prefix("Generating", "documentation")
161}
162
163fn print_packages_downloaded(start: Instant, count: usize) {
164 let elapsed = seconds(start.elapsed());
165 let msg = match count {
166 1 => format!("1 package in {elapsed}"),
167 _ => format!("{count} packages in {elapsed}"),
168 };
169 print_colourful_prefix("Downloaded", &msg)
170}
171
172pub fn seconds(duration: Duration) -> String {
173 format!("{:.2}s", duration.as_millis() as f32 / 1000.)
174}
175
176pub fn print_colourful_prefix(prefix: &str, text: &str) {
177 let buffer_writer = stderr_buffer_writer();
178 let mut buffer = buffer_writer.buffer();
179 buffer
180 .set_color(
181 ColorSpec::new()
182 .set_intense(true)
183 .set_fg(Some(Color::Magenta)),
184 )
185 .expect("print_green_prefix");
186 write!(buffer, "{prefix: >11}").expect("print_green_prefix");
187 buffer
188 .set_color(&ColorSpec::new())
189 .expect("print_green_prefix");
190 writeln!(buffer, " {text}").expect("print_green_prefix");
191 buffer_writer.print(&buffer).expect("print_green_prefix");
192}
193
194pub fn stderr_buffer_writer() -> BufferWriter {
195 // Don't add color codes to the output if standard error isn't connected to a terminal
196 BufferWriter::stderr(color_choice())
197}
198
199fn colour_forced() -> bool {
200 if let Ok(force) = std::env::var("FORCE_COLOR") {
201 !force.is_empty()
202 } else {
203 false
204 }
205}
206
207fn color_choice() -> ColorChoice {
208 if colour_forced() {
209 ColorChoice::Always
210 } else if std::io::stderr().is_terminal() {
211 ColorChoice::Auto
212 } else {
213 ColorChoice::Never
214 }
215}