diff --git a/index.html b/index.html new file mode 100644 index 0000000..1b09928 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + Project Dependencies + + +

Project Dependencies

Group IDArtifact IDVersion
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f53b3a8..d985f84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; -use std::env; +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::>(); @@ -11,15 +12,12 @@ fn main() { PathBuf::from(&args[1]) }; let project = parse_project(&dir).unwrap(); - // // - // // fs::write( - // // PathBuf::from("index.html"), - // // project.generate_dependency_html(), - // // ) - // // .unwrap(); - // - // report(&project); - for pom in project.iter(){ - println!("{:?}", pom); - } + get_settings().unwrap(); + fs::write( + PathBuf::from("index.html"), + project.generate_dependency_html(), + ) + .unwrap(); + + report(&project); } diff --git a/src/maven/mod.rs b/src/maven/mod.rs index aed2c31..bbb0a31 100644 --- a/src/maven/mod.rs +++ b/src/maven/mod.rs @@ -1,5 +1,14 @@ +use std::{env, sync::LazyLock}; + pub mod metadata; pub mod pom; pub mod pom_parser; pub mod project; -pub mod reporter; \ No newline at end of file +pub mod reporter; +pub mod settings; + +pub const HOME: LazyLock = LazyLock::new(|| env::var("HOME").unwrap()); +pub const MAVEN_HOME: LazyLock = + LazyLock::new(|| env::var("MAVEN_HOME").unwrap_or("".to_string())); +pub const CUSTOM_SETTINGS_LOCATION: LazyLock = + LazyLock::new(|| env::var("SETTINGS_PATH").unwrap_or("".to_string())); diff --git a/src/maven/pom.rs b/src/maven/pom.rs index 95a929e..ae9e87a 100644 --- a/src/maven/pom.rs +++ b/src/maven/pom.rs @@ -1,13 +1,9 @@ use std::collections::HashMap; -use std::env; use std::fmt::Display; use std::path::PathBuf; -use std::sync::LazyLock; /// the maven object model -const HOME: LazyLock = LazyLock::new(|| env::var("HOME").unwrap()); - #[derive(PartialEq, Debug)] pub struct Pom { pub parent: Option, @@ -80,13 +76,19 @@ impl Dependency { use std::fmt; +use crate::maven::HOME; + 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 + self.group_id.replace(".", "/"), + self.artifact_id, + version, + self.artifact_id, + version ) } } diff --git a/src/maven/pom_parser.rs b/src/maven/pom_parser.rs index 7f105b9..f3ea27a 100644 --- a/src/maven/pom_parser.rs +++ b/src/maven/pom_parser.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; /// parse the pom.xml into a Pom object (struct) pub fn get_pom(home_dir: PathBuf, xml: impl Into) -> Result { 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; @@ -21,7 +21,7 @@ pub fn get_pom(home_dir: PathBuf, xml: impl Into) -> Result 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, @@ -37,7 +37,7 @@ pub fn get_pom(home_dir: PathBuf, xml: impl Into) -> Result Vec { 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 +136,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(), } } diff --git a/src/maven/project.rs b/src/maven/project.rs index 3937e60..e55e10d 100644 --- a/src/maven/project.rs +++ b/src/maven/project.rs @@ -196,15 +196,14 @@ impl Project { get_project_pom(&self.root, group_id, artifact_id) } - pub fn iter(&self) -> PomIterator{ - PomIterator{ + pub fn iter<'a>(&'a self) -> PomIterator<'a> { + PomIterator { project: self, idx: 0, } } } - pub struct PomIterator<'a> { project: &'a Project, idx: usize, @@ -212,10 +211,7 @@ pub struct PomIterator<'a> { impl<'a> PomIterator<'a> { pub fn new(project: &'a Project) -> Self { - PomIterator { - project, - idx: 0, - } + PomIterator { project, idx: 0 } } } @@ -231,4 +227,4 @@ impl<'a> Iterator for PomIterator<'a> { None } } -} \ No newline at end of file +} diff --git a/src/maven/reporter.rs b/src/maven/reporter.rs index bb2d73a..0515409 100644 --- a/src/maven/reporter.rs +++ b/src/maven/reporter.rs @@ -3,7 +3,7 @@ use crate::maven::project::Project; use regex::Regex; use std::collections::HashSet; use std::fs::File; -use std::io::{BufWriter, Read, Write}; +use std::io::{BufWriter, Write}; use std::path::{Path, PathBuf}; use std::sync::LazyLock; use zip::ZipArchive; @@ -11,6 +11,7 @@ use zip::ZipArchive; static CLASS_EXPR: LazyLock = LazyLock::new(|| Regex::new(r"(.+)/.+\.class").unwrap()); const MAVEN_CENTRAL: &str = "https://repo1.maven.org/maven2/"; +// TODO should not be downloading dependencies pub fn report(project: &Project) { let pom = &project.root; for dep in &project.get_dependencies(pom) { @@ -52,14 +53,14 @@ fn download(dep: &Dependency) -> Result<(), String> { let url = format!("{}{}.jar", MAVEN_CENTRAL, dep); let client = Client::builder() - .timeout(std::time::Duration::from_secs(30)) // Ruime timeout instellen + .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") // Goede practice om een User-Agent te sturen + .header("User-Agent", "Maven/1.0") .send() .map_err(|e| e.to_string())?; if response.status().is_success() { diff --git a/src/maven/settings.rs b/src/maven/settings.rs new file mode 100644 index 0000000..9e140df --- /dev/null +++ b/src/maven/settings.rs @@ -0,0 +1,523 @@ +use std::{fs, path::PathBuf, str::FromStr}; + +use crate::{ + maven::{CUSTOM_SETTINGS_LOCATION, HOME, MAVEN_HOME}, + xml::dom_parser::{Node, get_document}, +}; + +pub fn get_settings() -> Result { + 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 settings_path = get_settings_path(); + let settings_file = fs::read_to_string(settings_path?).map_err(|e| e.to_string())?; + + for child in get_document(settings_file) + .map_err(|err| err.to_string())? + .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 { + let mut proxies = vec![]; + for child in element.children { + proxies.push(get_proxy(child)); + } + proxies +} + +fn get_active_profiles(element: Node) -> Vec { + 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 { + 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 { + 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 { + 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 { + 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 { + let mut properties = vec![]; + for child in element.children { + properties.push(Property { + name: child.name, + value: child.text, + }); + } + properties +} + +fn get_repositories(element: Node) -> Vec { + 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, + } +} + +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 { + 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) +} + +#[derive(Debug)] +pub struct Settings { + local_repository: Option, + interactive_mode: bool, + use_plugin_registry: bool, + offline: bool, + proxies: Vec, + servers: Vec, + mirrors: Vec, + profiles: Vec, + active_profiles: Vec, + plugin_groups: Vec, +} + +#[derive(Debug)] +struct Server { + id: Option, + username: Option, + password: Option, + private_key: Option, + passphrase: Option, + file_permissions: Option, + directory_permissions: Option, + configuration: Option, //xsd:any +} + +#[derive(Debug)] +struct Mirror { + id: Option, + mirror_of: Option, + name: Option, + url: Option, +} + +#[derive(Debug)] +struct Proxy { + active: bool, + protocol: String, + username: Option, + password: Option, + port: usize, + host: Option, + non_proxy_hosts: Option, + id: Option, +} + +#[derive(Debug)] +struct Profile { + id: Option, + activation: Option, + properties: Vec, + repositories: Vec, + plugin_repositories: Vec, +} + +#[derive(Debug)] +struct Activation { + active_by_default: bool, + jdk: Option, + os: Option, + property: Option, + file: Option, +} + +#[derive(Debug)] +struct ActivationOs { + name: Option, + family: Option, + arch: Option, + version: Option, +} + +#[derive(Debug)] +struct ActivationProperty { + name: Option, + value: Option, +} + +#[derive(Debug)] +struct ActivationFile { + missing: Option, + exists: Option, +} + +#[derive(Debug)] +struct Property { + name: String, + value: Option, +} + +#[derive(Debug)] +struct Repository { + releases: Option, + snapshots: Option, + id: Option, + name: Option, + url: Option, + layout: String, +} + +#[derive(Debug)] +struct RepositoryPolicy { + enabled: bool, + update_policy: Option, + checksum_policy: Option, +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test() { + let settings = get_settings().expect("no fail"); + println!("{:?}", settings); + } +} diff --git a/src/report.rs b/src/report.rs index 9b951c6..da04055 100644 --- a/src/report.rs +++ b/src/report.rs @@ -1,9 +1,11 @@ use crate::maven::project::Project; -use maud::{PreEscaped, html}; +use maud::{DOCTYPE, PreEscaped, html}; impl Project { pub fn generate_dependency_html(&self) -> String { let html = html! { + (DOCTYPE) + html { (PreEscaped(r#" @@ -27,26 +29,29 @@ impl Project { } -"#)) - 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()) } + "#)) + 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() } diff --git a/src/xml/dom_parser.rs b/src/xml/dom_parser.rs index 644d9a5..d3f726b 100644 --- a/src/xml/dom_parser.rs +++ b/src/xml/dom_parser.rs @@ -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 { +pub fn get_document(xml: impl Into) -> Result { 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()) } diff --git a/src/xml/sax_parser.rs b/src/xml/sax_parser.rs index 6295e8e..ab4bcf4 100644 --- a/src/xml/sax_parser.rs +++ b/src/xml/sax_parser.rs @@ -22,6 +22,7 @@ struct SAXParser<'a> { xml: Vec, handler: Box<&'a mut dyn SaxHandler>, position: usize, + current_line: usize, current: char, char_buffer: Vec, 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(), @@ -111,6 +113,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 +163,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 +256,10 @@ impl<'a> SAXParser<'a> { } else { '\0' }; + // print!("{}", self.current); + if self.current == '\n' { + self.current_line += 1; + } Ok(self.current) }