#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::must_use_candidate)] #![allow(clippy::return_self_not_must_use)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::doc_markdown)] #![allow(clippy::too_many_lines)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_sign_loss)] #![allow(clippy::similar_names)] #![allow(clippy::if_not_else)] #![allow(clippy::unnecessary_wraps)] #![allow(clippy::unused_self)] #![allow(clippy::match_same_arms)]
pub mod ast_summarizer;
pub mod chunker;
pub mod config;
pub mod config_manager;
pub mod config_validation;
pub mod error;
pub mod file_info;
pub mod formatter;
pub mod formatters;
pub mod json_schema;
pub mod language;
pub mod processor;
pub mod profile;
pub mod streaming;
pub mod summarizer;
pub mod summary;
pub mod summary_item;
pub mod tokens;
pub mod traits;
pub use tokens::TokenExtractor;
pub use config::BatlessConfig;
pub use error::{BatlessError, BatlessResult};
pub use file_info::FileInfo;
pub use formatter::{OutputFormatter, OutputMode};
pub use json_schema::{get_json_schema, validate_batless_output, JsonSchemaValidator};
pub use language::LanguageDetector;
pub use processor::FileProcessor;
pub use profile::CustomProfile;
pub use streaming::{StreamingCheckpoint, StreamingChunk, StreamingProcessor};
pub use summary::SummaryLevel;
pub use tokens::{AiModel, TokenCount, TokenCounter};
pub fn process_file(file_path: &str, config: &BatlessConfig) -> BatlessResult<FileInfo> {
FileProcessor::process_file(file_path, config)
}
pub fn detect_language(file_path: &str) -> Option<String> {
LanguageDetector::detect_language(file_path)
}
pub fn list_languages() -> Vec<String> {
LanguageDetector::list_languages()
}
pub fn format_output(
file_info: &FileInfo,
file_path: &str,
config: &BatlessConfig,
output_mode: OutputMode,
) -> BatlessResult<String> {
OutputFormatter::format_output(file_info, file_path, config, output_mode)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
fn create_test_file(content: &str) -> NamedTempFile {
let mut file = NamedTempFile::new().unwrap();
write!(file, "{content}").unwrap();
file
}
#[test]
fn test_process_file_basic() -> BatlessResult<()> {
let file = create_test_file("line1\nline2\nline3");
let config = BatlessConfig::default();
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert_eq!(result.lines.len(), 3);
assert_eq!(result.total_lines, 3);
assert!(!result.truncated);
Ok(())
}
#[test]
fn test_process_file_max_lines() -> BatlessResult<()> {
let file = create_test_file("line1\nline2\nline3\nline4\nline5");
let config = BatlessConfig::default().with_max_lines(3);
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert_eq!(result.lines.len(), 3);
assert!(result.truncated);
assert!(result.truncated_by_lines);
Ok(())
}
#[test]
fn test_process_file_max_bytes() -> BatlessResult<()> {
let large_content = "a".repeat(2000); let file = create_test_file(&large_content);
let config = BatlessConfig::default()
.with_max_bytes(Some(1000)) .with_max_lines(100);
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert!(result.truncated);
assert!(result.truncated_by_bytes);
Ok(())
}
#[test]
fn test_detect_language_rust() {
let language = detect_language("test.rs");
assert_eq!(language, Some("Rust".to_string()));
}
#[test]
fn test_detect_language_python() {
let language = detect_language("test.py");
assert_eq!(language, Some("Python".to_string()));
}
#[test]
fn test_detect_language_unknown() {
let language = detect_language("test.unknown");
assert_eq!(language, None);
}
#[test]
fn test_list_languages() {
let languages = list_languages();
assert!(!languages.is_empty());
assert!(languages.contains(&"Rust".to_string()));
}
#[test]
fn test_summary_mode() -> BatlessResult<()> {
let file = create_test_file("fn main() {\n println!(\"Hello\");\n}");
let config = BatlessConfig::default().with_summary_mode(true);
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert!(result.has_summary());
assert!(result.summary_line_count() > 0);
Ok(())
}
#[test]
fn test_include_tokens() -> BatlessResult<()> {
let file = create_test_file("fn main() { println!(\"Hello\"); }");
let config = BatlessConfig::default().with_include_tokens(true);
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert!(result.has_tokens());
assert!(result.token_count() > 0);
Ok(())
}
#[test]
fn test_encoding_detection() -> BatlessResult<()> {
let file = create_test_file("Hello, 世界!");
let config = BatlessConfig::default();
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert_eq!(result.encoding, "UTF-8");
Ok(())
}
#[test]
fn test_config_default() {
let config = BatlessConfig::default();
assert_eq!(config.max_lines, 10000);
assert_eq!(config.max_bytes, None);
assert_eq!(config.language, None);
assert!(!config.strip_ansi);
assert!(config.use_color);
assert!(!config.include_tokens);
assert!(!config.summary_mode);
}
#[test]
fn test_empty_file() -> BatlessResult<()> {
let file = create_test_file("");
let config = BatlessConfig::default();
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert_eq!(result.lines.len(), 0);
assert_eq!(result.total_lines, 0);
assert!(!result.truncated);
Ok(())
}
#[test]
fn test_single_line_file() -> BatlessResult<()> {
let file = create_test_file("single line without newline");
let config = BatlessConfig::default();
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert_eq!(result.lines.len(), 1);
assert_eq!(result.total_lines, 1);
assert!(!result.truncated);
Ok(())
}
#[test]
fn test_format_output_modes() -> BatlessResult<()> {
let file = create_test_file("fn main() {}");
let config = BatlessConfig::default();
let file_info = process_file(file.path().to_str().unwrap(), &config)?;
let plain = format_output(&file_info, "test.rs", &config, OutputMode::Plain)?;
assert_eq!(plain, "fn main() {}");
let json = format_output(&file_info, "test.rs", &config, OutputMode::Json)?;
assert!(
json.contains("\"file\": \"test.rs\"") || json.contains("\"file\":\"test.rs\""),
"JSON output did not contain expected file field: {json}"
);
let summary = format_output(&file_info, "test.rs", &config, OutputMode::Summary)?;
assert!(summary.contains("=== File Summary ==="));
Ok(())
}
#[test]
fn test_error_handling() {
let config = BatlessConfig::default();
let result = process_file("nonexistent_file.txt", &config);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
BatlessError::FileNotFound { .. }
));
}
#[test]
fn test_configuration_validation() {
let invalid_config = BatlessConfig::default().with_max_lines(0);
assert!(invalid_config.validate().is_err());
let valid_config = BatlessConfig::default().with_max_lines(100);
assert!(valid_config.validate().is_ok());
}
#[test]
fn test_language_override() -> BatlessResult<()> {
let file = create_test_file("print('hello')");
let config = BatlessConfig::default().with_language(Some("Python".to_string()));
let result = process_file(file.path().to_str().unwrap(), &config)?;
assert_eq!(result.language, Some("Python".to_string()));
Ok(())
}
}