Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#18] Expand the dir path to also accept files #27

Merged
merged 2 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ 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",
help = "Used to retrieve all classes with that pattern"
)]
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,
}
45 changes: 41 additions & 4 deletions src/joneslib/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@ static INHERITANCE_PATTERN: &str = r"<Inherit>\s\[(.*?)\]";
static OUTPUT_PATTERN: &str = r"<Output> (\w+)";

/// Loads all objects from a Python project, given through the python project path.
pub fn load_python_project(project_path: &PathBuf) -> Option<Vec<(String, String)>> {
pub fn load_python_project(path: &PathBuf) -> Option<Vec<(String, String)>> {
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);
Expand Down Expand Up @@ -129,6 +136,36 @@ for root, dirs, files in os.walk({:?}):
}
}

#[inline]
fn run_python_single_file_script(file_path: &PathBuf) -> Option<Vec<u8>> {
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("<Class> %s, <File> %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<Vec<u8>> {
let python_script = format!(
Expand Down
70 changes: 51 additions & 19 deletions src/joneslib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -49,44 +49,51 @@ 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<objects::PythonClass> {
pub fn fetch_object_details(path: &PathBuf, class_name: &String) -> Option<objects::PythonClass> {
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<objects::PythonClass> {
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;
}

/// Loads the project classes and filters them by the class name. Returns a vector
/// of tuples containing the class name and the file path.
pub fn search(dir_path: &PathBuf, class_name: &String) -> Option<Vec<ClassMatch>> {
let project_classes = match loader::load_python_project(dir_path) {
pub fn search(path: &PathBuf, class_name: &String) -> Option<Vec<ClassMatch>> {
let project_classes = match loader::load_python_project(path) {
Some(classes) => classes,
None => {
println!("Error occurred while loading project classes");
Expand All @@ -104,7 +111,7 @@ pub fn search(dir_path: &PathBuf, class_name: &String) -> Option<Vec<ClassMatch>

#[cfg(test)]
mod tests {
use super::project_traversal;
use super::fetch_object_details;
use std::fs;
use std::path::PathBuf;

Expand Down Expand Up @@ -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");
}
}
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
Expand Down
Loading