1use std::{
10 fmt,
11 io::{self, stdin, stdout, Read},
12 sync::atomic::{AtomicBool, AtomicUsize},
13};
14
15use dialoguer::Editor;
16use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
17use is_terminal::IsTerminal;
18use once_cell::sync::Lazy;
19use sha2::{Digest, Sha256};
20
21use crate::{quote::print_quote, tr};
22
23#[derive(Debug)]
25pub enum EditTempfileError {
26 EmptyBuffer,
28 IdenticalContents,
31 InputOutputError(io::Error),
33 EditorError(dialoguer::Error),
35}
36
37impl fmt::Display for EditTempfileError {
38 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39 match self {
40 EditTempfileError::EmptyBuffer => write!(f, "{}", tr!("Empty buffer after editing")),
41 EditTempfileError::IdenticalContents => {
42 write!(f, "{}", tr!("Contents are identical after editing"))
43 }
44 EditTempfileError::InputOutputError(err) => {
45 write!(
46 f,
47 "{}",
48 tr!("Input output error during edit: {}", format!("{err:?}"))
49 )
50 }
51 EditTempfileError::EditorError(err) => {
52 write!(
53 f,
54 "{}",
55 tr!("Editor error during edit: {}", format!("{err:?}"))
56 )
57 }
58 }
59 }
60}
61
62impl std::error::Error for EditTempfileError {}
63
64pub static STDOUT_IS_TTY: Lazy<bool> = Lazy::new(|| stdout().is_terminal());
69
70pub static STDIN_IS_TTY: Lazy<bool> = Lazy::new(|| stdin().is_terminal());
74
75pub static DISPLAY_PROGRESS: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(true));
77
78pub static NPROC: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(num_cpus::get()));
80
81pub static NPROC_STR: Lazy<String> =
83 Lazy::new(|| NPROC.load(std::sync::atomic::Ordering::SeqCst).to_string());
84
85pub fn edit_tempfile(
106 contents: Option<String>,
107 extension: Option<String>,
108) -> Result<String, EditTempfileError> {
109 if !*STDIN_IS_TTY {
110 let mut buf = String::new();
111 io::stdin()
112 .read_to_string(&mut buf)
113 .map_err(EditTempfileError::InputOutputError)?;
114 return Ok(buf);
115 }
116
117 let mut editor = Editor::new();
118 let editor = editor
119 .require_save(true)
120 .trim_newlines(false)
121 .extension(&extension.unwrap_or(".jja-tmp".to_string()));
122 let contents = contents.unwrap_or_default();
123
124 let buf = if let Some(buf) = editor
125 .edit(&contents)
126 .map_err(EditTempfileError::EditorError)?
127 {
128 buf
129 } else {
130 if *STDOUT_IS_TTY {
131 print_quote(None, false);
132 }
133 return Err(EditTempfileError::EmptyBuffer);
134 };
135
136 let mut hasher = Sha256::new();
137 hasher.update(contents.as_bytes());
138 let hash_orig = hasher.finalize();
139
140 let mut hasher = Sha256::new();
141 hasher.update(buf.as_bytes());
142 let hash_data = hasher.finalize();
143
144 if hash_orig == hash_data {
145 if *STDOUT_IS_TTY {
146 print_quote(None, false);
147 }
148 return Err(EditTempfileError::IdenticalContents);
149 }
150
151 Ok(buf)
152}
153
154#[cfg(unix)]
155pub fn get_username() -> Result<String, Box<dyn std::error::Error>> {
160 use nix::unistd::{getuid, User};
161
162 let user = User::from_uid(getuid())?;
163 let username = match user {
164 Some(u) => {
165 let gecos_str = u.gecos.to_string_lossy().into_owned();
166 let name = gecos_str.split(',').next().unwrap_or_default();
167 if name.is_empty() {
168 u.name
169 } else {
170 name.to_owned()
171 }
172 }
173 None => return Err(tr!("Failed to get user information").into()),
174 };
175
176 Ok(username)
177}
178
179#[cfg(windows)]
180pub fn get_username() -> Result<String, Box<dyn std::error::Error>> {
185 use username::get_user_name;
186 get_user_name().map_err(|e| e.into())
187}
188
189pub fn get_progress_bar(size: u64) -> ProgressBar {
191 if !DISPLAY_PROGRESS.load(std::sync::atomic::Ordering::SeqCst) {
192 return ProgressBar::hidden();
193 }
194
195 let st = ProgressStyle::default_bar()
196 .template(concat!(
197 "{msg:<15.bold.magenta}",
198 " [{pos:.bold.yellow}/{len:.bold.green}]",
199 " {per_sec:.bold.green}",
200 " [{wide_bar:.cyan/blue}]",
201 " [{elapsed_precise:.yellow}] ETA {eta_precise}"
202 ))
203 .expect(&tr!("Error in progress bar template, please report a bug!"))
204 .progress_chars("=>-");
205
206 let pb = ProgressBar::new(size);
207 pb.set_style(st);
208 pb.set_draw_target(ProgressDrawTarget::stderr_with_hz(1));
209
210 pb
211}
212
213pub fn get_progress_spinner() -> ProgressBar {
215 if !DISPLAY_PROGRESS.load(std::sync::atomic::Ordering::SeqCst) {
216 return ProgressBar::hidden();
217 }
218
219 let st = ProgressStyle::default_spinner()
220 .template(concat!(
221 "{msg:<15.bold.magenta}",
222 " [{pos:.bold.yellow}/?]",
223 " {per_sec:.bold.green}",
224 " [{elapsed_precise:.yellow}]"
225 ))
226 .expect(&tr!(
227 "Error in progress spinner template, please report a bug!"
228 ))
229 .tick_chars("-\\|/x");
230
231 let pb = ProgressBar::new_spinner();
232 pb.set_style(st);
233 pb.set_draw_target(ProgressDrawTarget::stderr_with_hz(1));
234
235 pb
236}