diff --git a/src/cmds/system/read.rs b/src/cmds/system/read.rs index 2f56687e..f0bc026d 100644 --- a/src/cmds/system/read.rs +++ b/src/cmds/system/read.rs @@ -4,6 +4,7 @@ use crate::core::filter::{self, FilterLevel, Language}; use crate::core::tracking; use anyhow::{Context, Result}; use std::fs; +use std::io::Write; use std::path::Path; pub fn run( @@ -20,9 +21,28 @@ pub fn run( eprintln!("Reading: {} (filter: {})", file.display(), level); } - // Read file content - let content = fs::read_to_string(file) - .with_context(|| format!("Failed to read file: {}", file.display()))?; + let bytes = fs::read(file).with_context(|| format!("Failed to read file: {}", file.display()))?; + let content = match String::from_utf8(bytes.clone()) { + Ok(content) => content, + Err(_) if supports_binary_passthrough(level, max_lines, tail_lines, line_numbers) => { + if verbose > 0 { + eprintln!( + "rtk: warning: {} is non-UTF-8, using raw byte passthrough", + file.display() + ); + } + let mut stdout = std::io::stdout().lock(); + stdout.write_all(&bytes)?; + timer.track_passthrough(&format!("cat {}", file.display()), "rtk read"); + return Ok(()); + } + Err(_) => { + anyhow::bail!( + "Failed to read file: {}: stream did not contain valid UTF-8", + file.display() + ); + } + }; // Detect language from extension let lang = file @@ -176,6 +196,18 @@ fn apply_line_window( content.to_string() } +fn supports_binary_passthrough( + level: FilterLevel, + max_lines: Option, + tail_lines: Option, + line_numbers: bool, +) -> bool { + level == FilterLevel::None + && max_lines.is_none() + && tail_lines.is_none() + && !line_numbers +} + #[cfg(test)] mod tests { use super::*; @@ -227,6 +259,40 @@ fn main() {{ assert!(output.contains("more lines")); } + #[test] + fn test_supports_binary_passthrough_only_for_plain_reads() { + assert!(supports_binary_passthrough( + FilterLevel::None, + None, + None, + false + )); + assert!(!supports_binary_passthrough( + FilterLevel::Minimal, + None, + None, + false + )); + assert!(!supports_binary_passthrough( + FilterLevel::None, + Some(10), + None, + false + )); + assert!(!supports_binary_passthrough( + FilterLevel::None, + None, + Some(10), + false + )); + assert!(!supports_binary_passthrough( + FilterLevel::None, + None, + None, + true + )); + } + fn rtk_bin() -> std::path::PathBuf { std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("target")