//! Skills aggregation shared by `nd resume` (cli) and the /u/ owner //! pages (hub): what does this maintainer build with, and with what. use crate::models::Project; use serde::Serialize; use std::collections::BTreeMap; #[derive(Debug, Default, Serialize)] pub struct Skills { pub languages: Vec<(String, usize)>, pub tools: Vec<(String, usize)>, pub topics: Vec<(String, usize)>, pub models: Vec, } fn bump(map: &mut BTreeMap, values: &[String]) { for v in values { let k = v.trim().to_lowercase(); if !k.is_empty() { *map.entry(k).or_default() += 1; } } } fn ranked(m: BTreeMap) -> Vec<(String, usize)> { let mut v: Vec<_> = m.into_iter().collect(); v.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0))); v } pub fn collect(projects: &[Project]) -> Skills { let (mut langs, mut tools, mut topics) = (BTreeMap::new(), BTreeMap::new(), BTreeMap::new()); let mut models: Vec = Vec::new(); for p in projects { bump(&mut langs, &p.languages); bump(&mut tools, &p.code_keywords); bump(&mut topics, &p.keywords); if !p.model.trim().is_empty() && !models.contains(&p.model) { models.push(p.model.clone()); } } Skills { languages: ranked(langs), tools: ranked(tools), topics: ranked(topics), models, } } /// "rust ×3, clap ×2, axum" — top n items pub fn x2(items: &[(String, usize)], n: usize) -> String { items .iter() .take(n) .map(|(k, c)| { if *c > 1 { format!("{k} ×{c}") } else { k.clone() } }) .collect::>() .join(", ") } #[cfg(test)] mod tests { use super::*; fn p(name: &str, langs: &[&str], tools: &[&str], topics: &[&str]) -> Project { Project { name: name.into(), slug: name.into(), version: "1.0.0".into(), languages: langs.iter().map(|s| s.to_string()).collect(), code_keywords: tools.iter().map(|s| s.to_string()).collect(), keywords: topics.iter().map(|s| s.to_string()).collect(), ..Default::default() } } #[test] fn ranking() { let projects = vec![ p("a", &["rust"], &["clap", "tokio"], &["cli"]), p("b", &["rust", "js"], &["clap"], &["cli", "llm"]), ]; let s = collect(&projects); assert_eq!(s.languages[0], ("rust".into(), 2)); assert_eq!(s.tools[0], ("clap".into(), 2)); assert_eq!(s.topics[0], ("cli".into(), 2)); assert_eq!(x2(&s.languages, 10), "rust ×2, js"); } }