mirror of
https://github.com/rtk-ai/rtk.git
synced 2026-09-19 07:33:17 +08:00
5e0f3ba774
Changed default filter level from "minimal" to "none". RTK read now shows complete file content by default. Filtering is opt-in: rtk read file.rs # full content (was: minimal filter) rtk read file.rs -l minimal # light filtering (opt-in) rtk read file.rs -l aggressive # signatures only (opt-in) Also adds fallback: if a filter produces empty output on non-empty file, show raw content with a warning. Fixes #822 Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
228 lines
6.1 KiB
Rust
228 lines
6.1 KiB
Rust
use crate::filter::{self, FilterLevel, Language};
|
|
use crate::tracking;
|
|
use anyhow::{Context, Result};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
pub fn run(
|
|
file: &Path,
|
|
level: FilterLevel,
|
|
max_lines: Option<usize>,
|
|
tail_lines: Option<usize>,
|
|
line_numbers: bool,
|
|
verbose: u8,
|
|
) -> Result<()> {
|
|
let timer = tracking::TimedExecution::start();
|
|
|
|
if verbose > 0 {
|
|
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()))?;
|
|
|
|
// Detect language from extension
|
|
let lang = file
|
|
.extension()
|
|
.and_then(|e| e.to_str())
|
|
.map(Language::from_extension)
|
|
.unwrap_or(Language::Unknown);
|
|
|
|
if verbose > 1 {
|
|
eprintln!("Detected language: {:?}", lang);
|
|
}
|
|
|
|
// Apply filter
|
|
let filter = filter::get_filter(level);
|
|
let mut filtered = filter.filter(&content, &lang);
|
|
|
|
// Safety: if filter emptied a non-empty file, fall back to raw content
|
|
if filtered.trim().is_empty() && !content.trim().is_empty() {
|
|
eprintln!(
|
|
"rtk: warning: filter produced empty output for {} ({} bytes), showing raw content",
|
|
file.display(),
|
|
content.len()
|
|
);
|
|
filtered = content.clone();
|
|
}
|
|
|
|
if verbose > 0 {
|
|
let original_lines = content.lines().count();
|
|
let filtered_lines = filtered.lines().count();
|
|
let reduction = if original_lines > 0 {
|
|
((original_lines - filtered_lines) as f64 / original_lines as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
};
|
|
eprintln!(
|
|
"Lines: {} -> {} ({:.1}% reduction)",
|
|
original_lines, filtered_lines, reduction
|
|
);
|
|
}
|
|
|
|
filtered = apply_line_window(&filtered, max_lines, tail_lines, &lang);
|
|
|
|
let rtk_output = if line_numbers {
|
|
format_with_line_numbers(&filtered)
|
|
} else {
|
|
filtered.clone()
|
|
};
|
|
println!("{}", rtk_output);
|
|
timer.track(
|
|
&format!("cat {}", file.display()),
|
|
"rtk read",
|
|
&content,
|
|
&rtk_output,
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn run_stdin(
|
|
level: FilterLevel,
|
|
max_lines: Option<usize>,
|
|
tail_lines: Option<usize>,
|
|
line_numbers: bool,
|
|
verbose: u8,
|
|
) -> Result<()> {
|
|
use std::io::{self, Read as IoRead};
|
|
|
|
let timer = tracking::TimedExecution::start();
|
|
|
|
if verbose > 0 {
|
|
eprintln!("Reading from stdin (filter: {})", level);
|
|
}
|
|
|
|
// Read from stdin
|
|
let mut content = String::new();
|
|
io::stdin()
|
|
.lock()
|
|
.read_to_string(&mut content)
|
|
.context("Failed to read from stdin")?;
|
|
|
|
// No file extension, so use Unknown language
|
|
let lang = Language::Unknown;
|
|
|
|
if verbose > 1 {
|
|
eprintln!("Language: {:?} (stdin has no extension)", lang);
|
|
}
|
|
|
|
// Apply filter
|
|
let filter = filter::get_filter(level);
|
|
let mut filtered = filter.filter(&content, &lang);
|
|
|
|
if verbose > 0 {
|
|
let original_lines = content.lines().count();
|
|
let filtered_lines = filtered.lines().count();
|
|
let reduction = if original_lines > 0 {
|
|
((original_lines - filtered_lines) as f64 / original_lines as f64) * 100.0
|
|
} else {
|
|
0.0
|
|
};
|
|
eprintln!(
|
|
"Lines: {} -> {} ({:.1}% reduction)",
|
|
original_lines, filtered_lines, reduction
|
|
);
|
|
}
|
|
|
|
filtered = apply_line_window(&filtered, max_lines, tail_lines, &lang);
|
|
|
|
let rtk_output = if line_numbers {
|
|
format_with_line_numbers(&filtered)
|
|
} else {
|
|
filtered.clone()
|
|
};
|
|
println!("{}", rtk_output);
|
|
|
|
timer.track("cat - (stdin)", "rtk read -", &content, &rtk_output);
|
|
Ok(())
|
|
}
|
|
|
|
fn format_with_line_numbers(content: &str) -> String {
|
|
let lines: Vec<&str> = content.lines().collect();
|
|
let width = lines.len().to_string().len();
|
|
let mut out = String::new();
|
|
for (i, line) in lines.iter().enumerate() {
|
|
out.push_str(&format!("{:>width$} │ {}\n", i + 1, line, width = width));
|
|
}
|
|
out
|
|
}
|
|
|
|
fn apply_line_window(
|
|
content: &str,
|
|
max_lines: Option<usize>,
|
|
tail_lines: Option<usize>,
|
|
lang: &Language,
|
|
) -> String {
|
|
if let Some(tail) = tail_lines {
|
|
if tail == 0 {
|
|
return String::new();
|
|
}
|
|
let lines: Vec<&str> = content.lines().collect();
|
|
let start = lines.len().saturating_sub(tail);
|
|
let mut result = lines[start..].join("\n");
|
|
if content.ends_with('\n') {
|
|
result.push('\n');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if let Some(max) = max_lines {
|
|
return filter::smart_truncate(content, max, lang);
|
|
}
|
|
|
|
content.to_string()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use tempfile::NamedTempFile;
|
|
|
|
#[test]
|
|
fn test_read_rust_file() -> Result<()> {
|
|
let mut file = NamedTempFile::with_suffix(".rs")?;
|
|
writeln!(
|
|
file,
|
|
r#"// Comment
|
|
fn main() {{
|
|
println!("Hello");
|
|
}}"#
|
|
)?;
|
|
|
|
// Just verify it doesn't panic
|
|
run(file.path(), FilterLevel::Minimal, None, None, false, 0)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_stdin_support_signature() {
|
|
// Test that run_stdin has correct signature and compiles
|
|
// We don't actually run it because it would hang waiting for stdin
|
|
// Compile-time verification that the function exists with correct signature
|
|
}
|
|
|
|
#[test]
|
|
fn test_apply_line_window_tail_lines() {
|
|
let input = "a\nb\nc\nd\n";
|
|
let output = apply_line_window(input, None, Some(2), &Language::Unknown);
|
|
assert_eq!(output, "c\nd\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_apply_line_window_tail_lines_no_trailing_newline() {
|
|
let input = "a\nb\nc\nd";
|
|
let output = apply_line_window(input, None, Some(2), &Language::Unknown);
|
|
assert_eq!(output, "c\nd");
|
|
}
|
|
|
|
#[test]
|
|
fn test_apply_line_window_max_lines_still_works() {
|
|
let input = "a\nb\nc\nd\n";
|
|
let output = apply_line_window(input, Some(2), None, &Language::Unknown);
|
|
assert!(output.starts_with("a\n"));
|
|
assert!(output.contains("more lines"));
|
|
}
|
|
}
|