use chrono::{TimeZone, Utc};
use md5;
use std::io::{self, IsTerminal, Write};
pub fn ensure_int_vector(vec: &[String]) -> Option<Vec<i32>> {
vec.iter()
.map(|s| s.parse::<i32>())
.collect::<Result<Vec<_>, _>>()
.map(|mut v| {
v.sort();
v
})
.ok()
}
pub fn calc_content_hash(content: &str) -> Vec<u8> {
md5::compute(content).0.to_vec()
}
pub fn confirm(prompt: &str) -> bool {
print!("{} (y/N): ", prompt);
io::stdout().flush().unwrap();
let mut user_input = String::new();
io::stdin()
.read_line(&mut user_input)
.expect("Failed to read line");
matches!(user_input.trim().to_lowercase().as_str(), "y" | "yes")
}
pub fn is_stdout_piped() -> bool {
!io::stdout().is_terminal()
}
pub fn is_stderr_piped() -> bool {
!io::stderr().is_terminal()
}
pub fn format_file_path(path: &str, max_length: usize) -> String {
if path.len() <= max_length {
path.to_string()
} else {
if path.starts_with('$') {
if let Some(slash_pos) = path.find('/') {
let (var_part, path_part) = path.split_at(slash_pos);
if var_part.len() + 3 < max_length {
let remaining_length = max_length - var_part.len() - 3; if path_part.len() > remaining_length {
let start = path_part.len() - remaining_length;
format!("{}...{}", var_part, &path_part[start..])
} else {
path.to_string()
}
} else {
format!("...{}", &path[path.len() - (max_length - 3)..])
}
} else {
path.to_string()
}
} else {
format!("...{}", &path[path.len() - (max_length - 3)..])
}
}
}
pub fn format_mtime(mtime: i32) -> String {
let datetime = Utc.timestamp_opt(mtime as i64, 0);
match datetime {
chrono::LocalResult::Single(dt) => dt.format("%Y-%m-%d %H:%M:%S").to_string(),
_ => "Invalid timestamp".to_string(),
}
}
pub fn create_shell_function_name(title: &str) -> String {
let cleaned_name = title
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' {
c.to_ascii_lowercase() } else if c.is_whitespace() || c == '_' {
'_' } else {
'\0'
}
})
.filter(|&c| c != '\0')
.collect::<String>()
.trim_matches('_')
.to_string();
if cleaned_name.is_empty() {
"shell_script".to_string()
} else if cleaned_name.chars().next().unwrap().is_ascii_digit() {
format!("script-{}", cleaned_name)
} else {
cleaned_name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn given_valid_string_numbers_when_ensure_int_vector_then_returns_sorted_integers() {
let input = vec!["3".to_string(), "1".to_string(), "2".to_string()];
let result = ensure_int_vector(&input);
assert_eq!(result, Some(vec![1, 2, 3]));
}
#[test]
fn given_invalid_string_numbers_when_ensure_int_vector_then_returns_none() {
let input = vec!["3".to_string(), "abc".to_string(), "2".to_string()];
let result = ensure_int_vector(&input);
assert!(result.is_none());
}
#[test]
fn given_content_string_when_calc_content_hash_then_returns_sha256_hash() {
let content = "hello world";
let hash = calc_content_hash(content);
let expected = md5::compute(content);
assert_eq!(hash, expected.0.to_vec());
}
#[test]
fn given_file_path_when_format_file_path_then_truncates_long_paths() {
assert_eq!(
format_file_path("/home/user/file.txt", 120),
"/home/user/file.txt"
);
let long_path =
"/home/user/very/long/path/to/some/deeply/nested/directory/structure/with/file.txt";
let formatted = format_file_path(long_path, 30);
assert!(formatted.starts_with("..."));
assert!(formatted.ends_with("file.txt"));
assert_eq!(formatted.len(), 30);
assert_eq!(
format_file_path("$HOME/scripts/test.sh", 120),
"$HOME/scripts/test.sh"
);
let var_path = "$SCRIPTS_HOME/very/long/path/to/some/script.sh";
let formatted = format_file_path(var_path, 30);
assert!(formatted.starts_with("$SCRIPTS_HOME..."));
assert!(formatted.ends_with("script.sh"));
}
#[test]
fn given_unix_timestamp_when_format_mtime_then_returns_formatted_datetime() {
let timestamp = 1704067200; assert_eq!(format_mtime(timestamp), "2024-01-01 00:00:00");
assert_eq!(format_mtime(-1), "1969-12-31 23:59:59");
}
#[test]
fn given_various_titles_when_create_shell_function_name_then_returns_valid_function_name() {
assert_eq!(create_shell_function_name("backup_script"), "backup_script");
assert_eq!(create_shell_function_name("backup-script"), "backup-script");
assert_eq!(create_shell_function_name("Deploy Script"), "deploy_script");
assert_eq!(create_shell_function_name("2fa-setup"), "script-2fa-setup");
assert_eq!(create_shell_function_name("test@#$script!"), "testscript");
assert_eq!(create_shell_function_name("@#$%"), "shell_script");
assert_eq!(create_shell_function_name("__test__"), "test");
}
}