use std::path::Path;
use crate::error::{Result, SecretraceError};
pub struct ParseResult {
pub ast: syn::File,
pub source: String,
}
impl ParseResult {
pub fn line_count(&self) -> usize {
self.source.lines().count()
}
pub fn get_line(&self, line: u32) -> Option<&str> {
self.source.lines().nth(line.saturating_sub(1) as usize)
}
pub fn get_context(&self, line: u32, before: usize, after: usize) -> Vec<(u32, &str)> {
let start = line.saturating_sub(before as u32);
let end = line.saturating_add(after as u32);
self.source
.lines()
.enumerate()
.skip(start.saturating_sub(1) as usize)
.take((end - start + 1) as usize)
.map(|(i, l)| ((i + 1) as u32, l))
.collect()
}
}
pub fn parse_file(path: impl AsRef<Path>) -> Result<ParseResult> {
let path = path.as_ref();
let source = std::fs::read_to_string(path).map_err(|e| SecretraceError::FileRead {
path: path.to_path_buf(),
source: e,
})?;
let ast = syn::parse_file(&source).map_err(|e| SecretraceError::Parse {
path: path.to_path_buf(),
source: e,
})?;
Ok(ParseResult { ast, source })
}
pub fn parse_str(source: &str) -> std::result::Result<syn::File, syn::Error> {
syn::parse_file(source)
}
pub fn is_rust_file(path: impl AsRef<Path>) -> bool {
path.as_ref()
.extension()
.map(|e| e == "rs")
.unwrap_or(false)
}
pub fn is_file_too_large(path: impl AsRef<Path>, max_bytes: usize) -> bool {
std::fs::metadata(path.as_ref())
.map(|m| m.len() as usize > max_bytes)
.unwrap_or(true)
}
pub fn looks_like_rust(content: &str) -> bool {
content.contains("fn ")
|| content.contains("use ")
|| content.contains("mod ")
|| content.contains("struct ")
|| content.contains("enum ")
|| content.contains("impl ")
|| content.contains("const ")
|| content.contains("static ")
|| content.contains("pub ")
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_parse_valid_rust() {
let code = r#"
fn main() {
println!("Hello");
}
"#;
let result = parse_str(code);
assert!(result.is_ok());
}
#[test]
fn test_parse_invalid_rust() {
let code = "fn broken( {";
let result = parse_str(code);
assert!(result.is_err());
}
#[test]
fn test_parse_file() {
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "fn main() {{}}").unwrap();
let result = parse_file(file.path());
assert!(result.is_ok());
}
#[test]
fn test_is_rust_file() {
assert!(is_rust_file("src/main.rs"));
assert!(is_rust_file("lib.rs"));
assert!(!is_rust_file("README.md"));
assert!(!is_rust_file("Cargo.toml"));
}
#[test]
fn test_looks_like_rust() {
assert!(looks_like_rust("fn main() {}"));
assert!(looks_like_rust("use std::io;"));
assert!(looks_like_rust("pub struct Foo;"));
assert!(!looks_like_rust("Hello, World!"));
}
#[test]
fn test_get_context() {
let source = "line1\nline2\nline3\nline4\nline5".to_string();
let result = ParseResult {
ast: syn::parse_str("fn x(){}").unwrap(),
source,
};
let context = result.get_context(3, 1, 1);
assert_eq!(context.len(), 3);
assert_eq!(context[0], (2, "line2"));
assert_eq!(context[1], (3, "line3"));
assert_eq!(context[2], (4, "line4"));
}
}