diff --git a/src/main.rs b/src/main.rs index 6d17762..16d402f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::path::Path; -use undeepend::maven::project_parser::parse_project; +use undeepend::maven::project::parse_project; fn main() { let project = parse_project(Path::new("tests/maven/resources/sample_project")).unwrap(); - println!("{:?}", project); + println!("{:?}", project.get_dependencies(&project.root)); } diff --git a/src/maven/mod.rs b/src/maven/mod.rs index f212c95..382bbec 100644 --- a/src/maven/mod.rs +++ b/src/maven/mod.rs @@ -2,4 +2,4 @@ pub mod metadata; pub mod pom; pub mod pom_view; pub mod pom_parser; -pub mod project_parser; +pub mod project; diff --git a/src/maven/pom.rs b/src/maven/pom.rs index a20319d..3636ee5 100644 --- a/src/maven/pom.rs +++ b/src/maven/pom.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::{Path, PathBuf}; /// The Maven variant to parse poms /// These structs is directly modelled after the XML because that is what strong-xml plugin requires @@ -14,7 +15,13 @@ pub struct Pom { pub dependencies: Vec, pub dependency_management: Vec, pub properties: HashMap, - pub modules: Vec, + pub module_names: Vec, + pub modules: Vec, + pub directory: PathBuf, +} + +impl Pom { + } #[derive(PartialEq, Debug)] @@ -42,13 +49,4 @@ pub struct Dependency { pub group_id: String, pub artifact_id: String, pub version: Option, -} - -#[cfg(test)] -mod test { - - use crate::maven::pom::Pom; - - #[test] - fn parse_should_not_fail() {} -} +} \ No newline at end of file diff --git a/src/maven/pom_parser.rs b/src/maven/pom_parser.rs index 2d104e9..e897e2b 100644 --- a/src/maven/pom_parser.rs +++ b/src/maven/pom_parser.rs @@ -2,6 +2,7 @@ use crate::maven::pom::{Dependency, Developer, Parent, Pom}; use crate::xml::SaxError; use crate::xml::dom_parser::{Node, get_document}; use std::collections::HashMap; +use std::path::PathBuf; pub fn get_pom(xml: impl Into) -> Result { let mut group_id = None; @@ -14,7 +15,7 @@ pub fn get_pom(xml: impl Into) -> Result { let mut dependencies = vec![]; let mut dependency_management = vec![]; let mut properties = HashMap::new(); // useless assignment... - let mut modules = vec![]; // not useless assignment... + let mut module_names = vec![]; // not useless assignment... for child in get_document(xml.into().as_str())?.root.children { match child.name.as_str() { @@ -28,7 +29,7 @@ pub fn get_pom(xml: impl Into) -> Result { "dependencies" => dependencies = get_dependencies(child), "dependencyManagement" => dependency_management = get_dependency_mgmt(child), "properties" => properties = get_properties(child), - "modules" => add_modules(child, &mut modules), + "modules" => add_modules(child, &mut module_names), _ => {} } } @@ -43,7 +44,9 @@ pub fn get_pom(xml: impl Into) -> Result { dependencies, dependency_management, properties, - modules, + module_names, + modules: vec![], + directory: PathBuf::new(), // resolved later, make optional? }) } diff --git a/src/maven/project.rs b/src/maven/project.rs new file mode 100644 index 0000000..d5148e1 --- /dev/null +++ b/src/maven/project.rs @@ -0,0 +1,139 @@ +use crate::maven::pom::{Dependency, Pom}; +use crate::maven::pom_parser::get_pom; +use regex::Regex; +use std::fs; +use std::path::Path; +use std::sync::LazyLock; + +static PROPERTY_EXPR: LazyLock = LazyLock::new(|| Regex::new(r"\$\{(.+)}").unwrap()); + +pub fn parse_project(project_dir: &Path) -> Result { + if !project_dir.is_dir() { + return Err(format!("{:?} is not a directory", project_dir)); + } + + let mut pom_file = project_dir.to_path_buf(); + pom_file.push(Path::new("pom.xml")); + + let pom_file = fs::read_to_string(pom_file).map_err(|e| e.to_string())?; + let mut root = get_pom(pom_file).map_err(|e| e.to_string())?; + + resolve_modules(project_dir, &mut root); + Ok(Project { root }) +} + +fn resolve_modules(project_dir: &Path, pom: &mut Pom) { + let mut modules = pom + .module_names + .iter() + .map(|module| read_module_pom(project_dir, module)) + .collect(); + for module in &mut modules { + resolve_modules(project_dir, module); + } + pom.modules.append(&mut modules); +} + +fn read_module_pom(project_dir: &Path, module: &String) -> Pom { + let mut module_dir = project_dir.to_path_buf(); + module_dir.push(Path::new(module)); + let mut module_file = module_dir.clone(); + module_file.push(Path::new("pom.xml")); + let module_pom = + fs::read_to_string(module_file).expect(format!("Cannot read file {}", module).as_str()); + + let mut pom = + get_pom(module_pom).expect(format!("Cannot create module pom {}", module).as_str()); + pom.directory = module_dir; + pom +} + +#[derive(Debug)] +pub struct Project { + pub root: Pom, +} + +impl Project { + pub fn get_dependencies(&self, pom: &Pom) -> Vec { + pom.dependencies + .iter() + .map(|dependency| { + let version = self.get_version(pom, &dependency.group_id, &dependency.artifact_id); + Dependency { + group_id: dependency.group_id.clone(), + artifact_id: dependency.artifact_id.clone(), + version, + } + }) + .collect() + } + + fn get_version(&self, pom: &Pom, group_id: &str, artifact_id: &str) -> Option { + pom.dependencies + .iter() + .find(|d| d.group_id == group_id && d.artifact_id == artifact_id) + .and_then(|d| d.version.clone()) + .and_then(|version| { + if PROPERTY_EXPR.is_match(&version) { + let property_name = &PROPERTY_EXPR.captures(&version).unwrap()[1]; + self.get_property(pom, property_name) + } else { + Some(version) + } + }) + .or_else(|| { + pom.dependency_management + .iter() + .find(|d| d.group_id == group_id && d.artifact_id == artifact_id) + .and_then(|d| d.version.clone()) + .and_then(|version| { + if PROPERTY_EXPR.is_match(&version) { + let property_name = &PROPERTY_EXPR.captures(&version).unwrap()[1]; + self.get_property(pom, property_name) + } else { + Some(version) + } + }) + }) + } + + fn get_property(&self, pom: &Pom, name: &str) -> Option { + if pom.properties.contains_key(name) { + pom.properties.get(name).cloned() + } else if let Some(parent) = &pom.parent { + if let Some(parent_pom) = self.get_pom(&parent.group_id, &parent.artifact_id) { + self.get_property(parent_pom, name) + } else { + None + } + } else { + None + } + } + + fn get_pom<'a>(&'a self, group_id: &str, artifact_id: &str) -> Option<&'a Pom> { + fn get_project_pom<'a>(pom: &'a Pom, group_id: &str, artifact_id: &str) -> Option<&'a Pom> { + if is_same(pom, group_id, artifact_id) { + return Some(pom); + } else { + for module in &pom.modules { + return get_project_pom(module, group_id, artifact_id); + } + } + None + } + get_project_pom(&self.root, group_id, artifact_id) + } +} + +fn is_same(pom: &Pom, group_id: &str, artifact_id: &str) -> bool { + if pom.artifact_id == artifact_id { + if let Some(pom_group_id) = &pom.group_id { + pom_group_id == group_id + } else { + false + } + } else { + false + } +} diff --git a/src/maven/project_parser.rs b/src/maven/project_parser.rs deleted file mode 100644 index 1feee7d..0000000 --- a/src/maven/project_parser.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::maven::pom::Pom; -use crate::maven::pom_parser::get_pom; -use std::fs; -use std::path::Path; - -pub fn parse_project(project_dir: &Path) -> Result { - if !project_dir.is_dir() { - return Err(format!("{:?} is not a directory", project_dir)); - } - - let mut pom_file = project_dir.to_path_buf(); - pom_file.push(Path::new("pom.xml")); - - let pom_file = fs::read_to_string(pom_file).map_err(|e| e.to_string())?; - let root = get_pom(pom_file).map_err(|e| e.to_string())?; - - let modules= root.modules - .iter() - .map(|module| read_module_pom(project_dir, module)) - .collect(); - - Ok(Project { - root, - modules, - }) -} - -fn read_module_pom(project_dir: &Path, module: &String) -> Pom { - let mut module_file = project_dir.to_path_buf(); - module_file.push(Path::new(module)); - module_file.push(Path::new("pom.xml")); - let module_pom = fs::read_to_string(module_file) - .expect(format!("Cannot read file {}", module).as_str()); - get_pom(module_pom).expect(format!("Cannot create module pom {}", module).as_str()) -} - -#[derive(Debug)] -pub struct Project { - pub root: Pom, - pub modules: Vec, -} diff --git a/tests/maven/pom_parser_test.rs b/tests/maven/pom_parser_test.rs index 84b4a13..e5dd8fe 100644 --- a/tests/maven/pom_parser_test.rs +++ b/tests/maven/pom_parser_test.rs @@ -28,8 +28,8 @@ fn test_pom_parser_is_correct() { assert_eq!(Some("1.0".to_string()), objenesis.version); assert_eq!(2, pom.modules.len()); - assert_eq!("a", pom.modules[0]); - assert_eq!("b", pom.modules[1]); + assert_eq!("a", pom.module_names[0]); + assert_eq!("b", pom.module_names[1]); assert_eq!(1, pom.dependency_management.len()); let hamcrest = &pom.dependency_management[0]; diff --git a/tests/maven/project_parser_test.rs b/tests/maven/project_parser_test.rs index 601531e..2131bd7 100644 --- a/tests/maven/project_parser_test.rs +++ b/tests/maven/project_parser_test.rs @@ -1,3 +1,3 @@ use std::path::Path; -use undeepend::maven::project_parser::parse_project; +use undeepend::maven::project::parse_project;