upstream-ontologist 0.3.0

tracking of upstream project metadata
Documentation
//! Documentation: <https://maven.apache.org/pom.html>

use crate::{
    vcs, Certainty, GuesserSettings, ProviderError, UpstreamDatum, UpstreamDatumWithMetadata,
};
use log::warn;
use std::fs::File;
use std::path::Path;

/// Extracts upstream metadata from Maven pom.xml file
pub fn guess_from_pom_xml(
    path: &Path,
    _settings: &GuesserSettings,
) -> std::result::Result<Vec<UpstreamDatumWithMetadata>, ProviderError> {
    use xmltree::Element;
    let file = File::open(path).expect("Failed to open file");

    let file = std::io::BufReader::new(file);

    let root = Element::parse(file)
        .map_err(|e| ProviderError::ParseError(format!("Unable to parse package.xml: {}", e)))?;

    let mut result = Vec::new();
    if root.name == "project" {
        if let Some(name_tag) = root.get_child("name") {
            if let Some(name) = name_tag.get_text() {
                if !name.contains('$') {
                    result.push(UpstreamDatumWithMetadata {
                        datum: UpstreamDatum::Name(name.to_string()),
                        certainty: Some(Certainty::Certain),
                        origin: Some(path.into()),
                    });
                }
            }
        } else if let Some(artifact_id_tag) = root.get_child("artifactId") {
            if let Some(artifact_id) = artifact_id_tag.get_text() {
                result.push(UpstreamDatumWithMetadata {
                    datum: UpstreamDatum::Name(artifact_id.to_string()),
                    certainty: Some(Certainty::Possible),
                    origin: Some(path.into()),
                });
            }
        }

        if let Some(description_tag) = root.get_child("description") {
            if let Some(description) = description_tag.get_text() {
                result.push(UpstreamDatumWithMetadata {
                    datum: UpstreamDatum::Summary(description.to_string()),
                    certainty: Some(Certainty::Certain),
                    origin: Some(path.into()),
                });
            }
        }

        if let Some(version_tag) = root.get_child("version") {
            if let Some(version) = version_tag.get_text() {
                if !version.contains('$') {
                    result.push(UpstreamDatumWithMetadata {
                        datum: UpstreamDatum::Version(version.to_string()),
                        certainty: Some(Certainty::Certain),
                        origin: Some(path.into()),
                    });
                }
            }
        }

        if let Some(licenses_tag) = root.get_child("licenses") {
            for license_tag in licenses_tag
                .children
                .iter()
                .filter(|c| c.as_element().is_some_and(|e| e.name == "license"))
            {
                if let Some(license_tag) = license_tag.as_element() {
                    if let Some(name_tag) = license_tag.get_child("name") {
                        if let Some(license_name) = name_tag.get_text() {
                            result.push(UpstreamDatumWithMetadata {
                                datum: UpstreamDatum::License(license_name.to_string()),
                                certainty: Some(Certainty::Certain),
                                origin: Some(path.into()),
                            });
                        }
                    }
                }
            }
        }

        for scm_tag in root
            .children
            .iter()
            .filter(|c| c.as_element().is_some_and(|e| e.name == "scm"))
        {
            if let Some(scm_tag) = scm_tag.as_element() {
                if let Some(url_tag) = scm_tag.get_child("url") {
                    if let Some(url) = url_tag.get_text() {
                        if url.starts_with("scm:") && url.matches(':').count() >= 3 {
                            let url_parts: Vec<&str> = url.splitn(3, ':').collect();
                            let browse_url = url_parts[2];
                            if vcs::plausible_browse_url(browse_url) {
                                result.push(UpstreamDatumWithMetadata {
                                    datum: UpstreamDatum::RepositoryBrowse(browse_url.to_owned()),
                                    certainty: Some(Certainty::Certain),
                                    origin: Some(path.into()),
                                });
                            }
                        } else {
                            result.push(UpstreamDatumWithMetadata {
                                datum: UpstreamDatum::RepositoryBrowse(url.to_string()),
                                certainty: Some(Certainty::Certain),
                                origin: Some(path.into()),
                            });
                        }
                    }
                }

                if let Some(connection_tag) = scm_tag.get_child("connection") {
                    if let Some(connection) = connection_tag.get_text() {
                        let connection_parts: Vec<&str> = connection.splitn(3, ':').collect();
                        if connection_parts.len() == 3 && connection_parts[0] == "scm" {
                            result.push(UpstreamDatumWithMetadata {
                                datum: UpstreamDatum::Repository(connection_parts[2].to_owned()),
                                certainty: Some(Certainty::Certain),
                                origin: Some(path.into()),
                            });
                        } else {
                            warn!("Invalid format for SCM connection: {}", connection);
                        }
                    }
                }
            }
        }

        for issue_mgmt_tag in root
            .children
            .iter()
            .filter(|c| c.as_element().is_some_and(|e| e.name == "issueManagement"))
        {
            if let Some(issue_mgmt_tag) = issue_mgmt_tag.as_element() {
                if let Some(url_tag) = issue_mgmt_tag.get_child("url") {
                    if let Some(url) = url_tag.get_text() {
                        result.push(UpstreamDatumWithMetadata {
                            datum: UpstreamDatum::BugDatabase(url.to_string()),
                            certainty: Some(Certainty::Certain),
                            origin: Some(path.into()),
                        });
                    }
                }
            }
        }

        if let Some(url_tag) = root.get_child("url") {
            if let Some(url) = url_tag.get_text() {
                if !url.starts_with("scm:") {
                    result.push(UpstreamDatumWithMetadata {
                        datum: UpstreamDatum::Homepage(url.into_owned()),
                        certainty: Some(Certainty::Certain),
                        origin: Some(path.into()),
                    });
                }
            }
        }
    }

    Ok(result)
}