use std::{
fmt,
io::{self, stdin, stdout, Read},
sync::atomic::{AtomicBool, AtomicUsize},
};
use dialoguer::Editor;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use is_terminal::IsTerminal;
use once_cell::sync::Lazy;
use sha2::{Digest, Sha256};
use crate::{quote::print_quote, tr};
#[derive(Debug)]
pub enum EditTempfileError {
EmptyBuffer,
IdenticalContents,
InputOutputError(io::Error),
EditorError(dialoguer::Error),
}
impl fmt::Display for EditTempfileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
EditTempfileError::EmptyBuffer => write!(f, "{}", tr!("Empty buffer after editing")),
EditTempfileError::IdenticalContents => {
write!(f, "{}", tr!("Contents are identical after editing"))
}
EditTempfileError::InputOutputError(err) => {
write!(
f,
"{}",
tr!("Input output error during edit: {}", format!("{err:?}"))
)
}
EditTempfileError::EditorError(err) => {
write!(
f,
"{}",
tr!("Editor error during edit: {}", format!("{err:?}"))
)
}
}
}
}
impl std::error::Error for EditTempfileError {}
pub static STDOUT_IS_TTY: Lazy<bool> = Lazy::new(|| stdout().is_terminal());
pub static STDIN_IS_TTY: Lazy<bool> = Lazy::new(|| stdin().is_terminal());
pub static DISPLAY_PROGRESS: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(true));
pub static NPROC: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(num_cpus::get()));
pub static NPROC_STR: Lazy<String> =
Lazy::new(|| NPROC.load(std::sync::atomic::Ordering::SeqCst).to_string());
pub fn edit_tempfile(
contents: Option<String>,
extension: Option<String>,
) -> Result<String, EditTempfileError> {
if !*STDIN_IS_TTY {
let mut buf = String::new();
io::stdin()
.read_to_string(&mut buf)
.map_err(EditTempfileError::InputOutputError)?;
return Ok(buf);
}
let mut editor = Editor::new();
let editor = editor
.require_save(true)
.trim_newlines(false)
.extension(&extension.unwrap_or(".jja-tmp".to_string()));
let contents = contents.unwrap_or_default();
let buf = if let Some(buf) = editor
.edit(&contents)
.map_err(EditTempfileError::EditorError)?
{
buf
} else {
if *STDOUT_IS_TTY {
print_quote(None, false);
}
return Err(EditTempfileError::EmptyBuffer);
};
let mut hasher = Sha256::new();
hasher.update(contents.as_bytes());
let hash_orig = hasher.finalize();
let mut hasher = Sha256::new();
hasher.update(buf.as_bytes());
let hash_data = hasher.finalize();
if hash_orig == hash_data {
if *STDOUT_IS_TTY {
print_quote(None, false);
}
return Err(EditTempfileError::IdenticalContents);
}
Ok(buf)
}
#[cfg(unix)]
pub fn get_username() -> Result<String, Box<dyn std::error::Error>> {
use nix::unistd::{getuid, User};
let user = User::from_uid(getuid())?;
let username = match user {
Some(u) => {
let gecos_str = u.gecos.to_string_lossy().into_owned();
let name = gecos_str.split(',').next().unwrap_or_default();
if name.is_empty() {
u.name
} else {
name.to_owned()
}
}
None => return Err(tr!("Failed to get user information").into()),
};
Ok(username)
}
#[cfg(windows)]
pub fn get_username() -> Result<String, Box<dyn std::error::Error>> {
use username::get_user_name;
get_user_name().map_err(|e| e.into())
}
pub fn get_progress_bar(size: u64) -> ProgressBar {
if !DISPLAY_PROGRESS.load(std::sync::atomic::Ordering::SeqCst) {
return ProgressBar::hidden();
}
let st = ProgressStyle::default_bar()
.template(concat!(
"{msg:<15.bold.magenta}",
" [{pos:.bold.yellow}/{len:.bold.green}]",
" {per_sec:.bold.green}",
" [{wide_bar:.cyan/blue}]",
" [{elapsed_precise:.yellow}] ETA {eta_precise}"
))
.expect(&tr!("Error in progress bar template, please report a bug!"))
.progress_chars("=>-");
let pb = ProgressBar::new(size);
pb.set_style(st);
pb.set_draw_target(ProgressDrawTarget::stderr_with_hz(1));
pb
}
pub fn get_progress_spinner() -> ProgressBar {
if !DISPLAY_PROGRESS.load(std::sync::atomic::Ordering::SeqCst) {
return ProgressBar::hidden();
}
let st = ProgressStyle::default_spinner()
.template(concat!(
"{msg:<15.bold.magenta}",
" [{pos:.bold.yellow}/?]",
" {per_sec:.bold.green}",
" [{elapsed_precise:.yellow}]"
))
.expect(&tr!(
"Error in progress spinner template, please report a bug!"
))
.tick_chars("-\\|/x");
let pb = ProgressBar::new_spinner();
pb.set_style(st);
pb.set_draw_target(ProgressDrawTarget::stderr_with_hz(1));
pb
}