2019-02-19 00:59:08 -06:00

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);
}
}