26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
245 lines
5.6 KiB
Rust
245 lines
5.6 KiB
Rust
use std::path::Path;
|
|
|
|
const BINARY_EXTENSIONS: &[&str] = &[
|
|
// Data formats
|
|
"parquet",
|
|
"avro",
|
|
"orc",
|
|
"arrow",
|
|
"feather",
|
|
"hdf5",
|
|
"h5",
|
|
"npy",
|
|
"npz",
|
|
// Databases
|
|
"db",
|
|
"sqlite",
|
|
"sqlite3",
|
|
"mdb",
|
|
"accdb",
|
|
"ldb",
|
|
// Archives
|
|
"zip",
|
|
"gz",
|
|
"tar",
|
|
"bz2",
|
|
"xz",
|
|
"7z",
|
|
"rar",
|
|
"zst",
|
|
"lz4",
|
|
"lzma",
|
|
// Images
|
|
"png",
|
|
"jpg",
|
|
"jpeg",
|
|
"gif",
|
|
"webp",
|
|
"bmp",
|
|
"ico",
|
|
"tiff",
|
|
"tif",
|
|
"svg",
|
|
"psd",
|
|
"raw",
|
|
"cr2",
|
|
"nef",
|
|
"heic",
|
|
"heif",
|
|
"avif",
|
|
// Audio/Video
|
|
"mp3",
|
|
"mp4",
|
|
"wav",
|
|
"flac",
|
|
"ogg",
|
|
"avi",
|
|
"mkv",
|
|
"mov",
|
|
"webm",
|
|
"m4a",
|
|
// Executables/Libraries
|
|
"exe",
|
|
"dll",
|
|
"so",
|
|
"dylib",
|
|
"o",
|
|
"a",
|
|
"obj",
|
|
"lib",
|
|
"pdb",
|
|
"class",
|
|
"jar",
|
|
"war",
|
|
"ear",
|
|
// Compiled/Bytecode
|
|
"pyc",
|
|
"pyo",
|
|
"whl",
|
|
"egg",
|
|
"beam",
|
|
"wasm",
|
|
"wast",
|
|
// ML models
|
|
"model",
|
|
"onnx",
|
|
"pt",
|
|
"pth",
|
|
"safetensors",
|
|
"gguf",
|
|
"ggml",
|
|
"tflite",
|
|
"pb",
|
|
"h5",
|
|
"keras",
|
|
// Serialized
|
|
"pkl",
|
|
"pickle",
|
|
"bin",
|
|
"dat",
|
|
"protobuf",
|
|
// Documents (binary)
|
|
"pdf",
|
|
"doc",
|
|
"docx",
|
|
"xls",
|
|
"xlsx",
|
|
"ppt",
|
|
"pptx",
|
|
"odt",
|
|
"ods",
|
|
// Fonts
|
|
"ttf",
|
|
"otf",
|
|
"woff",
|
|
"woff2",
|
|
"eot",
|
|
// Disk images
|
|
"iso",
|
|
"img",
|
|
"vmdk",
|
|
"qcow2",
|
|
];
|
|
|
|
/// Fast extension-based binary detection (zero I/O).
|
|
fn has_binary_extension(path: &str) -> bool {
|
|
Path::new(path)
|
|
.extension()
|
|
.and_then(|e| e.to_str())
|
|
.map(str::to_ascii_lowercase)
|
|
.is_some_and(|ext| BINARY_EXTENSIONS.contains(&ext.as_str()))
|
|
}
|
|
|
|
/// Heuristic: read first 8 KB and check for NULL bytes.
|
|
/// Standard method used by `file(1)`, git, etc.
|
|
fn has_binary_content(path: &str) -> bool {
|
|
let Ok(file) = std::fs::File::open(path) else {
|
|
return false;
|
|
};
|
|
use std::io::Read;
|
|
let mut buf = [0u8; 8192];
|
|
let mut reader = std::io::BufReader::new(file);
|
|
let Ok(n) = reader.read(&mut buf) else {
|
|
return false;
|
|
};
|
|
buf[..n].contains(&0)
|
|
}
|
|
|
|
/// Returns `true` if the file is likely a binary file.
|
|
/// Checks extension first (zero I/O), falls back to content inspection.
|
|
pub fn is_binary_file(path: &str) -> bool {
|
|
if has_binary_extension(path) {
|
|
return true;
|
|
}
|
|
has_binary_content(path)
|
|
}
|
|
|
|
/// Returns a human-readable file type label for common binary extensions.
|
|
fn file_type_label(path: &str) -> &'static str {
|
|
let ext = Path::new(path)
|
|
.extension()
|
|
.and_then(|e| e.to_str())
|
|
.unwrap_or("");
|
|
match ext.to_ascii_lowercase().as_str() {
|
|
"parquet" | "avro" | "orc" | "arrow" | "feather" => "columnar data file",
|
|
"hdf5" | "h5" | "npy" | "npz" => "scientific data file",
|
|
"db" | "sqlite" | "sqlite3" => "database file",
|
|
"zip" | "gz" | "tar" | "bz2" | "xz" | "7z" | "rar" | "zst" => "compressed archive",
|
|
"png" | "jpg" | "jpeg" | "gif" | "webp" | "bmp" | "ico" | "heic" => "image file",
|
|
"mp3" | "mp4" | "wav" | "flac" | "ogg" | "avi" | "mkv" | "mov" => "media file",
|
|
"exe" | "dll" | "so" | "dylib" => "native binary",
|
|
"wasm" => "WebAssembly binary",
|
|
"pdf" => "PDF document",
|
|
"onnx" | "pt" | "pth" | "safetensors" | "gguf" | "ggml" => "ML model file",
|
|
"pkl" | "pickle" => "serialized object",
|
|
"pyc" | "pyo" => "Python bytecode",
|
|
"class" | "jar" | "war" => "Java bytecode",
|
|
_ => "binary file",
|
|
}
|
|
}
|
|
|
|
/// Returns a helpful error message for binary files, including file type and suggestions.
|
|
pub fn binary_file_message(path: &str) -> String {
|
|
let ext = Path::new(path)
|
|
.extension()
|
|
.and_then(|e| e.to_str())
|
|
.unwrap_or("unknown");
|
|
let label = file_type_label(path);
|
|
format!(
|
|
"Binary file detected (.{ext}, {label}). \
|
|
lean-ctx cannot read binary files as text. \
|
|
Use a specialized tool for this file type."
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn detects_binary_extensions() {
|
|
assert!(has_binary_extension("data.parquet"));
|
|
assert!(has_binary_extension("model.onnx"));
|
|
assert!(has_binary_extension("archive.tar.gz"));
|
|
assert!(has_binary_extension("photo.PNG"));
|
|
assert!(has_binary_extension("/path/to/file.sqlite3"));
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_text_extensions() {
|
|
assert!(!has_binary_extension("main.rs"));
|
|
assert!(!has_binary_extension("config.toml"));
|
|
assert!(!has_binary_extension("README.md"));
|
|
assert!(!has_binary_extension("script.py"));
|
|
}
|
|
|
|
#[test]
|
|
fn message_includes_type() {
|
|
let msg = binary_file_message("data.parquet");
|
|
assert!(msg.contains("columnar data file"));
|
|
assert!(msg.contains(".parquet"));
|
|
}
|
|
|
|
#[test]
|
|
fn message_for_unknown_binary() {
|
|
let msg = binary_file_message("file.xyz");
|
|
assert!(msg.contains("binary file"));
|
|
}
|
|
|
|
#[test]
|
|
fn null_byte_detection() {
|
|
let dir = std::env::temp_dir().join("lean_ctx_binary_test");
|
|
std::fs::create_dir_all(&dir).ok();
|
|
|
|
let bin_path = dir.join("test.bin");
|
|
std::fs::write(&bin_path, b"\x00\x01\x02\x03").unwrap();
|
|
assert!(has_binary_content(bin_path.to_str().unwrap()));
|
|
|
|
let txt_path = dir.join("test.txt");
|
|
std::fs::write(&txt_path, b"hello world").unwrap();
|
|
assert!(!has_binary_content(txt_path.to_str().unwrap()));
|
|
|
|
std::fs::remove_dir_all(&dir).ok();
|
|
}
|
|
}
|