1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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")
}