mod error;
mod rust_parser;
mod types;
mod walker;
pub use error::{ParseError, ParseResult};
pub use rust_parser::RustParser;
pub use types::{
CodeBase, EnumInfo, EnumVariant, FieldInfo, FileInfo, MethodInfo, ParameterInfo, Receiver,
StructInfo, Visibility,
};
pub use walker::{DirectoryWalker, WalkerConfig};
use std::path::Path;
pub struct CodebaseParser {
walker_config: WalkerConfig,
include_private: bool,
continue_on_error: bool,
}
impl Default for CodebaseParser {
fn default() -> Self {
Self::new()
}
}
impl CodebaseParser {
pub fn new() -> Self {
Self {
walker_config: WalkerConfig::default(),
include_private: true,
continue_on_error: true,
}
}
pub fn walker_config(mut self, config: WalkerConfig) -> Self {
self.walker_config = config;
self
}
pub fn include_private(mut self, include: bool) -> Self {
self.include_private = include;
self
}
pub fn continue_on_error(mut self, continue_on_error: bool) -> Self {
self.continue_on_error = continue_on_error;
self
}
pub fn parse_directory<P: AsRef<Path>>(&self, path: P) -> ParseResult<CodeBase> {
let path = path.as_ref();
let walker = DirectoryWalker::new(self.walker_config.clone());
let rust_files = walker.discover_rust_files(path)?;
let rust_parser = RustParser::new().include_private(self.include_private);
let mut codebase = CodeBase::new();
for file_path in rust_files {
match rust_parser.parse_file(&file_path) {
Ok(file_codebase) => {
codebase.merge(file_codebase);
}
Err(e) => {
if !self.continue_on_error {
return Err(e);
}
eprintln!("Warning: Failed to parse {:?}: {}", file_path, e);
}
}
}
Ok(codebase)
}
pub fn parse_file<P: AsRef<Path>>(&self, path: P) -> ParseResult<CodeBase> {
let rust_parser = RustParser::new().include_private(self.include_private);
rust_parser.parse_file(path)
}
pub fn parse_source(&self, source: &str, file_path: &str) -> ParseResult<CodeBase> {
let rust_parser = RustParser::new().include_private(self.include_private);
rust_parser.parse_source(source, file_path.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_codebase_parser_directory() {
let dir = tempdir().unwrap();
let dir_path = dir.path();
fs::write(
dir_path.join("lib.rs"),
r#"
/// A test struct.
pub struct TestStruct {
/// A field.
pub value: i32,
}
impl TestStruct {
/// Creates a new instance.
pub fn new(value: i32) -> Self {
Self { value }
}
}
"#,
)
.unwrap();
fs::write(
dir_path.join("enums.rs"),
r#"
/// A test enum.
#[derive(Debug, Clone)]
pub enum TestEnum {
/// First variant.
First,
/// Second variant.
Second(i32),
}
"#,
)
.unwrap();
let parser = CodebaseParser::new();
let codebase = parser.parse_directory(dir_path).unwrap();
assert_eq!(codebase.structs.len(), 1);
assert_eq!(codebase.enums.len(), 1);
assert_eq!(codebase.files.len(), 2);
let test_struct = &codebase.structs[0];
assert_eq!(test_struct.name, "TestStruct");
assert_eq!(test_struct.methods.len(), 1);
let test_enum = &codebase.enums[0];
assert_eq!(test_enum.name, "TestEnum");
assert_eq!(test_enum.variants.len(), 2);
}
#[test]
fn test_codebase_parser_source() {
let source = r#"
pub struct Point {
pub x: f64,
pub y: f64,
}
"#;
let parser = CodebaseParser::new();
let codebase = parser.parse_source(source, "test.rs").unwrap();
assert_eq!(codebase.structs.len(), 1);
assert_eq!(codebase.structs[0].name, "Point");
}
}