lockspot 0.0.5

let's ask package-lock some questions
Documentation
extern crate serde;
extern crate serde_json;

use regex::Regex;
use serde::Deserialize;
use std::collections::HashMap;

fn default_bool() -> bool {
    false
}

fn default_requires() -> HashMap<String, String> {
    HashMap::new()
}

fn default_dependencies() -> Dependencies {
    HashMap::new()
}

pub type Dependencies = HashMap<String, Dependency>;

#[derive(Deserialize, Debug)]
pub struct Dependency {
    version: String,
    integrity: String,
    #[serde(default = "default_bool")]
    bundled: bool,
    #[serde(default = "default_bool")]
    dev: bool,
    #[serde(default = "default_bool")]
    optional: bool,
    #[serde(default = "default_requires")]
    requires: HashMap<String, String>,
    #[serde(default = "default_dependencies")]
    pub dependencies: Dependencies,
}

#[derive(Deserialize, Debug)]
pub struct PackageLock {
    pub name: String,
    #[serde(rename = "lockfileVersion")]
    pub lockfile_version: i32,
    pub requires: bool,
    pub dependencies: Dependencies,
}

pub fn validate(lock: &PackageLock) -> bool {
    lock.lockfile_version == 1 && lock.requires == true
}

pub type Patterns = Vec<Option<Regex>>;

#[derive(Clone, Copy)]
pub enum SortType {
    Dont,
    Count,
    Name,
}

pub struct FlatOptions {
    pub patterns: Patterns,
    pub production: bool,
}

pub struct Options {
    pub production: bool,
    pub patterns: Patterns,
    pub min: Option<usize>,
    pub sort: Option<SortType>,
}

fn gather_dependencies(dependencies: &Dependencies, options: &Options) -> Vec<String> {
    let mut names = vec![];
    let patterns = &options.patterns;
    let patterns_length = patterns.len();
    for (name, info) in dependencies {
        if options.production && info.dev {
            continue;
        }
        if patterns_length > 0 {
            for pattern in patterns.iter() {
                if let Some(p) = pattern {
                    if p.is_match(name) {
                        names.push(name.to_string())
                    }
                }
            }
        } else {
            names.push(name.to_string());
        }

        names.append(&mut gather_dependencies(
            &info.dependencies,
            &Options {
                production: options.production,
                patterns: patterns.clone(),
                min: options.min,
                sort: options.sort,
            },
        ));
    }
    return names;
}

pub fn check_for_flatness(lock: PackageLock, options: Options) -> bool {
    let names = gather_dependencies(&lock.dependencies, &options);
    let mut seen = vec![];

    for name in names {
        if seen.contains(&name) {
            return false;
        }
        seen.push(name);
    }
    true
}

pub fn get_depcount(lock: PackageLock, options: Options) -> String {
    let names = gather_dependencies(&lock.dependencies, &options);
    let mut seen = HashMap::new();

    for name in names {
        let occurrences = seen.entry(name).or_insert(0);
        *occurrences += 1;
    }

    let mut seen_enough = vec![];
    let mut longest = 0;

    if let Some(min) = options.min {
        for (name, occurences) in seen.iter() {
            if occurences >= &min {
                let length = name.len();
                if longest < length {
                    longest = length
                }
                seen_enough.push((name, occurences))
            }
        }
    };

    match options.sort {
        Some(SortType::Name) => seen_enough.sort_by(|a, b| a.0.cmp(&b.0)),
        Some(SortType::Count) => seen_enough.sort_by(|a, b| a.1.cmp(&b.1)),
        Some(SortType::Dont) => {}
        None => {}
    };

    let mut lines = vec![];

    for (name, count) in seen_enough.iter() {
        lines.push(format!(
            "{}{}{}",
            name,
            " ".repeat(longest - name.len() + 8),
            count
        ))
    }

    lines.join("\n")
}