Compare commits
10 commits
3b55dd3536
...
bb5517470e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb5517470e | ||
|
|
3f30afa689 | ||
|
|
ab651b9b97 | ||
|
|
935bfb4139 | ||
|
|
f6e39067c8 | ||
|
|
2deae73132 | ||
|
|
e7dee59605 | ||
|
|
25a5bde49c | ||
|
|
098cb5e0bc | ||
|
|
856961b8f5 |
22 changed files with 3123 additions and 95 deletions
1996
Cargo.lock
generated
1996
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,4 +6,7 @@ edition = "2024"
|
|||
[dependencies]
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
regex="1.11"
|
||||
regex="1.11"
|
||||
maud = "*"
|
||||
zip = "4.3"
|
||||
reqwest = { version = "0.12", features = ["blocking", "rustls-tls"] }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ COPY . .
|
|||
RUN cargo install --path .
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
RUN apt-get update && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /usr/local/cargo/bin/undeepend /usr/local/bin/undeepend
|
||||
CMD ["undeepend"]
|
||||
|
||||
|
|
|
|||
41
README.md
41
README.md
|
|
@ -1,15 +1,48 @@
|
|||
currently implementing in rust:
|
||||
**currently implementing in rust:**
|
||||
* V a sax parser to read xml files (and existing xml binding in rust has trouble reading maven properties)
|
||||
* V a dom parser to get a generic xml representation
|
||||
* V a pom reader to get a maven specific representation
|
||||
* V to find out what dependencies you have
|
||||
* try default localRepository ~/.m2/repository
|
||||
* V try default localRepository ~/.m2/repository
|
||||
* load settings.xml
|
||||
* search dependency in localRepository
|
||||
* download dependency from remote repo's
|
||||
* V search dependency in localRepository
|
||||
* V download dependency from remote repo's
|
||||
|
||||
Why rust and not a maven plugin?
|
||||
* faster
|
||||
* more challenges
|
||||
* run it in docker as a separate step
|
||||
|
||||
|
||||
* report in html
|
||||
* list dependencies in descending 'should-I-use-it-score' order (below)
|
||||
* drill down to code usage in project
|
||||
|
||||
**gradle**
|
||||
* probably easiest to run gradle itself to get the dependency list
|
||||
* maybe should've done that with maven as well...
|
||||
* but currently it's working rather well (as a POC, it's still missing essential features)
|
||||
|
||||
**elaborating**
|
||||
* deciding if you should ditch a dependency, likely involves other factors:
|
||||
* (dependency) project quality, as defined by:
|
||||
* date of last commit
|
||||
* date of highest version on mavencentral
|
||||
* java version in bytecode (pre/post java11, I would say)
|
||||
* nr of collaborators
|
||||
* nr of issues (ratio open/solved vs total)
|
||||
* nr of superseded transitive dependencies
|
||||
* reported vulnerabilities
|
||||
* in some weighted sum(s), yielding a 'should-I-use-it score'
|
||||
* and replaceability score: how much work to replace it
|
||||
* how many occurrences of usage?c
|
||||
* lib or framework?
|
||||
* this is going to be a large database,
|
||||
* incrementally populated with data
|
||||
* what stack?
|
||||
|
||||
**Another idea**
|
||||
* compute amount of (dependency) code that is reachable from the application
|
||||
* count references (traverse all)
|
||||
* what to do with dynamically loaded code?
|
||||
|
||||
24
index.html
Normal file
24
index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html><html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Project Dependencies</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body><h1>Project Dependencies</h1><table><thead><tr><th>Group ID</th><th>Artifact ID</th><th>Version</th></tr></thead><tbody></tbody></table></body></html>
|
||||
17
main.rs
17
main.rs
|
|
@ -1,17 +0,0 @@
|
|||
fn main() {
|
||||
println!("Hello, Rust!");
|
||||
|
||||
// Example: Simple calculation
|
||||
let x = 5;
|
||||
let y = 10;
|
||||
let sum = x + y;
|
||||
|
||||
println!("The sum of {} and {} is {}", x, y, sum);
|
||||
|
||||
// Example: Vector operations
|
||||
let numbers = vec![1, 2, 3, 4, 5];
|
||||
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
|
||||
|
||||
println!("Original: {:?}", numbers);
|
||||
println!("Doubled: {:?}", doubled);
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod maven;
|
||||
pub mod xml;
|
||||
mod report;
|
||||
20
src/main.rs
20
src/main.rs
|
|
@ -1,12 +1,24 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
use undeepend::maven::project::parse_project;
|
||||
use undeepend::maven::reporter::report;
|
||||
use undeepend::maven::settings::get_settings;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
let dir = if args.len() ==1 {
|
||||
let dir = if args.len() == 1 {
|
||||
env::current_dir().expect("Could not access current directory")
|
||||
} else { PathBuf::from(&args[1]) };
|
||||
} else {
|
||||
PathBuf::from(&args[1])
|
||||
};
|
||||
|
||||
let project = parse_project(&dir).unwrap();
|
||||
println!("{:?}", project.get_dependencies(&project.root));
|
||||
|
||||
fs::write(
|
||||
PathBuf::from("index.html"),
|
||||
project.generate_dependency_html(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
report(&project);
|
||||
}
|
||||
|
|
|
|||
79
src/maven/common_model.rs
Normal file
79
src/maven/common_model.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::xml::dom_parser::Node;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Repository {
|
||||
pub releases: Option<RepositoryPolicy>,
|
||||
pub snapshots: Option<RepositoryPolicy>,
|
||||
pub id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub url: Option<String>,
|
||||
pub layout: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RepositoryPolicy {
|
||||
pub enabled: bool,
|
||||
pub update_policy: Option<String>,
|
||||
pub checksum_policy: Option<String>,
|
||||
}
|
||||
|
||||
pub fn get_repositories(element: Node) -> Vec<Repository> {
|
||||
let mut repositories = vec![];
|
||||
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"repository" => repositories.push(get_repository(child)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
repositories
|
||||
}
|
||||
|
||||
fn get_repository(element: Node) -> Repository {
|
||||
let mut releases = None;
|
||||
let mut snapshots = None;
|
||||
let mut id = None;
|
||||
let mut name = None;
|
||||
let mut url = None;
|
||||
let mut layout = "default".to_owned();
|
||||
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"releases" => releases = Some(get_update_policy(child)),
|
||||
"snapshots" => snapshots = Some(get_update_policy(child)),
|
||||
"id" => id = child.text,
|
||||
"name" => name = child.text,
|
||||
"url" => url = child.text,
|
||||
"layout" => layout = child.text.unwrap_or("default".to_owned()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Repository {
|
||||
releases,
|
||||
snapshots,
|
||||
id,
|
||||
name,
|
||||
url,
|
||||
layout,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_update_policy(element: Node) -> RepositoryPolicy {
|
||||
let mut enabled = true;
|
||||
let mut update_policy = None;
|
||||
let mut checksum_policy = None;
|
||||
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"enabled" => enabled = child.text.map(|b| b == "true").unwrap_or(true),
|
||||
"update_policy" => update_policy = child.text,
|
||||
"checksum_policy" => checksum_policy = child.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
RepositoryPolicy {
|
||||
enabled,
|
||||
update_policy,
|
||||
checksum_policy,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,15 @@
|
|||
use std::{env, sync::LazyLock};
|
||||
|
||||
pub mod common_model;
|
||||
pub mod metadata;
|
||||
pub mod pom;
|
||||
pub mod pom_parser;
|
||||
pub mod project;
|
||||
pub mod reporter;
|
||||
pub mod settings;
|
||||
|
||||
pub const HOME: LazyLock<String> = LazyLock::new(|| env::var("HOME").unwrap());
|
||||
pub const MAVEN_HOME: LazyLock<String> =
|
||||
LazyLock::new(|| env::var("MAVEN_HOME").unwrap_or("".to_string()));
|
||||
pub const CUSTOM_SETTINGS_LOCATION: LazyLock<String> =
|
||||
LazyLock::new(|| env::var("SETTINGS_PATH").unwrap_or("".to_string()));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// the maven object model
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct Pom {
|
||||
pub parent: Option<Parent>,
|
||||
pub group_id: Option<String>,
|
||||
|
|
@ -18,11 +19,10 @@ pub struct Pom {
|
|||
pub module_names: Vec<String>,
|
||||
pub modules: Vec<Pom>,
|
||||
pub directory: PathBuf,
|
||||
pub repositories: Vec<Repository>,
|
||||
}
|
||||
|
||||
impl Pom {
|
||||
|
||||
}
|
||||
impl Pom {}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct License {
|
||||
|
|
@ -49,4 +49,55 @@ pub struct Dependency {
|
|||
pub group_id: String,
|
||||
pub artifact_id: String,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
// pub scope: Option<String>, // TODO need this?
|
||||
}
|
||||
|
||||
impl Dependency {
|
||||
/// returns a relative path to the dependency location
|
||||
pub fn to_jar_path(&self) -> PathBuf {
|
||||
let mut path = PathBuf::new();
|
||||
path.push(self.group_id.replace(".", "/"));
|
||||
path.push(&self.artifact_id);
|
||||
let version = self.version.clone().unwrap_or_else(|| "latest".to_string());
|
||||
path.push(&version);
|
||||
path.push(format!("{}-{}.jar", &self.artifact_id, &version));
|
||||
path
|
||||
// why is the version (in the filename) wrong when I use PathBuf::set_extension("jar") ???
|
||||
}
|
||||
|
||||
/// returns an absolute path based on the default maven localRepository location
|
||||
// useful?
|
||||
pub fn to_absolute_jar_path(&self) -> PathBuf {
|
||||
let mut absolute_path = PathBuf::from(HOME.as_str());
|
||||
absolute_path.push(".m2/repository");
|
||||
absolute_path.push(self.to_jar_path());
|
||||
absolute_path
|
||||
}
|
||||
|
||||
pub fn is_snapshot(&self) -> bool {
|
||||
self.version
|
||||
.as_ref()
|
||||
.map(|v| v.ends_with("SNAPSHOT"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::maven::HOME;
|
||||
use crate::maven::common_model::Repository;
|
||||
|
||||
impl Display for Dependency {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let version = self.version.clone().unwrap_or_else(|| "latest".to_string());
|
||||
write!(
|
||||
f,
|
||||
"{}/{}/{}/{}-{}",
|
||||
self.group_id.replace(".", "/"),
|
||||
self.artifact_id,
|
||||
version,
|
||||
self.artifact_id,
|
||||
version
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::maven::common_model::get_repositories;
|
||||
use crate::maven::pom::{Dependency, Developer, Parent, Pom};
|
||||
use crate::xml::SaxError;
|
||||
use crate::xml::dom_parser::{Node, get_document};
|
||||
|
|
@ -5,9 +6,9 @@ use std::collections::HashMap;
|
|||
use std::path::PathBuf;
|
||||
|
||||
/// parse the pom.xml into a Pom object (struct)
|
||||
pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
|
||||
pub fn get_pom(home_dir: PathBuf, xml: impl Into<String>) -> Result<Pom, SaxError> {
|
||||
let mut group_id = None;
|
||||
let mut artefact_id = None;
|
||||
let mut artifact_id = None;
|
||||
let mut parent = None;
|
||||
let mut version = None;
|
||||
let mut name = None;
|
||||
|
|
@ -17,11 +18,12 @@ pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
|
|||
let mut dependency_management = vec![];
|
||||
let mut properties = HashMap::new(); // useless assignments...
|
||||
let mut module_names = vec![]; // not useless assignment...
|
||||
let mut repositories = vec![]; // not useless assignment...
|
||||
|
||||
for child in get_document(xml.into().as_str())?.root.children {
|
||||
match child.name.as_str() {
|
||||
"groupId" => group_id = child.text,
|
||||
"artifactId" => artefact_id = child.text,
|
||||
"artifactId" => artifact_id = child.text,
|
||||
"parent" => parent = Some(get_parent(&child)),
|
||||
"version" => version = child.text,
|
||||
"name" => name = child.text,
|
||||
|
|
@ -31,13 +33,20 @@ pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
|
|||
"dependencyManagement" => dependency_management = get_dependency_mgmt(child),
|
||||
"properties" => properties = get_properties(child),
|
||||
"modules" => add_modules(child, &mut module_names),
|
||||
"repositories" => repositories = get_repositories(child),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO before returning, calculate all
|
||||
// * dependency versions
|
||||
// * repositories
|
||||
// maybe put that in a separate model struct
|
||||
|
||||
Ok(Pom {
|
||||
parent,
|
||||
group_id,
|
||||
artifact_id: artefact_id.unwrap(),
|
||||
artifact_id: artifact_id.unwrap(),
|
||||
version,
|
||||
name,
|
||||
packaging,
|
||||
|
|
@ -47,11 +56,12 @@ pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
|
|||
properties,
|
||||
module_names,
|
||||
modules: vec![],
|
||||
directory: PathBuf::new(), // resolved later, make optional?
|
||||
directory: home_dir,
|
||||
repositories,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_modules(element: Node, modules: &mut Vec<String>){
|
||||
fn add_modules(element: Node, modules: &mut Vec<String>) {
|
||||
for module in element.children {
|
||||
modules.push(module.text.expect("Cannot read module name"));
|
||||
}
|
||||
|
|
@ -91,19 +101,19 @@ fn get_dependencies(element: Node) -> Vec<Dependency> {
|
|||
|
||||
fn get_dependency(element: Node) -> Dependency {
|
||||
let mut grouo_id = None;
|
||||
let mut artefact_id = None;
|
||||
let mut artifact_id = None;
|
||||
let mut version = None;
|
||||
for node in element.children {
|
||||
match node.name.as_str() {
|
||||
"groupId" => grouo_id = node.text,
|
||||
"artifactId" => artefact_id = node.text,
|
||||
"artifactId" => artifact_id = node.text,
|
||||
"version" => version = node.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Dependency {
|
||||
group_id: grouo_id.unwrap(),
|
||||
artifact_id: artefact_id.unwrap(),
|
||||
artifact_id: artifact_id.unwrap(),
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
|
@ -136,19 +146,19 @@ fn get_developer(element: Node) -> Developer {
|
|||
|
||||
fn get_parent(element: &Node) -> Parent {
|
||||
let mut group_id = None;
|
||||
let mut artefact_id = None;
|
||||
let mut artifact_id = None;
|
||||
let mut version = None;
|
||||
for child in &element.children {
|
||||
match child.name.as_str() {
|
||||
"groupId" => group_id = child.text.clone(),
|
||||
"artefactId" => artefact_id = child.text.clone(),
|
||||
"artifactId" => artifact_id = child.text.clone(),
|
||||
"version" => version = child.text.clone(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Parent {
|
||||
group_id: group_id.unwrap(),
|
||||
artifact_id: artefact_id.unwrap(),
|
||||
artifact_id: artifact_id.unwrap(),
|
||||
version: version.unwrap(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
use crate::maven::common_model::Repository;
|
||||
use crate::maven::pom::{Dependency, Pom};
|
||||
use crate::maven::pom_parser::get_pom;
|
||||
use crate::maven::settings::{Settings, get_settings};
|
||||
use regex::Regex;
|
||||
use reqwest::blocking::Client;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const MAVEN_CENTRAL: &str = "https://repo1.maven.org/maven2/";
|
||||
|
||||
static PROPERTY_EXPR: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\$\{(.+)}").unwrap());
|
||||
|
||||
/// Loads all poms from a given project directory.
|
||||
|
|
@ -18,15 +25,41 @@ pub fn parse_project(project_dir: &Path) -> Result<Project, String> {
|
|||
|
||||
let mut pom_file = project_dir.to_path_buf();
|
||||
pom_file.push(Path::new("pom.xml"));
|
||||
if !pom_file.exists(){
|
||||
return Err(format!("Directory {} does not contain pom.xml", project_dir.to_str().unwrap()));
|
||||
if !pom_file.exists() {
|
||||
return Err(format!(
|
||||
"Directory {} does not contain pom.xml",
|
||||
project_dir.to_str().unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
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())?;
|
||||
let mut root = get_pom(project_dir.to_path_buf(), pom_file).map_err(|e| e.to_string())?;
|
||||
|
||||
resolve_modules(project_dir, &mut root);
|
||||
Ok(Project { root })
|
||||
let project_home = project_dir.to_str().unwrap_or_else(|| "?").to_string();
|
||||
let settings = get_settings()?;
|
||||
|
||||
let mut project = Project {
|
||||
settings,
|
||||
project_home,
|
||||
root,
|
||||
repositories: vec![],
|
||||
};
|
||||
|
||||
let repositories = project.get_repositories();
|
||||
project.repositories = repositories; // well this is convoluted
|
||||
|
||||
for pom in &project.root.modules {
|
||||
for dep in &project.get_dependencies(pom) {
|
||||
let path = PathBuf::from(dep.to_absolute_jar_path());
|
||||
if !path.exists() {
|
||||
project
|
||||
.download(dep)
|
||||
.expect(&format!("Can't download jar file {}", dep));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
// examines modules in pom and loads them
|
||||
|
|
@ -51,21 +84,23 @@ fn read_module_pom(project_dir: &Path, module: &String) -> Pom {
|
|||
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
|
||||
get_pom(module_dir, module_pom).expect(format!("Cannot create module pom {}", module).as_str())
|
||||
}
|
||||
|
||||
//main entry to project
|
||||
//the (root) pom holds the child references to modules
|
||||
#[derive(Debug)]
|
||||
pub struct Project {
|
||||
pub settings: Settings,
|
||||
pub project_home: String,
|
||||
pub root: Pom,
|
||||
pub repositories: Vec<Repository>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
/// get a list of dependencies for a pom in the project
|
||||
///
|
||||
/// Note to self: maybe calculating the versions should be done earlier
|
||||
pub fn get_dependencies(&self, pom: &Pom) -> Vec<Dependency> {
|
||||
pom.dependencies
|
||||
.iter()
|
||||
|
|
@ -92,52 +127,127 @@ impl Project {
|
|||
.find(|d| d.group_id == group_id && d.artifact_id == artifact_id)
|
||||
// extract the version
|
||||
.and_then(|d| d.version.clone())
|
||||
// is it a property?
|
||||
.and_then(|version| {
|
||||
if PROPERTY_EXPR.is_match(&version) {
|
||||
let property_name = &PROPERTY_EXPR.captures(&version).unwrap()[1];
|
||||
// search property in project hierarchy
|
||||
self.get_property(pom, property_name)
|
||||
} else {
|
||||
Some(version)
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
// version not set, try dependencyManagement
|
||||
// TODO also search super poms
|
||||
pom.dependency_management
|
||||
self.collect_managed_dependencies(pom, group_id, artifact_id)
|
||||
.iter()
|
||||
.find(|d| d.group_id == group_id && d.artifact_id == artifact_id)
|
||||
.find(|d| d.version.is_some())
|
||||
.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)
|
||||
}
|
||||
})
|
||||
})
|
||||
.and_then(|v| {
|
||||
if PROPERTY_EXPR.is_match(v.as_str()) {
|
||||
let property_name = &PROPERTY_EXPR.captures(&v).unwrap()[1];
|
||||
// search property in project hierarchy
|
||||
self.get_property(pom, property_name).ok()
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn download(&self, dep: &Dependency) -> Result<(), String> {
|
||||
// self.repositories has all repos,
|
||||
// but mirrors are not yet taken into account
|
||||
|
||||
let url = format!("{}{}.jar", MAVEN_CENTRAL, dep);
|
||||
|
||||
let client = Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
println!("Downloading {}", &url);
|
||||
let response = client
|
||||
.get(&url)
|
||||
.header("User-Agent", "Maven/1.0")
|
||||
.send()
|
||||
.map_err(|e| e.to_string())?;
|
||||
if response.status().is_success() {
|
||||
let bytes = response.bytes().map_err(|e| e.to_string())?;
|
||||
let mut buf_writer = BufWriter::new(
|
||||
File::create(dep.to_absolute_jar_path()).map_err(|e| e.to_string())?,
|
||||
);
|
||||
|
||||
buf_writer.write_all(&bytes).map_err(|e| e.to_string())?;
|
||||
buf_writer.flush().map_err(|e| e.to_string())?;
|
||||
println!("Downloaded {}", &url);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_repositories(&self) -> Vec<Repository> {
|
||||
let mut repositories = vec![];
|
||||
for pom in &self.root.modules {
|
||||
repositories.append(&mut pom.repositories.to_vec());
|
||||
}
|
||||
self.add_repositories(&self.root, &mut repositories);
|
||||
repositories.append(&mut self.settings.get_repositories().to_vec());
|
||||
repositories
|
||||
}
|
||||
|
||||
fn add_repositories(&self, pom: &Pom, repositories: &mut Vec<Repository>) {
|
||||
repositories.append(&mut pom.repositories.to_vec());
|
||||
if let Some(parent) = &pom.parent {
|
||||
if let Some(parent_pom) = self.get_pom(&parent.group_id, &parent.artifact_id) {
|
||||
self.add_repositories(parent_pom, repositories);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// searches in managed_dependencies for dependencies
|
||||
fn collect_managed_dependencies<'a>(
|
||||
&self,
|
||||
pom: &'a Pom,
|
||||
group_id: &str,
|
||||
artifact_id: &str,
|
||||
) -> Vec<Dependency> {
|
||||
fn collect<'a>(
|
||||
project: &'a Project,
|
||||
pom: &'a Pom,
|
||||
deps: &mut Vec<Dependency>,
|
||||
group_id: &str,
|
||||
artifact_id: &str,
|
||||
) {
|
||||
deps.append(
|
||||
&mut pom
|
||||
.dependency_management
|
||||
.iter()
|
||||
.filter(|d| d.group_id == group_id && d.artifact_id == artifact_id)
|
||||
.map(|d| d.clone())
|
||||
.collect::<Vec<Dependency>>(),
|
||||
);
|
||||
if let Some(parent) = &pom.parent {
|
||||
if let Some(parent_pom) = project.get_pom(&parent.group_id, &parent.artifact_id) {
|
||||
collect(project, parent_pom, deps, group_id, artifact_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dependencies = Vec::new();
|
||||
collect(self, pom, &mut dependencies, group_id, artifact_id);
|
||||
dependencies
|
||||
}
|
||||
|
||||
// recursively searches a property going up the chain towards parents
|
||||
fn get_property(&self, pom: &Pom, name: &str) -> Option<String> {
|
||||
fn get_property(&self, pom: &Pom, name: &str) -> Result<String, String> {
|
||||
if pom.properties.contains_key(name) {
|
||||
pom.properties.get(name).cloned()
|
||||
pom.properties
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or(format!("Unknown property {}", name))
|
||||
} 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
|
||||
Err(format!("Unknown property {}", name))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
Err(format!("Unknown property {}", name))
|
||||
}
|
||||
}
|
||||
|
||||
// look up a pom in the project
|
||||
fn get_pom<'a>(&'a self, group_id: &str, artifact_id: &str) -> Option<&'a Pom> {
|
||||
|
||||
// inner function to match poms (by artifactId and groupId)
|
||||
// (extract if needed elsewhere)
|
||||
fn is_same(pom: &Pom, group_id: &str, artifact_id: &str) -> bool {
|
||||
|
|
@ -166,5 +276,36 @@ impl Project {
|
|||
|
||||
get_project_pom(&self.root, group_id, artifact_id)
|
||||
}
|
||||
|
||||
pub fn iter<'a>(&'a self) -> PomIterator<'a> {
|
||||
PomIterator {
|
||||
project: self,
|
||||
idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PomIterator<'a> {
|
||||
project: &'a Project,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> PomIterator<'a> {
|
||||
pub fn new(project: &'a Project) -> Self {
|
||||
PomIterator { project, idx: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PomIterator<'a> {
|
||||
type Item = &'a Pom;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.idx < self.project.root.modules.len() {
|
||||
let module = &self.project.root.modules[self.idx];
|
||||
self.idx += 1;
|
||||
Some(module)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
71
src/maven/reporter.rs
Normal file
71
src/maven/reporter.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use crate::maven::pom::{Dependency, Pom};
|
||||
use crate::maven::project::Project;
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::LazyLock;
|
||||
use zip::ZipArchive;
|
||||
|
||||
static CLASS_EXPR: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(.+)/.+\.class").unwrap());
|
||||
|
||||
// TODO should not be downloading dependencies
|
||||
pub fn report(project: &Project) {
|
||||
let pom = &project.root; // TODO other modules
|
||||
for dep in &project.get_dependencies(pom) {
|
||||
let jar_file = File::open(dep.to_absolute_jar_path()).expect("Can't open jar file");
|
||||
let mut archive = ZipArchive::new(jar_file).expect("Can't read jar file");
|
||||
|
||||
let mut packages = HashSet::new();
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let file = archive.by_index(i).expect("Can't read file");
|
||||
let name = file.name();
|
||||
if CLASS_EXPR.is_match(name) {
|
||||
let package = &CLASS_EXPR.captures(name).unwrap()[1];
|
||||
packages.insert(package.replace("/", ".").to_string());
|
||||
}
|
||||
}
|
||||
|
||||
analyse_source(&packages, &new_path(&pom.directory, "src/main/java"));
|
||||
analyse_source(&packages, &new_path(&pom.directory, "src/test/java")); //TODO other src dirs, generated src
|
||||
}
|
||||
}
|
||||
|
||||
fn new_path(dir: &PathBuf, child: &str) -> PathBuf {
|
||||
let mut new_dir = dir.clone();
|
||||
new_dir.push(child);
|
||||
new_dir
|
||||
}
|
||||
|
||||
fn analyse_source(packages: &HashSet<String>, dir: &Path) {
|
||||
if dir.exists() {
|
||||
for entry in dir.read_dir().unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
analyse_source(packages, &path);
|
||||
} else {
|
||||
if path.extension().unwrap() == "java" {
|
||||
analyse(packages, &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO deal with import wildcards
|
||||
fn analyse(packages: &HashSet<String>, path: &Path) {
|
||||
let content = std::fs::read_to_string(path).unwrap();
|
||||
let lines = content.lines();
|
||||
for line in lines {
|
||||
if line.contains("import") {
|
||||
for package in packages {
|
||||
if line.contains(package) {
|
||||
println!("{:?}: {}", path, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
475
src/maven/settings.rs
Normal file
475
src/maven/settings.rs
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
use std::{fs, path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
maven::{
|
||||
CUSTOM_SETTINGS_LOCATION, HOME, MAVEN_HOME,
|
||||
common_model::{Repository, get_repositories},
|
||||
},
|
||||
xml::dom_parser::{Node, get_document},
|
||||
};
|
||||
|
||||
pub fn get_settings() -> Result<Settings, String> {
|
||||
let settings_path = get_settings_path().map_err(|e| e.to_string())?;
|
||||
get_settings_from_path(settings_path)
|
||||
}
|
||||
|
||||
pub fn get_settings_from_path(settings_path: PathBuf) -> Result<Settings, String> {
|
||||
let settings = fs::read_to_string(settings_path).map_err(|e| e.to_string())?;
|
||||
get_settings_from_string(settings)
|
||||
}
|
||||
|
||||
pub fn get_settings_from_string(settings: String) -> Result<Settings, String> {
|
||||
let mut local_repository = None;
|
||||
let mut interactive_mode = true;
|
||||
let mut use_plugin_registry = false;
|
||||
let mut offline = false;
|
||||
let mut proxies = vec![];
|
||||
let mut servers = vec![];
|
||||
let mut mirrors = vec![];
|
||||
let mut profiles = vec![];
|
||||
let mut active_profiles = vec![];
|
||||
let mut plugin_groups = vec![];
|
||||
|
||||
let root = get_document(settings).map_err(|err| err.to_string())?.root;
|
||||
for child in root.children {
|
||||
match child.name.as_str() {
|
||||
"localRepository" => local_repository = child.text,
|
||||
"interactiveMode" => interactive_mode = child.text.map(|b| b == "true").unwrap_or(true),
|
||||
"usePluginRegistry" => {
|
||||
use_plugin_registry = child.text.map(|b| b == "true").unwrap_or(false)
|
||||
}
|
||||
"offline" => offline = child.text.map(|b| b == "true").unwrap_or(false),
|
||||
"proxies" => proxies = get_proxies(child),
|
||||
"servers" => servers = get_servers(child),
|
||||
"mirrors" => mirrors = get_mirrors(child),
|
||||
"profiles" => profiles = get_profiles(child),
|
||||
"activeProfiles" => active_profiles = get_active_profiles(child),
|
||||
"pluginGroups" => plugin_groups = get_plugin_groups(child),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Settings {
|
||||
local_repository,
|
||||
interactive_mode,
|
||||
use_plugin_registry,
|
||||
offline,
|
||||
proxies,
|
||||
servers,
|
||||
mirrors,
|
||||
profiles,
|
||||
active_profiles,
|
||||
plugin_groups,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_proxies(element: Node) -> Vec<Proxy> {
|
||||
let mut proxies = vec![];
|
||||
for child in element.children {
|
||||
proxies.push(get_proxy(child));
|
||||
}
|
||||
proxies
|
||||
}
|
||||
|
||||
fn get_active_profiles(element: Node) -> Vec<String> {
|
||||
let mut active_profiles = vec![];
|
||||
for child in element.children {
|
||||
if let Some(active_profile) = child.text {
|
||||
active_profiles.push(active_profile);
|
||||
}
|
||||
}
|
||||
active_profiles
|
||||
}
|
||||
|
||||
fn get_plugin_groups(element: Node) -> Vec<String> {
|
||||
let mut plugin_groups = vec![];
|
||||
for child in element.children {
|
||||
if let Some(plugin_group) = child.text {
|
||||
plugin_groups.push(plugin_group);
|
||||
}
|
||||
}
|
||||
plugin_groups
|
||||
}
|
||||
|
||||
fn get_servers(servers_element: Node) -> Vec<Server> {
|
||||
let mut servers = vec![];
|
||||
for server_element in servers_element.children {
|
||||
servers.push(get_server(server_element));
|
||||
}
|
||||
servers
|
||||
}
|
||||
|
||||
fn get_mirrors(mirrors_element: Node) -> Vec<Mirror> {
|
||||
let mut mirrors = vec![];
|
||||
for mirror_element in mirrors_element.children {
|
||||
mirrors.push(get_mirror(mirror_element));
|
||||
}
|
||||
mirrors
|
||||
}
|
||||
|
||||
fn get_profiles(profiles_element: Node) -> Vec<Profile> {
|
||||
let mut profiles = vec![];
|
||||
for mirror_element in profiles_element.children {
|
||||
profiles.push(get_profile(mirror_element));
|
||||
}
|
||||
profiles
|
||||
}
|
||||
|
||||
fn get_server(server_element: Node) -> Server {
|
||||
let mut id = None;
|
||||
let mut username = None;
|
||||
let mut password = None;
|
||||
let mut private_key = None;
|
||||
let mut passphrase = None;
|
||||
let mut file_permissions = None;
|
||||
let mut directory_permissions = None;
|
||||
let mut configuration = None;
|
||||
for child in server_element.children {
|
||||
match child.name.as_str() {
|
||||
"id" => id = child.text,
|
||||
"username" => username = child.text,
|
||||
"password" => password = child.text,
|
||||
"private_key" => private_key = child.text,
|
||||
"passphrase" => passphrase = child.text,
|
||||
"filePermissions" => file_permissions = child.text,
|
||||
"directoryPermissions" => directory_permissions = child.text,
|
||||
"configuration" => configuration = Some(child),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Server {
|
||||
id,
|
||||
username,
|
||||
password,
|
||||
private_key,
|
||||
passphrase,
|
||||
file_permissions,
|
||||
directory_permissions,
|
||||
configuration,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_proxy(element: Node) -> Proxy {
|
||||
let mut active = false;
|
||||
let mut protocol = "http".to_owned();
|
||||
let mut username = None;
|
||||
let mut password = None;
|
||||
let mut port: usize = 8080;
|
||||
let mut host = None;
|
||||
let mut non_proxy_hosts = None;
|
||||
let mut id = None;
|
||||
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"active" => active = child.text.map(|b| b == "true").unwrap_or(false),
|
||||
"protocol" => protocol = child.text.unwrap_or("http".to_owned()),
|
||||
"username" => username = child.text,
|
||||
"password" => password = child.text,
|
||||
"port" => {
|
||||
port = child
|
||||
.text
|
||||
.map(|i| {
|
||||
usize::from_str(&i).expect(&format!("Illegal value for port: '{}'", i))
|
||||
})
|
||||
.unwrap_or(8080)
|
||||
}
|
||||
"host" => host = child.text,
|
||||
"non_proxy_hosts" => non_proxy_hosts = child.text,
|
||||
"id" => id = child.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Proxy {
|
||||
active,
|
||||
protocol,
|
||||
username,
|
||||
password,
|
||||
port,
|
||||
host,
|
||||
non_proxy_hosts,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mirror(mirror_element: Node) -> Mirror {
|
||||
let mut id = None;
|
||||
let mut mirror_of = None;
|
||||
let mut url = None;
|
||||
let mut name = None;
|
||||
for child in mirror_element.children {
|
||||
match child.name.as_str() {
|
||||
"id" => id = child.text,
|
||||
"mirror_of" => mirror_of = child.text,
|
||||
"url" => url = child.text,
|
||||
"name" => name = child.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Mirror {
|
||||
id,
|
||||
mirror_of,
|
||||
url,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_profile(profile_element: Node) -> Profile {
|
||||
let mut id = None;
|
||||
let mut activation = None;
|
||||
let mut properties = vec![];
|
||||
let mut repositories = vec![];
|
||||
let mut plugin_repositories = vec![];
|
||||
|
||||
for child in profile_element.children {
|
||||
match child.name.as_str() {
|
||||
"id" => id = child.text,
|
||||
"activation" => activation = Some(get_activation(child)),
|
||||
"properties" => properties.append(&mut get_properties(child)),
|
||||
"repositories" => repositories = get_repositories(child),
|
||||
"pluginRepositories" => plugin_repositories = get_repositories(child),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Profile {
|
||||
id,
|
||||
activation,
|
||||
properties,
|
||||
repositories,
|
||||
plugin_repositories,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_activation(activation_element: Node) -> Activation {
|
||||
let mut active_by_default = false;
|
||||
let mut jdk = None;
|
||||
let mut os = None;
|
||||
let mut property = None;
|
||||
let mut file = None;
|
||||
for child in activation_element.children {
|
||||
match child.name.as_str() {
|
||||
"activeByDefault" => {
|
||||
active_by_default = child.text.map(|b| b == "true").unwrap_or(false)
|
||||
}
|
||||
"jdk" => jdk = child.text,
|
||||
"os" => os = Some(get_activation_os(child)),
|
||||
"property" => property = Some(get_activation_property(child)),
|
||||
"file" => file = Some(get_activation_file(child)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Activation {
|
||||
active_by_default,
|
||||
jdk,
|
||||
os,
|
||||
property,
|
||||
file,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_properties(element: Node) -> Vec<Property> {
|
||||
let mut properties = vec![];
|
||||
for child in element.children {
|
||||
properties.push(Property {
|
||||
name: child.name,
|
||||
value: child.text,
|
||||
});
|
||||
}
|
||||
properties
|
||||
}
|
||||
|
||||
fn get_activation_os(element: Node) -> ActivationOs {
|
||||
let mut name = None;
|
||||
let mut family = None;
|
||||
let mut arch = None;
|
||||
let mut version = None;
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"name" => name = child.text,
|
||||
"family" => family = child.text,
|
||||
"arch" => arch = child.text,
|
||||
"version" => version = child.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ActivationOs {
|
||||
name,
|
||||
family,
|
||||
arch,
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_activation_property(element: Node) -> ActivationProperty {
|
||||
let mut name = None;
|
||||
let mut value = None;
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"name" => name = child.text,
|
||||
"value" => value = child.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ActivationProperty { name, value }
|
||||
}
|
||||
|
||||
fn get_activation_file(element: Node) -> ActivationFile {
|
||||
let mut missing = None;
|
||||
let mut exists = None;
|
||||
for child in element.children {
|
||||
match child.name.as_str() {
|
||||
"missing" => missing = child.text,
|
||||
"exists" => exists = child.text,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ActivationFile { missing, exists }
|
||||
}
|
||||
|
||||
fn get_settings_path() -> Result<PathBuf, String> {
|
||||
let mut settings = PathBuf::from_str(HOME.as_str()).map_err(|e| e.to_string())?;
|
||||
settings.push(".m2/settings.xml");
|
||||
if !settings.exists() {
|
||||
settings = PathBuf::from_str(MAVEN_HOME.as_str()).map_err(|e| e.to_string())?;
|
||||
settings.push("conf/settings.xml");
|
||||
}
|
||||
if !settings.exists() {
|
||||
settings =
|
||||
PathBuf::from_str(CUSTOM_SETTINGS_LOCATION.as_str()).map_err(|e| e.to_string())?;
|
||||
if settings.is_dir() {
|
||||
settings.push("settings.xml");
|
||||
}
|
||||
}
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn get_active_profiles(&self) -> Vec<&Profile> {
|
||||
self.profiles
|
||||
.iter()
|
||||
.filter(|p| {
|
||||
if let Some(activation) = &p.activation {
|
||||
activation.active_by_default //TODO other activation types are possible
|
||||
} else if let Some(id) = &p.id {
|
||||
self.active_profiles.contains(id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_repositories(&self) -> Vec<Repository> {
|
||||
self.get_active_profiles()
|
||||
.iter()
|
||||
.map(|p| &p.repositories)
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_plugin_repositories(&self) -> Vec<Repository> {
|
||||
self.get_active_profiles()
|
||||
.iter()
|
||||
.map(|p| &p.plugin_repositories)
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub local_repository: Option<String>,
|
||||
pub interactive_mode: bool,
|
||||
pub use_plugin_registry: bool,
|
||||
pub offline: bool,
|
||||
pub proxies: Vec<Proxy>,
|
||||
pub servers: Vec<Server>,
|
||||
pub mirrors: Vec<Mirror>,
|
||||
pub profiles: Vec<Profile>,
|
||||
pub active_profiles: Vec<String>,
|
||||
pub plugin_groups: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Server {
|
||||
pub id: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
pub private_key: Option<String>,
|
||||
pub passphrase: Option<String>,
|
||||
pub file_permissions: Option<String>,
|
||||
pub directory_permissions: Option<String>,
|
||||
pub configuration: Option<Node>, //xsd:any
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mirror {
|
||||
pub id: Option<String>,
|
||||
pub mirror_of: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Proxy {
|
||||
pub active: bool,
|
||||
pub protocol: String,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
pub port: usize,
|
||||
pub host: Option<String>,
|
||||
pub non_proxy_hosts: Option<String>,
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Profile {
|
||||
pub id: Option<String>,
|
||||
pub activation: Option<Activation>,
|
||||
pub properties: Vec<Property>,
|
||||
pub repositories: Vec<Repository>,
|
||||
pub plugin_repositories: Vec<Repository>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Activation {
|
||||
pub active_by_default: bool,
|
||||
pub jdk: Option<String>,
|
||||
pub os: Option<ActivationOs>,
|
||||
pub property: Option<ActivationProperty>,
|
||||
pub file: Option<ActivationFile>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActivationOs {
|
||||
pub name: Option<String>,
|
||||
pub family: Option<String>,
|
||||
pub arch: Option<String>,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActivationProperty {
|
||||
pub name: Option<String>,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActivationFile {
|
||||
pub missing: Option<String>,
|
||||
pub exists: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Property {
|
||||
pub name: String,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
58
src/report.rs
Normal file
58
src/report.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::maven::project::Project;
|
||||
use maud::{DOCTYPE, PreEscaped, html};
|
||||
|
||||
impl Project {
|
||||
pub fn generate_dependency_html(&self) -> String {
|
||||
let html = html! {
|
||||
(DOCTYPE)
|
||||
html {
|
||||
(PreEscaped(r#"
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Project Dependencies</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
"#))
|
||||
body{
|
||||
h1{"Project Dependencies"}
|
||||
table{
|
||||
thead{
|
||||
tr {
|
||||
th{"Group ID"}
|
||||
th{"Artifact ID"}
|
||||
th{"Version"}
|
||||
}
|
||||
}
|
||||
tbody{
|
||||
@for dependency in &self.get_dependencies(&self.root) {
|
||||
tr {
|
||||
td { (dependency.group_id) }
|
||||
td { (dependency.artifact_id) }
|
||||
td { (dependency.version.clone().unwrap()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
html.into_string()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ use crate::xml::sax_parser::parse_string;
|
|||
use crate::xml::{Attribute, SaxError, SaxHandler};
|
||||
|
||||
/// get a generic XML object (Document) from the xml contents. This is called DOM parsing
|
||||
pub fn get_document(xml: &str) -> Result<Document, SaxError> {
|
||||
pub fn get_document(xml: impl Into<String>) -> Result<Document, SaxError> {
|
||||
let mut dom_hax_handler = DomSaxHandler::new();
|
||||
parse_string(xml, Box::new(&mut dom_hax_handler))?;
|
||||
parse_string(&xml.into(), Box::new(&mut dom_hax_handler))?;
|
||||
|
||||
Ok(dom_hax_handler.into_doc())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ struct SAXParser<'a> {
|
|||
xml: Vec<char>,
|
||||
handler: Box<&'a mut dyn SaxHandler>,
|
||||
position: usize,
|
||||
current_line: usize,
|
||||
current: char,
|
||||
char_buffer: Vec<char>,
|
||||
namespace_stack: Vec<(String, isize)>,
|
||||
|
|
@ -35,6 +36,7 @@ impl<'a> SAXParser<'a> {
|
|||
xml: xml.chars().collect(),
|
||||
handler,
|
||||
position: 0,
|
||||
current_line: 0,
|
||||
current: '\0',
|
||||
char_buffer: Vec::new(),
|
||||
namespace_stack: Vec::new(),
|
||||
|
|
@ -44,10 +46,10 @@ impl<'a> SAXParser<'a> {
|
|||
|
||||
fn parse(&mut self) -> Result<(), SaxError> {
|
||||
self.advance()?;
|
||||
self.expect(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
||||
"Content is not allowed in prolog.",
|
||||
)?;
|
||||
// self.expect(
|
||||
// "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
||||
// "Content is not allowed in prolog.",
|
||||
// ).unwrap_or_default(); // not fatal TODO
|
||||
self.skip_whitespace()?;
|
||||
self.handler.start_document();
|
||||
self.parse_elements()
|
||||
|
|
@ -61,6 +63,12 @@ impl<'a> SAXParser<'a> {
|
|||
self.char_buffer.clear();
|
||||
}
|
||||
self.advance()?;
|
||||
if self.current == '?' {
|
||||
self.expect(
|
||||
"?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
||||
"Content is not allowed in prolog.",
|
||||
)?;
|
||||
}
|
||||
if self.current == '!' {
|
||||
self.skip_comment()?;
|
||||
} else if self.current != '/' {
|
||||
|
|
@ -111,6 +119,9 @@ impl<'a> SAXParser<'a> {
|
|||
|
||||
while c.is_whitespace() {
|
||||
self.skip_whitespace()?;
|
||||
if self.current == '/' {
|
||||
break;
|
||||
}
|
||||
atts.push(self.parse_attribute()?);
|
||||
c = self.advance()?;
|
||||
}
|
||||
|
|
@ -158,15 +169,21 @@ impl<'a> SAXParser<'a> {
|
|||
let att_name = self.read_until("=")?;
|
||||
self.skip_whitespace()?;
|
||||
self.expect("=", "Expected =")?;
|
||||
self.expect("\"", "Expected start of attribute value")?;
|
||||
self.skip_whitespace()?;
|
||||
self.expect(
|
||||
r#"""#,
|
||||
&format!(
|
||||
"Expected start of attribute value at line {}. Instead found [{}]",
|
||||
self.current_line, self.current
|
||||
),
|
||||
)?;
|
||||
let att_value = self.read_until("\"")?;
|
||||
|
||||
if att_name.starts_with("xmlns:") {
|
||||
let prefix = att_name[6..].to_string();
|
||||
self.prefix_mapping
|
||||
.insert(prefix.clone(), att_value.to_string());
|
||||
self.handler
|
||||
.start_prefix_mapping(&prefix, &att_value);
|
||||
self.handler.start_prefix_mapping(&prefix, &att_value);
|
||||
}
|
||||
|
||||
let namespace = if att_name == "xmlns" {
|
||||
|
|
@ -245,6 +262,10 @@ impl<'a> SAXParser<'a> {
|
|||
} else {
|
||||
'\0'
|
||||
};
|
||||
// print!("{}", self.current);
|
||||
if self.current == '\n' {
|
||||
self.current_line += 1;
|
||||
}
|
||||
Ok(self.current)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
mod pom_parser_test;
|
||||
mod project_parser_test;
|
||||
mod project_parser_test;
|
||||
mod settings_test;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use std::path::PathBuf;
|
||||
use undeepend::maven::pom_parser::get_pom;
|
||||
|
||||
#[test]
|
||||
fn test_pom_parser_is_correct() {
|
||||
let test_xml = include_str!("../maven/resources/pom.xml");
|
||||
let pom = get_pom(test_xml).expect("failed to get document");
|
||||
let pom = get_pom(PathBuf::from("../maven/resources"), test_xml).expect("failed to get document");
|
||||
assert_eq!(Some("Mockito".to_string()),pom.name);
|
||||
assert_eq!(Some("org.mockito".to_string()),pom.group_id);
|
||||
assert_eq!("mockito-core",pom.artifact_id);
|
||||
|
|
@ -27,7 +28,7 @@ fn test_pom_parser_is_correct() {
|
|||
assert_eq!("objenesis", objenesis.artifact_id);
|
||||
assert_eq!(Some("1.0".to_string()), objenesis.version);
|
||||
|
||||
assert_eq!(2, pom.modules.len());
|
||||
assert_eq!(2, pom.module_names.len());
|
||||
assert_eq!("a", pom.module_names[0]);
|
||||
assert_eq!("b", pom.module_names[1]);
|
||||
|
||||
|
|
|
|||
47
tests/maven/resources/settings.xml
Normal file
47
tests/maven/resources/settings.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
|
||||
http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>github</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://repo1.maven.org/maven2</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>github</id>
|
||||
<url>https://maven.pkg.github.com/shautvast/JsonToy</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>reflective</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://repo1.maven.org/maven2</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>github</id>
|
||||
<url>https://maven.pkg.github.com/shautvast/reflective</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>github</id>
|
||||
<username>shautvast</username>
|
||||
<password>foobar</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
8
tests/maven/settings_test.rs
Normal file
8
tests/maven/settings_test.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use undeepend::maven::settings::get_settings_from_string;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let settings = include_str!("../maven/resources/settings.xml").to_string();
|
||||
let settings = get_settings_from_string(settings).expect("no fail");
|
||||
assert!(!settings.profiles.is_empty());
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue