use std::{
borrow::Cow,
env,
fs::{File, OpenOptions},
io::{BufReader, BufWriter, stdin, stdout},
path::{Path, PathBuf},
sync::atomic::{AtomicBool, Ordering},
};
use std::sync::LazyLock;
use blake3::Hasher;
use dashmap::DashSet;
use path_dedot::ParseDot;
use rand::RngExt;
static APP_SALT: LazyLock<String> = LazyLock::new(generate_salt);
static REDACTION_ENABLED: AtomicBool = AtomicBool::new(false);
const MIN_TOKIO_BLOCKING_THREADS: usize = 32;
const TOKIO_BLOCKING_THREADS_PER_JOB: usize = 8;
const MAX_TOKIO_BLOCKING_THREADS: usize = 256;
pub fn tokio_blocking_threads_limit(num_jobs: usize) -> usize {
num_jobs
.saturating_mul(TOKIO_BLOCKING_THREADS_PER_JOB)
.clamp(MIN_TOKIO_BLOCKING_THREADS, MAX_TOKIO_BLOCKING_THREADS)
}
pub fn intern(s: &str) -> &'static str {
static INTERN: LazyLock<DashSet<&'static str>> = LazyLock::new(|| DashSet::with_capacity(512));
if let Some(existing) = INTERN.get(s) {
return *existing;
}
let static_str: &'static str = Box::leak(s.to_owned().into_boxed_str());
INTERN.insert(static_str);
static_str
}
pub fn is_safe_path(path: &Path) -> std::io::Result<bool> {
Ok(path
.parse_dot()
.map(|p| !p.components().any(|c| matches!(c, std::path::Component::ParentDir)))
.unwrap_or(false))
}
pub fn expand_tilde(path: &Path) -> PathBuf {
expand_tilde_with_home(path, current_user_home().as_deref())
}
fn current_user_home() -> Option<String> {
if cfg!(windows) {
if let Ok(profile) = env::var("USERPROFILE")
&& !profile.is_empty()
{
return Some(profile);
}
if let (Ok(drive), Ok(home_path)) = (env::var("HOMEDRIVE"), env::var("HOMEPATH"))
&& !drive.is_empty()
&& !home_path.is_empty()
{
return Some(format!("{drive}{home_path}"));
}
return None;
}
env::var("HOME").ok().filter(|home| !home.is_empty())
}
fn expand_tilde_with_home(path: &Path, home: Option<&str>) -> PathBuf {
let Some(text) = path.to_str() else {
return path.to_path_buf();
};
let windows_backslash_form = cfg!(windows) && text.starts_with("~\\");
if text != "~" && !text.starts_with("~/") && !windows_backslash_form {
return path.to_path_buf();
}
let Some(home) = home.filter(|home| !home.is_empty()) else {
return path.to_path_buf();
};
if text == "~" {
return PathBuf::from(home);
}
PathBuf::from(format!("{home}{}", &text[1..]))
}
pub fn redact_value(value: &str) -> String {
let mut hasher = Hasher::new();
hasher.update(APP_SALT.as_bytes());
hasher.update(value.as_bytes());
let hash = hasher.finalize();
format!("[REDACTED:{}]", hash_to_short_id(&hash))
}
pub fn set_redaction_enabled(enabled: bool) {
REDACTION_ENABLED.store(enabled, Ordering::Relaxed);
}
pub fn redaction_enabled() -> bool {
REDACTION_ENABLED.load(Ordering::Relaxed)
}
pub fn display_value(value: &str) -> Cow<'_, str> {
if redaction_enabled() { Cow::Owned(redact_value(value)) } else { Cow::Borrowed(value) }
}
fn generate_salt() -> String {
let bytes: [u8; 16] = rand::rng().random();
hex::encode(bytes)
}
fn hash_to_short_id(hash: &blake3::Hash) -> String {
hash.to_hex().chars().take(8).collect()
}
pub enum Counted<'a> {
Regular { singular: &'a str, count: usize },
Explicit { singular: &'a str, count: usize, plural: &'a str },
}
impl<'a> Counted<'a> {
pub fn new(count: usize, singular: &'a str, plural: &'a str) -> Self {
Counted::Explicit { singular, plural, count }
}
pub fn regular(count: usize, singular: &'a str) -> Self {
Counted::Regular { singular, count }
}
}
impl<'a> std::fmt::Display for Counted<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Counted::Explicit { singular, plural, count } => {
write!(f, "{} {}", count, if *count == 1 { singular } else { plural })
}
Counted::Regular { singular, count } => {
write!(f, "{} {}{}", count, singular, if *count == 1 { "" } else { "s" })
}
}
}
}
pub fn get_writer_for_file_or_stdout<P: AsRef<Path>>(
path: Option<P>,
) -> std::io::Result<Box<dyn std::io::Write>> {
match path {
None => Ok(Box::new(BufWriter::new(stdout()))),
Some(p) => Ok(Box::new(BufWriter::new(create_no_follow(p.as_ref())?))),
}
}
pub fn create_no_follow(path: &Path) -> std::io::Result<File> {
let mut opts = OpenOptions::new();
opts.write(true).create(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.custom_flags(libc::O_NOFOLLOW);
}
#[cfg(not(unix))]
if std::fs::symlink_metadata(path).is_ok_and(|meta| meta.file_type().is_symlink()) {
return Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("refusing to write output through symlink: {}", path.display()),
));
}
match opts.open(path) {
Ok(file) => Ok(file),
#[cfg(unix)]
Err(e) if e.raw_os_error() == Some(libc::ELOOP) => Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("refusing to write output through symlink: {}", path.display()),
)),
Err(e) => Err(e),
}
}
pub fn get_reader_for_file_or_stdin<P: AsRef<Path>>(
path: Option<P>,
) -> std::io::Result<Box<dyn std::io::Read>> {
match path {
None => Ok(Box::new(BufReader::new(stdin()))),
Some(p) => Ok(Box::new(BufReader::new(File::open(p)?))),
}
}
pub fn is_base64(input: &str) -> bool {
input.len().is_multiple_of(4)
&& input
.bytes()
.all(|b| matches!(b, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'/' | b'='))
}
pub fn is_test_like_path(path: &Path) -> bool {
path.components().any(|c| {
if let std::path::Component::Normal(os) = c
&& let Some(name) = os.to_str()
{
let name = name.to_ascii_lowercase();
return name.contains("test")
|| name.contains("spec")
|| name.contains("fixture")
|| name.contains("example")
|| name.contains("sample");
}
false
})
}
#[cfg(test)]
mod tests {
use std::{
io::{Cursor, Read, Write},
path::PathBuf,
};
use super::{is_test_like_path, *};
#[test]
fn tokio_blocking_threads_limit_scales_and_caps() {
assert_eq!(tokio_blocking_threads_limit(0), 32);
assert_eq!(tokio_blocking_threads_limit(1), 32);
assert_eq!(tokio_blocking_threads_limit(4), 32);
assert_eq!(tokio_blocking_threads_limit(8), 64);
assert_eq!(tokio_blocking_threads_limit(32), 256);
assert_eq!(tokio_blocking_threads_limit(usize::MAX), 256);
}
#[test]
fn test_is_test_like_path_positive() {
let positives = [
"src/tests/helpers.rs",
"/project/spec/controllers/user_spec.rb",
"C:\\repo\\fixtures\\config.json",
"examples/hello_world/main.go",
"/home/user/scripts/local-testCert.pem",
"samples/data/sample_input.txt",
];
for p in positives {
assert!(
is_test_like_path(Path::new(p)),
"Path {p:?} was expected to be test-like but was not"
);
}
}
#[test]
fn test_is_test_like_path_negative() {
let negatives = [
"src/main.rs",
"/opt/service/config/production.yml",
"C:\\Program Files\\app\\README.md",
"docs/architecture/overview.md",
"assets/images/logo.png",
];
for p in negatives {
assert!(
!is_test_like_path(Path::new(p)),
"Path {p:?} was incorrectly classified as test-like"
);
}
}
#[test]
fn test_counted_display_regular() {
let single = Counted::regular(1, "rule");
let multiple = Counted::regular(3, "rule");
assert_eq!(format!("{}", single), "1 rule");
assert_eq!(format!("{}", multiple), "3 rules");
}
#[test]
fn test_counted_display_explicit() {
let single = Counted::new(1, "person", "people");
let multiple = Counted::new(5, "person", "people");
assert_eq!(format!("{}", single), "1 person");
assert_eq!(format!("{}", multiple), "5 people");
}
#[test]
fn test_get_writer_for_file_or_stdout_stdout() {
use std::io::Write;
let mut writer = get_writer_for_file_or_stdout::<PathBuf>(None).unwrap();
let result = writer.write(b"Test output to stdout\n");
assert!(result.is_ok(), "Failed to write to stdout");
}
#[test]
fn test_get_writer_for_file_or_stdout_file() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let path = temp_file.path().to_path_buf();
let mut writer = get_writer_for_file_or_stdout(Some(&path)).unwrap();
writer.write_all(b"Test content").unwrap();
writer.flush().unwrap();
let mut file_content = String::new();
std::fs::File::open(&path).unwrap().read_to_string(&mut file_content).unwrap();
assert_eq!(file_content, "Test content");
}
#[cfg(unix)]
#[test]
fn test_get_writer_for_file_refuses_symlink() {
use std::io::Write;
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("target.txt");
std::fs::write(&target, b"ORIGINAL_CONTENT").unwrap();
let link = dir.path().join("report.json");
std::os::unix::fs::symlink(&target, &link).unwrap();
let err = get_writer_for_file_or_stdout(Some(&link)).err();
assert!(err.is_some(), "writer must refuse a symlinked output path");
assert_eq!(std::fs::read(&target).unwrap(), b"ORIGINAL_CONTENT");
let regular = dir.path().join("plain.json");
std::fs::write(®ular, b"stale").unwrap();
let mut writer = get_writer_for_file_or_stdout(Some(®ular)).unwrap();
writer.write_all(b"fresh").unwrap();
writer.flush().unwrap();
drop(writer);
assert_eq!(std::fs::read(®ular).unwrap(), b"fresh");
}
#[test]
fn test_get_reader_for_file_or_stdin_stdin() {
let input = b"stdin test content";
let mut stdin_mock = Cursor::new(input);
let mut reader = BufReader::new(&mut stdin_mock);
let mut buffer = String::new();
reader.read_to_string(&mut buffer).unwrap();
assert_eq!(buffer, "stdin test content");
}
#[test]
fn test_get_reader_for_file_or_stdin_file() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let path = temp_file.path().to_path_buf();
std::fs::write(&path, "File test content").unwrap();
let mut reader = get_reader_for_file_or_stdin(Some(&path)).unwrap();
let mut buffer = String::new();
reader.read_to_string(&mut buffer).unwrap();
assert_eq!(buffer, "File test content");
}
#[test]
fn test_is_base64_valid() {
let valid_base64 = "SGVsbG8gV29ybGQh"; let valid_base64_with_padding = "SGVsbG8gdGhpcyB3b3JsZAo=";
let valid_empty = "";
assert!(is_base64(valid_base64));
assert!(is_base64(valid_base64_with_padding));
assert!(is_base64(valid_empty));
}
#[test]
fn test_is_base64_invalid() {
let invalid_base64 = "Hello World!";
let invalid_length = "SGVsbG8"; let invalid_characters = "SGVsbG8$V29ybGQh";
assert!(!is_base64(invalid_base64));
assert!(!is_base64(invalid_length));
assert!(!is_base64(invalid_characters));
}
#[test]
fn test_expand_tilde_expands_leading_tilde() {
assert_eq!(
expand_tilde_with_home(Path::new("~/rules"), Some("/home/alice")),
PathBuf::from("/home/alice/rules")
);
assert_eq!(
expand_tilde_with_home(Path::new("~/src/kingfisher-rules-113"), Some("/home/alice")),
PathBuf::from("/home/alice/src/kingfisher-rules-113")
);
assert_eq!(
expand_tilde_with_home(Path::new("~"), Some("/home/alice")),
PathBuf::from("/home/alice")
);
}
#[test]
fn test_expand_tilde_leaves_other_paths_untouched() {
assert_eq!(
expand_tilde_with_home(Path::new("/abs/rules"), Some("/home/alice")),
PathBuf::from("/abs/rules")
);
assert_eq!(
expand_tilde_with_home(Path::new("relative/rules"), Some("/home/alice")),
PathBuf::from("relative/rules")
);
assert_eq!(
expand_tilde_with_home(Path::new("rules/~"), Some("/home/alice")),
PathBuf::from("rules/~")
);
}
#[test]
fn test_expand_tilde_without_home_is_passthrough() {
assert_eq!(expand_tilde_with_home(Path::new("~/rules"), None), PathBuf::from("~/rules"));
assert_eq!(
expand_tilde_with_home(Path::new("~/rules"), Some("")),
PathBuf::from("~/rules")
);
}
#[test]
fn test_expand_tilde_preserves_literal_after_tilde() {
assert_eq!(
expand_tilde_with_home(Path::new("~/rules"), Some("/home/alice/")),
PathBuf::from("/home/alice//rules")
);
assert_eq!(
expand_tilde_with_home(Path::new("~rules"), Some("/home/alice")),
PathBuf::from("~rules")
);
}
#[cfg(windows)]
#[test]
fn test_expand_tilde_handles_windows_style_separator() {
assert_eq!(
expand_tilde_with_home(Path::new("~\\rules"), Some(r"C:\Users\alice")),
PathBuf::from(r"C:\Users\alice\rules")
);
}
#[cfg(not(windows))]
#[test]
fn test_expand_tilde_backslash_form_is_literal_on_unix() {
assert_eq!(
expand_tilde_with_home(Path::new("~\\rules"), Some("/home/alice")),
PathBuf::from("~\\rules")
);
}
#[test]
fn test_expand_tilde_non_tilde_path_is_deterministic() {
assert_eq!(expand_tilde(Path::new("/abs/rules")), PathBuf::from("/abs/rules"));
}
}