use std::path::{Path, PathBuf};
use log::debug;
use crate::config::RuntimeConfig;
#[must_use]
pub fn format_duration(duration: std::time::Duration) -> String {
let secs = duration.as_secs_f64();
if secs < 0.001 {
format!("{:.2} μs", secs * 1_000_000.0)
} else if secs < 1.0 {
format!("{:.2} ms", secs * 1000.0)
} else if secs < 60.0 {
format!("{secs:.2} s")
} else {
let mins = secs / 60.0;
format!("{mins:.2} m")
}
}
#[must_use]
pub fn format_bytes(bytes: usize) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB"];
const THRESHOLD: f64 = 1024.0;
if bytes == 0 {
return "0 B".to_string();
}
let bytes_f64 = bytes as f64;
let exp = (bytes_f64.ln() / THRESHOLD.ln()).floor() as usize;
let exp = exp.min(UNITS.len() - 1);
let exp_i32 = i32::try_from(exp).unwrap_or(i32::MAX);
let value = bytes_f64 / THRESHOLD.powi(exp_i32);
if exp == 0 {
format!("{} {}", bytes, UNITS[exp])
} else {
format!("{:.2} {}", value, UNITS[exp])
}
}
#[must_use]
pub fn sanitize_filename(name: &str) -> String {
name.chars()
.filter_map(|c| match c {
' ' | '\t' | '\n' | '\r' | '\'' | ',' => None,
'{' | '}' | '[' | ']' | '(' | ')' => Some('_'), _ => Some(c), })
.collect()
}
#[must_use]
pub fn redact_path(path: &str) -> String {
let filename = Path::new(path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
format!("***/{filename}")
}
#[must_use]
pub fn should_ignore_path(path: &str, config: &RuntimeConfig) -> bool {
let basename = Path::new(path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
if basename.is_empty() {
return false;
}
if config.flags.ignore_hidden_files && basename.starts_with('.') {
return true;
}
for pat in &config.ignore_patterns {
if pat.is_empty() {
continue;
}
if pat.starts_with('*') {
if basename.ends_with(pat.get(1..).unwrap_or("")) {
return true;
}
} else if pat.ends_with('*') && pat.len() > 1 {
if basename.starts_with(pat.get(..pat.len() - 1).unwrap_or("")) {
return true;
}
} else if basename.eq_ignore_ascii_case(pat) {
return true;
}
}
false
}
#[must_use]
pub fn get_temp_output_path(input_path: &str, config: &RuntimeConfig) -> String {
let path = Path::new(input_path);
let input_name = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(crate::PKG_NAME);
let sanitized_name = sanitize_filename(input_name);
let temp_dir = std::env::temp_dir();
temp_dir
.join(format!(
"{}.{}",
sanitized_name,
config.temp_file_extension()
))
.to_string_lossy()
.to_string()
}
#[must_use]
pub fn determine_output_path(
input_path: &str,
output_dir: Option<&str>,
config: &RuntimeConfig,
) -> String {
if let Some(out_dir) = output_dir {
let path = Path::new(input_path);
let input_name = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(crate::PKG_NAME);
let sanitized_name = sanitize_filename(input_name);
PathBuf::from(out_dir)
.join(format!(
"{}.{}",
sanitized_name,
config.temp_file_extension()
))
.to_string_lossy()
.to_string()
} else {
get_temp_output_path(input_path, config)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaceholderType {
Word,
Prefix,
Suffix,
Position,
Pos,
Col,
Header,
List,
CodeBlock,
Paragraph,
}
impl PlaceholderType {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
PlaceholderType::Word => "WORD",
PlaceholderType::Prefix => "PREFIX",
PlaceholderType::Suffix => "SUFFIX",
PlaceholderType::Position => "POS",
PlaceholderType::Pos => "pos",
PlaceholderType::Col => "col",
PlaceholderType::Header => "HEADER",
PlaceholderType::List => "LIST",
PlaceholderType::CodeBlock => "CODE_BLOCK",
PlaceholderType::Paragraph => "PARAGRAPH",
}
}
}
#[must_use]
pub fn format_placeholder(name: &str, index: usize) -> String {
format!("{name}_{index:02}")
}
#[must_use]
pub fn format_placeholder_typed(placeholder_type: PlaceholderType, index: usize) -> String {
format_placeholder(placeholder_type.as_str(), index)
}
#[must_use]
pub fn format_placeholder_bracketed(name: &str, index: usize) -> String {
format!("[{}]", format_placeholder(name, index))
}
#[must_use]
pub fn format_placeholder_bracketed_typed(
placeholder_type: PlaceholderType,
index: usize,
) -> String {
match placeholder_type {
PlaceholderType::Prefix => "[PREFIX]".to_string(),
PlaceholderType::Suffix => "[SUFFIX]".to_string(),
_ => format_placeholder_bracketed(placeholder_type.as_str(), index),
}
}
pub fn print_progress_handler(message: &str, show_progress: bool) {
debug!("{message}");
if show_progress {
println!("{message}");
}
}
pub(crate) trait ToPathIter {
fn to_path_iter(self) -> Vec<String>;
}
macro_rules! impl_to_path_iter {
(pass: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
self
}
}
};
(wrap: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
vec![self]
}
}
};
(clone: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
vec![self.clone()]
}
}
};
(clone_ref: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
self.clone()
}
}
};
(to_string: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
vec![self.to_string()]
}
}
};
(to_vec: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
self.to_vec()
}
}
};
(map_iter: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
self.iter().map(|s| s.to_string()).collect()
}
}
};
(map_into: $t:ty) => {
impl ToPathIter for $t {
fn to_path_iter(self) -> Vec<String> {
self.into_iter().map(|s| s.to_string()).collect()
}
}
};
}
impl_to_path_iter!(to_string: &str);
impl_to_path_iter!(clone: &String);
impl_to_path_iter!(wrap: String);
impl_to_path_iter!(pass: Vec<String>);
impl_to_path_iter!(clone_ref: &Vec<String>);
impl_to_path_iter!(to_vec: &[String]);
impl_to_path_iter!(map_iter: &[&str]);
impl_to_path_iter!(map_into: Vec<&str>);
impl_to_path_iter!(map_iter: &Vec<&str>);
impl<const N: usize> ToPathIter for [&str; N] {
fn to_path_iter(self) -> Vec<String> {
self.into_iter()
.map(std::string::ToString::to_string)
.collect()
}
}