pub const FOOTER_INDENT: usize = 75;
pub fn bold(s: &str) -> String {
format!("\x1b[1m{}\x1b[0m", s)
}
pub fn italic(s: &str) -> String {
format!("\x1b[3m{}\x1b[0m", s)
}
pub fn pad_right(s: &str, width: usize) -> String {
format!("{:<width$}", s, width = width)
}
pub fn pad_left(s: &str, width: usize) -> String {
format!("{:>width$}", s, width = width)
}
pub fn mins2readable(mins: i64, want_sign: bool, short: bool) -> String {
let abs_m = mins.abs();
let hours = abs_m / 60;
let minutes = abs_m % 60;
let sign = if mins > 0 && want_sign {
"+"
} else if mins < 0 && want_sign {
"-"
} else {
"" };
if short {
format!("{}{:02}h{:02}m", sign, hours, minutes)
} else {
format!("{}{:02}h {:02}m", sign, hours, minutes)
}
}
pub fn describe_position(code: &str) -> (String, &'static str) {
match code.to_uppercase().as_str() {
"O" => ("Office".into(), "\x1b[34m"),
"R" => ("Remote".into(), "\x1b[36m"),
"C" => ("On-site (Client)".into(), "\x1b[33m"),
"H" => ("Holiday".into(), "\x1b[45;97;1m"),
"M" => ("Mixed".into(), "\x1b[35m"),
other => (other.to_string(), "\x1b[0m"),
}
}
pub fn visible_len(s: &str) -> usize {
let bytes = s.as_bytes();
let mut i = 0usize;
let mut count = 0usize;
while i < bytes.len() {
if bytes[i] == 0x1b && i + 1 < bytes.len() && bytes[i + 1] == b'[' {
i += 2; while i < bytes.len() {
let b = bytes[i];
i += 1;
if (0x40..=0x7E).contains(&b) {
break;
}
}
} else {
let ch = s[i..].chars().next().unwrap();
count += 1;
i += ch.len_utf8();
}
}
count
}
pub fn right_pad_prefix(box_width: usize, visible_text: &str) -> String {
let len = visible_len(visible_text);
let pad = box_width.saturating_sub(len);
" ".repeat(pad)
}
pub fn build_import_source(base: &str, format: &str) -> String {
format!("{} (from {})", base, format.to_ascii_lowercase())
}