diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a824cc4..f86e628 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -3,7 +3,7 @@ use structopt::StructOpt; #[derive(StructOpt)] pub struct CLI { - // Flag to search all classes with that value + /// Flag to search all classes with that value #[structopt( short = "g", long = "grep", @@ -11,11 +11,11 @@ pub struct CLI { )] pub grep: bool, - // Class name to be fetched + /// Class name to be fetched #[structopt(help = "Name of the Python class")] pub class_name: String, - // Search directory + /// Search path #[structopt(parse(from_os_str), default_value = ".", help = "Search directory")] - pub dir_path: PathBuf, + pub path: PathBuf, } diff --git a/src/joneslib/loader.rs b/src/joneslib/loader.rs index be6d1bb..b777f4d 100644 --- a/src/joneslib/loader.rs +++ b/src/joneslib/loader.rs @@ -22,13 +22,20 @@ static INHERITANCE_PATTERN: &str = r"\s\[(.*?)\]"; static OUTPUT_PATTERN: &str = r" (\w+)"; /// Loads all objects from a Python project, given through the python project path. -pub fn load_python_project(project_path: &PathBuf) -> Option> { +pub fn load_python_project(path: &PathBuf) -> Option> { let class_name_pattern = Regex::new(CLASS_NAME_PATTERN).unwrap(); let file_name_pattern = Regex::new(FILE_NAME_PATTERN).unwrap(); - let script_output = match run_python_script(&project_path) { - Some(output) => String::from_utf8(output).unwrap(), - None => return None, + let script_output = if path.is_dir() { + match run_python_script(&path) { + Some(output) => String::from_utf8(output).unwrap(), + None => return None, + } + } else { + match run_python_single_file_script(&path) { + Some(output) => String::from_utf8(output).unwrap(), + None => return None, + } }; let file_pattern_captures = file_name_pattern.captures_iter(&script_output); @@ -129,6 +136,36 @@ for root, dirs, files in os.walk({:?}): } } +#[inline] +fn run_python_single_file_script(file_path: &PathBuf) -> Option> { + let python_script = format!( + r#"import ast +import os + +file_name = {:?} +with open(file_name, "r") as file: + tree = ast.parse(file.read()) + +for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + + class_name = node.name + print(" %s, %s" % (class_name, file_name)) +"#, + file_path.as_os_str(), + ); + + let output = Command::new("python").arg("-c").arg(python_script).output(); + + if let Ok(output) = output { + println!("{}", String::from_utf8(output.stderr).unwrap()); + Some(output.stdout) + } else { + None + } +} + #[inline] fn run_python_class_script(file_path: &PathBuf, class_name: &String) -> Option> { let python_script = format!( diff --git a/src/joneslib/mod.rs b/src/joneslib/mod.rs index 81f7729..daad7e6 100644 --- a/src/joneslib/mod.rs +++ b/src/joneslib/mod.rs @@ -31,7 +31,7 @@ type ClassMatch = (String, String); /// /// # Errors /// It panics if the file is cannot be read properly -fn check_file_contains_class(class_name: &str, file_path: &str) -> bool { +fn check_file_contains_class(class_name: &str, file_path: &PathBuf) -> bool { let class_name_inheritance = CLASS_TEMPLATE_INHERITANCE .clone() .replace(TEMPLATE_KEYWORD, class_name); @@ -49,35 +49,42 @@ fn check_file_contains_class(class_name: &str, file_path: &str) -> bool { /// Searches recursively through a project for a Python class and extracts that /// class into an PythonClass struct. -pub fn project_traversal(dir_path: &PathBuf, class_name: &String) -> Option { +pub fn fetch_object_details(path: &PathBuf, class_name: &String) -> Option { + if path.is_file() && check_file_contains_class(class_name, path) { + return loader::load_python_object(path, class_name); + } + + recursive_fetch_object(path, class_name) +} + +fn recursive_fetch_object(dir_path: &PathBuf, class_name: &String) -> Option { let current_dir = match fs::read_dir(dir_path) { Ok(dir) => dir, Err(err) => { - println!("Error occured while reading dir: {}", err); + println!("Error occurred while reading dir: {}", err); return None; } }; for file in current_dir { let file_path = file.unwrap().path(); - let file_path_name = file_path.to_str().unwrap(); if file_path.is_dir() { - match project_traversal(&file_path, class_name) { + match recursive_fetch_object(&file_path, class_name) { Some(value) => return Some(value), None => continue, }; - } else { - match file_path.extension() { - Some(extension) => { - if extension != PYTHON_EXTENSION { - continue; - } + } + + match file_path.extension() { + Some(extension) => { + if extension != PYTHON_EXTENSION { + continue; } - None => continue, - } - if check_file_contains_class(class_name, &file_path_name) { - return loader::load_python_object(&file_path, &class_name); } + None => continue, + } + if check_file_contains_class(class_name, &file_path) { + return loader::load_python_object(&file_path, &class_name); } } return None; @@ -85,8 +92,8 @@ pub fn project_traversal(dir_path: &PathBuf, class_name: &String) -> Option Option> { - let project_classes = match loader::load_python_project(dir_path) { +pub fn search(path: &PathBuf, class_name: &String) -> Option> { + let project_classes = match loader::load_python_project(path) { Some(classes) => classes, None => { println!("Error occurred while loading project classes"); @@ -104,7 +111,7 @@ pub fn search(dir_path: &PathBuf, class_name: &String) -> Option #[cfg(test)] mod tests { - use super::project_traversal; + use super::fetch_object_details; use std::fs; use std::path::PathBuf; @@ -143,9 +150,34 @@ mod tests { pathbuf.push("./testing_none"); // Assert - assert_eq!(project_traversal(&pathbuf, &"TestCode".to_string()), None); + assert_eq!( + fetch_object_details(&pathbuf, &"TestCode".to_string()), + None + ); // Destroy the test dir fs::remove_dir_all("./testing_none").expect("Could not delete dir"); } + + #[test] + fn test_fetch_object_given_file_path() { + // Paths + let test_dir = String::from("./test_fetch_objects"); + let python_file = String::from("./test_fetch_objects/test.py"); + let mut pathbuf = PathBuf::new(); + + // Create dir and files + fs::create_dir(test_dir).expect("Could not write dir"); + fs::write(python_file, PYTHON_CODE).unwrap(); + pathbuf.push("./test_fetch_objects/test.py"); + + // Assert + assert_eq!( + fetch_object_details(&pathbuf, &"TestCode".to_string()), + None + ); + + // Destroy the test dir + fs::remove_dir_all("./test_fetch_objects").expect("Could not delete dir"); + } } diff --git a/src/main.rs b/src/main.rs index 6c80a59..01cb19f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,13 +18,13 @@ fn main() { let comms = commands::CLI::from_args(); if comms.grep { // Search for a keyword in class name - match joneslib::search(&comms.dir_path, &comms.class_name) { + match joneslib::search(&comms.path, &comms.class_name) { Some(matches) => display::class_matches(matches), None => display::not_found_message(), } } else { // Generate python class - match joneslib::project_traversal(&comms.dir_path, &comms.class_name) { + match joneslib::fetch_object_details(&comms.path, &comms.class_name) { Some(class) => joneslib::display::output_class(&class), None => display::not_found_message(), }