209 lines
6.5 KiB
Rust
209 lines
6.5 KiB
Rust
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum ProjectType {
|
|
Maven,
|
|
Npm,
|
|
Cargo
|
|
}
|
|
|
|
impl ProjectType {
|
|
fn detect<P: AsRef<Path>>(path: P) -> Option<ProjectType> {
|
|
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<String> {
|
|
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<String> {
|
|
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()]
|
|
}
|
|
}
|
|
|
|
pub fn install_command(&self) -> Vec<String> {
|
|
match self {
|
|
ProjectType::Maven => vec!["mvn".to_owned(), "install".to_owned()],
|
|
ProjectType::Npm => vec!["npm".to_owned(), "install".to_owned()],
|
|
ProjectType::Cargo => vec!["cargo".to_owned(), "install".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("<?xml").expect("failed to find start of xml")
|
|
..
|
|
full_output.find("</project>").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<cargo_metadata::Metadata> 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<P: AsRef<Path>>(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<Self::Item> {
|
|
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);
|
|
}
|
|
}
|