use std::path::{Path, PathBuf}; use std::process::Command; #[derive(Debug, PartialEq)] pub enum ProjectType { Maven, Npm, Cargo } impl ProjectType { fn detect>(path: P) -> Option { let project_path: PathBuf = path.as_ref().into(); if project_path.join("pom.xml").is_file() { return Some(ProjectType::Maven); } else if project_path.join("package.json").is_file() { return Some(ProjectType::Npm); } else if project_path.join("Cargo.toml").is_file() { return Some(ProjectType::Cargo); } None } pub fn build_command(&self) -> Vec { match self { ProjectType::Maven => vec!["mvn".to_owned(), "install".to_owned()], ProjectType::Npm => vec!["npm".to_owned(), "build".to_owned()], ProjectType::Cargo => vec!["cargo".to_owned(), "build".to_owned()] } } pub fn run_command(&self) -> Vec { match self { ProjectType::Maven => vec!["mvn".to_owned(), "exec".to_owned()], ProjectType::Npm => vec!["npm".to_owned(), "start".to_owned()], ProjectType::Cargo => vec!["cargo".to_owned(), "run".to_owned()] } } } #[derive(Debug)] pub struct Project { pub project_type: ProjectType, path: PathBuf } impl Project { pub fn path(&self) -> &Path { &self.path } pub fn info(&self) -> ProjectInfo { match self.project_type { ProjectType::Maven => { let effective_pom = self.read_effective_pom(); ProjectInfo::from_pom(&effective_pom) }, ProjectType::Npm => ProjectInfo { name: "placeholder".into(), version: "placeholder".into() }, ProjectType::Cargo => self.read_cargo_metadata().into() } } fn read_cargo_metadata(&self) -> cargo_metadata::Metadata { cargo_metadata::MetadataCommand::new() .current_dir(&self.path) .exec() .expect("metadata call failed") } fn read_effective_pom(&self) -> sxd_document::Package { let full_output = std::str::from_utf8(&Command::new("mvn") .arg("help:effective-pom") .current_dir(&self.path) .output() .expect("failed to generate effective pom") .stdout).expect("failed to generate string from output").to_owned(); let xml = full_output.get( full_output.find("").expect("failed to find end of xml") + 10 ).expect("failed to extract xml body"); sxd_document::parser::parse(xml).expect("failed to parse effective pom") } } #[derive(Debug)] pub struct ProjectInfo { pub name: String, pub version: String } impl From for ProjectInfo { fn from(metadata: cargo_metadata::Metadata) -> Self { let root_package_id = metadata.resolve .expect("non-existent resolve field") .root .expect("non-existent root package id"); let package = metadata.packages.into_iter() .find(|package| package.id == root_package_id) .expect("failed to find package in metadata"); ProjectInfo { name: package.name.into(), version: format!("{}", package.version) } } } impl ProjectInfo { fn from_pom(pom: &sxd_document::Package) -> Self { let document = pom.as_document(); let mut context = sxd_xpath::context::Context::new(); context.set_namespace("mvn", "http://maven.apache.org/POM/4.0.0"); let factory = sxd_xpath::Factory::new(); let name_xpath = factory.build("/mvn:project/mvn:name") .expect("failed to compile name xpath") .expect("No XPath was compiled"); let version_xpath = factory.build("/mvn:project/mvn:version") .expect("failed to compile name xpath") .expect("No XPath was compiled"); ProjectInfo { name: name_xpath.evaluate(&context, document.root()) .expect("failed to evaluate name expression") .into_string(), version: version_xpath.evaluate(&context, document.root()) .expect("failed to evaluate version expression") .into_string(), } } } pub struct Scouter { search_path: PathBuf } impl Scouter { pub fn new>(path: P) -> Scouter { let mut search_path: PathBuf = path.as_ref().into(); if !search_path.is_dir() { search_path.pop(); } Scouter { search_path: search_path } } } impl Iterator for Scouter { type Item = Project; fn next(&mut self) -> Option { loop { let project_type = ProjectType::detect(&self.search_path); if project_type.is_some() { return project_type.map(|project_type| Project { project_type: project_type, path: self.search_path.to_owned() }); } if !self.search_path.pop() { return None; } } } } #[cfg(test)] mod test { use super::{ProjectType, Scouter}; #[test] fn test_scouter() { let mut scouter = Scouter::new(file!()); let project = scouter.next().unwrap(); let project_info = project.info(); assert_eq!(ProjectType::Cargo, project.project_type); assert_eq!(env!("CARGO_PKG_NAME"), project_info.name); assert_eq!(env!("CARGO_PKG_VERSION"), project_info.version); } #[test] fn test_maven_project() { let mut scouter = Scouter::new(format!("{}/testdata/maven", env!("CARGO_MANIFEST_DIR"))); let project = scouter.next().unwrap(); let project_info = project.info(); assert_eq!(ProjectType::Maven, project.project_type); assert_eq!("Shenlong Maven Test Project", project_info.name); assert_eq!("1.0.0-SNAPSHOT", project_info.version); } }