package-parser 0.1.0

A fast, multi-language package metadata extractor written in Rust, designed for SCA purpose.
Documentation
#![allow(clippy::new_without_default)]

pub mod error;
pub mod helper;
pub mod pkgs;
pub mod types;

use globset::{Glob, GlobSet, GlobSetBuilder};
pub use pkgs::common::model::{DependentPackage, Package, PackageManifest};
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use crate::types::SupportedType;


#[derive(Clone)]
pub struct Scanner {

    scanners: Vec<Arc<dyn PackageManifest + Send + Sync>>,
    types: Vec<SupportedType>,

    glob_index_to_scanner_index: HashMap<usize, usize>,
    glob_set: GlobSet,
}

impl Scanner {
    pub fn new() -> Self {
        let scanners = pkgs::create_scanners();

        let mut glob_index_to_scanner_index = HashMap::new();
        let mut glob_set = GlobSetBuilder::new();
        let mut current_glob_index = 0usize;

        let mut supported_types = vec![];
        for (index, scanner) in scanners.iter().enumerate() {
            let supported_type = SupportedType {
                name: scanner.get_identifier(),
                filenames: scanner
                    .file_name_patterns()
                    .iter()
                    .map(|s| s.to_string())
                    .collect(),
                patterns: vec![],
            };
            supported_types.push(supported_type);

            for pat in scanner.file_name_patterns() {
                let glob = Glob::new(pat).expect("Failed to compile pattern");
                glob_set.add(glob);
                glob_index_to_scanner_index.insert(current_glob_index, index);
                current_glob_index += 1;
            }
        }

        let glob_set = glob_set.build().expect("Failed to build glob set");

        Self {
            scanners,
            types: supported_types,

            glob_index_to_scanner_index,
            glob_set,
        }
    }

    pub async fn scan(
        &self,
        path: impl AsRef<Path>,
        prefix: impl AsRef<Path>,
    ) -> Result<(String, Package), error::SourcePkgError> {
        let location = path.as_ref();
        let prefix = prefix.as_ref();

        if let Some(match_idx) = self
            .glob_set
            .matches(
                location
                    .file_name()
                    .ok_or(error::SourcePkgError::GenericsError(
                        "Invalid file name ending in '..'",
                    ))?,
            )
            .first()
        {
            let scanner_idx = self.glob_index_to_scanner_index[match_idx];
            let scanner = &self.scanners[scanner_idx];
            let ctx = pkgs::RecognizeContext {
                prefix: prefix.to_path_buf(),
            };

            return match scanner.recognize_with_config(location, &ctx).await {
                Ok(manifest) => Ok((scanner.get_name(), manifest)),
                Err(e) => Err(e),
            };
        }

        Err(error::SourcePkgError::NotSupported)
    }

    pub fn supported_types(&self) -> &[SupportedType] {
        &self.types
    }
}