// colors from https://raw.githubusercontent.com/ozh/github-colors/master/colors.json use anyhow::{anyhow, Result}; use git2::Repository; use lazy_static::lazy_static; use log::warn; use std::collections::{HashMap, HashSet}; use std::io::ErrorKind; use tokei::{Config as TokeiConfig, LanguageType, Languages}; lazy_static! { pub static ref LANGUAGE_COLORS: HashMap<LanguageType, &'static str> = HashMap::from([ (LanguageType::Abap, "#E8274B"), (LanguageType::ActionScript, "#882B0F#"), (LanguageType::Ada, "#02f88c"), (LanguageType::Agda, "#315665"), (LanguageType::Alex, "#123456"), // Find real color (LanguageType::Alloy, "#64C800"), (LanguageType::Arduino, "#123456"), // Find real color (LanguageType::AsciiDoc, "#73a0c5"), (LanguageType::Asn1, "#123456"), // Find real color (LanguageType::Asp, "#123456"), // Find real color (LanguageType::AspNet, "#9400ff"), (LanguageType::Assembly, "#6E4C13"), (LanguageType::AssemblyGAS, "#123456"), // Find real color (LanguageType::Ats, "#1ac620"), (LanguageType::AutoHotKey, "#6594b9"), (LanguageType::Autoit, "#1C3552"), (LanguageType::Automake, "#123456"), // Find real color // (LanguageType::Awk, "#c30e9b"), (LanguageType::Bash, "#89e051"), (LanguageType::Batch, "#C1F12E"), (LanguageType::Bean, "#123456"), // Find real color (LanguageType::Bitbake, "#00bce4"), (LanguageType::BrightScript, "#662D91"), (LanguageType::C, "#555555"), (LanguageType::Cabal, "#483465"), (LanguageType::Cassius, "#123456"), // Find real color (LanguageType::Ceylon, "#dfa535"), (LanguageType::CHeader, "#123456"), // Find real color (LanguageType::Clojure, "#db5855"), (LanguageType::ClojureC, "#123456"), // Find real color (LanguageType::ClojureScript, "#123456"), // Find real color (LanguageType::CMake, "#DA3434"), (LanguageType::Cobol, "#123456"), // Find real color (LanguageType::CodeQL, "#140f46"), (LanguageType::CoffeeScript, "#244776"), (LanguageType::Cogent, "#123456"), // Find real color (LanguageType::ColdFusion, "#ed2cd6"), (LanguageType::ColdFusionScript, "#ed2cd6"), (LanguageType::Coq, "#d0b68c"), (LanguageType::Cpp, "#f34b7d"), (LanguageType::CppHeader, "#123456"), // Find real color (LanguageType::Crystal, "#000100"), (LanguageType::CSharp, "#178600"), (LanguageType::CShell, "#123456"), // Find real color (LanguageType::Css, "#563d7c"), // (LanguageType::CUDA, "#3A4E3A"), (LanguageType::Cython, "#fedf5b"), (LanguageType::D, "#ba595e"), (LanguageType::Daml, "#123456"), // Find real color (LanguageType::Dart, "#00B4AB"), (LanguageType::DeviceTree, "#123456"), // Find real color (LanguageType::Dhall, "#dfafff"), (LanguageType::Dockerfile, "#384d54"), (LanguageType::DotNetResource, "#123456"), // Find real color (LanguageType::DreamMaker, "#123456"), // Find real color (LanguageType::Dust, "#123456"), // Find real color (LanguageType::Ebuild, "#9400ff"), (LanguageType::Edn, "#123456"), // Find real color (LanguageType::Elisp, "#c065db"), (LanguageType::Elixir, "#6e4a7e"), (LanguageType::Elm, "#60B5CC"), (LanguageType::Elvish, "#55BB55"), (LanguageType::EmacsDevEnv, "#123456"), // Find real color (LanguageType::Emojicode, "#123456"), // Find real color (LanguageType::Erlang, "#B83998"), (LanguageType::Factor, "#636746"), (LanguageType::FEN, "#123456"), // Find real color (LanguageType::Fennel, "#fff3d7"), (LanguageType::Fish, "#4aae47"), (LanguageType::FlatBuffers, "#123456"), // Find real color (LanguageType::ForgeConfig, "#123456"), // Find real color (LanguageType::Forth, "#341708"), (LanguageType::FortranLegacy, "#4d41b1"), (LanguageType::FortranModern, "#4d41b1"), (LanguageType::FreeMarker, "#0050b2"), (LanguageType::FSharp, "#b845fc"), (LanguageType::Fstar, "#572e30"), (LanguageType::Futhark, "#5f021f"), (LanguageType::GDB, "#123456"), // Find real color (LanguageType::GdScript, "#355570"), (LanguageType::Gherkin, "#5B2063"), (LanguageType::Gleam, "#ffaff3"), (LanguageType::Glsl, "#5686a5"), (LanguageType::Gml, "#123456"), // Find real color (LanguageType::Go, "#00ADD8"), (LanguageType::Gohtml, "#123456"), // Find real color (LanguageType::Graphql, "#e10098"), (LanguageType::Groovy, "#4298b8"), (LanguageType::Gwion, "#123456"), // Find real color (LanguageType::Haml, "#ece2a9"), (LanguageType::Hamlet, "#123456"), // Find real color (LanguageType::Happy, "#123456"), // Find real color (LanguageType::Handlebars, "#f7931e"), (LanguageType::Haskell, "#5e5086"), (LanguageType::Haxe, "#df7900"), (LanguageType::Hcl, "#844FBA"), (LanguageType::Hlsl, "#aace60"), (LanguageType::HolyC, "#ffefaf"), (LanguageType::Html, "#e34c26"), (LanguageType::Hy, "#7790B2"), (LanguageType::Idris, "#b30000"), (LanguageType::Ini, "#d1dbe0"), (LanguageType::Isabelle, "#FEFE00"), (LanguageType::Jai, "#123456"), // Find real color (LanguageType::Java, "#b07219"), (LanguageType::JavaScript, "#f1e05a"), (LanguageType::Jinja2, "#a52a22"), (LanguageType::Json, "#292929"), (LanguageType::Jsonnet, "#0064bd"), (LanguageType::Jsx, "#f1e05a"), (LanguageType::Julia, "#a270ba"), (LanguageType::Julius, "#123456"), // Find real color (LanguageType::Jupyter, "#DA5B0B"), (LanguageType::K, "#123456"), // Find real color (LanguageType::KakouneScript, "#6f8042"), (LanguageType::Kotlin, "#A97BFF"), (LanguageType::Ksh, "#123456"), // Find real color (LanguageType::KvLanguage, "#1da6e0"), (LanguageType::Lean, "#123456"), // Find real color (LanguageType::Less, "#1d365d"), (LanguageType::Liquid, "#67b8de"), (LanguageType::LinkerScript, "#123456"), // Find real color (LanguageType::Lisp, "#123456"), // Find real color (LanguageType::LiveScript, "#499886"), (LanguageType::LLVM, "#185619"), (LanguageType::Logtalk, "#295b9a"), (LanguageType::LolCode, "#cc9900"), (LanguageType::Lua, "#000080"), (LanguageType::Lucius, "#123456"), // Find real color (LanguageType::M4, "#123456"), // Find real color (LanguageType::Madlang, "#123456"), // Find real color (LanguageType::Makefile, "#427819"), (LanguageType::Markdown, "#083fa1"), (LanguageType::Meson, "#007800"), (LanguageType::Metal, "#8f14e9"), (LanguageType::Mint, "#02b046"), (LanguageType::Mlatu, "#123456"), // Find real color (LanguageType::ModuleDef, "#123456"), // Find real color (LanguageType::MoonScript, "#ff4585"), (LanguageType::MsBuild, "#123456"), // Find real color (LanguageType::Mustache, "#724b3b"), (LanguageType::Nextflow, "#3ac486"), (LanguageType::Nim, "#ffc200"), (LanguageType::Nix, "#7e7eff"), (LanguageType::NotQuitePerl, "#123456"), // Find real color (LanguageType::ObjectiveC, "#438eff"), (LanguageType::ObjectiveCpp, "#6866fb"), (LanguageType::OCaml, "#3be133"), (LanguageType::Odin, "#60AFFE"), (LanguageType::OpenPolicyAgent, "#7d9199"), (LanguageType::OpenType, "#123456"), // Find real color (LanguageType::Org, "#77aa99"), (LanguageType::Oz, "#fab738"), (LanguageType::Pan, "#cc0000"), (LanguageType::Pascal, "#E3F171"), (LanguageType::Perl, "#0298c3"), (LanguageType::Pest, "#123456"), // Find real color (LanguageType::Php, "#4F5D95"), (LanguageType::Poke, "#123456"), // Find real color (LanguageType::Polly, "#123456"), // Find real color (LanguageType::Pony, "#123456"), // Find real color (LanguageType::PostCss, "#dc3a0c"), (LanguageType::PowerShell, "#012456"), (LanguageType::Processing, "#0096D8"), (LanguageType::PSL, "#123456"), // Find real color (LanguageType::Prolog, "#74283c"), (LanguageType::Protobuf, "#123456"), // Find real color (LanguageType::Pug, "#a86454"), (LanguageType::Puppet, "#302B6D"), (LanguageType::PureScript, "#1D222D"), (LanguageType::Python, "#3572A5"), (LanguageType::Q, "#0040cd"), (LanguageType::Qcl, "#123456"), // Find real color (LanguageType::Qml, "#44a51c"), (LanguageType::R, "#198CE7"), (LanguageType::Racket, "#3c5caa"), (LanguageType::Rakefile, "#123456"), // Find real color (LanguageType::Raku, "#0000fb"), (LanguageType::Razor, "#123456"), // Find real color (LanguageType::Renpy, "#ff7f7f"), (LanguageType::ReScript, "#ed5051"), (LanguageType::ReStructuredText, "#141414"), (LanguageType::RON, "#123456"), // Find real color (LanguageType::RPMSpecfile, "#123456"), // Find real color (LanguageType::Ruby, "#701516"), (LanguageType::RubyHtml, "#123456"), // Find real color (LanguageType::Rust, "#dea584"), (LanguageType::Sass, "#a53b70"), (LanguageType::Scala, "#c22d40"), (LanguageType::Scheme, "#1e4aec"), (LanguageType::Scons, "#123456"), // Find real color (LanguageType::Sh, "#89e051"), (LanguageType::ShaderLab, "#222c37"), (LanguageType::Solidity, "#AA6746"), (LanguageType::SpecmanE, "#123456"), // Find real color (LanguageType::Spice, "#123456"), // Find real color (LanguageType::Sql, "#e38c00"), (LanguageType::Sqf, "#3F3F3F"), (LanguageType::SRecode, "#348a34"), (LanguageType::Stan, "#b2011d"), (LanguageType::Stratego, "#123456"), // Find real color (LanguageType::Stylus, "#ff6347"), (LanguageType::Svelte, "#ff3e00"), (LanguageType::Svg, "#ff9900"), (LanguageType::Swift, "#F0513"), (LanguageType::Swig, "#123456"), // Find real color (LanguageType::SystemVerilog, "#DAE1C2"), (LanguageType::Tcl, "#e4cc98"), (LanguageType::Tera, "#00004c"), (LanguageType::Tex, "#3D6117"), (LanguageType::Text, "#123456"), // Find real color (LanguageType::Thrift, "#D12127"), (LanguageType::Toml, "#9c4221"), (LanguageType::Tsx, "#3178c6"), (LanguageType::Ttcn, "#123456"), // Find real color (LanguageType::Twig, "#c1d026"), (LanguageType::TypeScript, "#3178c6"), (LanguageType::UMPL, "#123456"), // Find real color (LanguageType::Unison, "#123456"), // Find real color (LanguageType::UnrealDeveloperMarkdown, "#123456"), // Find real color (LanguageType::UnrealPlugin, "#123456"), // Find real color (LanguageType::UnrealProject, "#123456"), // Find real color (LanguageType::UnrealScript, "#a54c4d"), (LanguageType::UnrealShader, "#123456"), // Find real color (LanguageType::UnrealShaderHeader, "#123456"), // Find real color (LanguageType::UrWeb, "#ccccee"), (LanguageType::UrWebProject, "#123456"), // Find real color (LanguageType::Vala, "#a56de2"), (LanguageType::VB6, "#2c6353"), (LanguageType::VBScript, "#15dcdc"), (LanguageType::Velocity, "#507cff"), (LanguageType::Verilog, "#b2b7f8"), (LanguageType::VerilogArgsFile, "#123456"), // Find real color (LanguageType::Vhdl, "#adb2cb"), (LanguageType::VisualBasic, "#123456"), // Find real color (LanguageType::VisualStudioProject, "#123456"), // Find real color (LanguageType::VisualStudioSolution, "#123456"), // Find real color (LanguageType::VimScript, "#199f4b"), (LanguageType::Vue, "#41b883"), (LanguageType::WebAssembly, "#04133b"), (LanguageType::WenYan, "#123456"), // Find real color (LanguageType::WGSL, "#123456"), // Find real color (LanguageType::Wolfram, "#123456"), // Find real color (LanguageType::Xaml, "#123456"), // Find real color (LanguageType::XcodeConfig, "#123456"), // Find real color (LanguageType::Xml, "#0060ac"), (LanguageType::XSL, "#EB8CEB"), (LanguageType::Xtend, "#24255d"), (LanguageType::Yaml, "#cb171e"), (LanguageType::ZenCode, "#00BCD1"), (LanguageType::Zig, "#ec915c"), (LanguageType::Zsh, "#123456"), // Find real color ]); pub static ref DATA_LANGUAGES: HashSet<LanguageType> = vec![ LanguageType::Text, LanguageType::Json, LanguageType::RON, LanguageType::Xml, LanguageType::Yaml, LanguageType::Toml, ] .into_iter() .collect(); pub static ref IGNORED_COUNT_EXTENTIONS: HashSet<&'static str> = vec![ ".lock", ".mod", ".sum", ".yml", ".yaml", ".txt", ".ron", ".json", ".toml", ".xml", // other non-code data files ".svg", ".pub", ".service", // binary files ".ico", ".jpg", ".jpeg", ".webm", ".mp4", ".wav", ".pdf", ".ttf", // not understood by tokei yet: ".kdl", // e.g. typst ".typ", ].into_iter().collect(); } #[derive(Clone, Debug)] pub struct LanguageStats { pub name: LanguageType, pub total: usize, pub percentage: f64, } #[derive(Clone, Debug)] pub struct RepoLanguageStats { pub languages: Vec<LanguageStats>, pub total_loc: usize, } pub fn create_worktree(repo: &Repository, username: &str, repo_path: &str) -> Result<String> { let head = repo.head()?; let tree = head.peel_to_tree()?; let tmpfs_mount = format!("/tmp/lilgit_{}_{}", username, repo_path); let tmpfs_mount_clone = tmpfs_mount.clone(); // want to ignore error match std::fs::remove_dir_all(&tmpfs_mount) { Err(x) => { if x.kind() != ErrorKind::NotFound { return Err(anyhow!("unable to delete temp directory: {}", x)); }; } _ => (), }; std::fs::create_dir_all(&tmpfs_mount)?; let mut i = 0; tree.walk( git2::TreeWalkMode::PreOrder, move |parent_directory, entry| { i += 1; let kind = match entry.kind() { Some(x) => x, _ => return git2::TreeWalkResult::Skip, }; let is_dir = kind == git2::ObjectType::Tree; let entry_name = match entry.name() { Some(x) => x, _ => return git2::TreeWalkResult::Ok, }; if kind != git2::ObjectType::Tree && kind != git2::ObjectType::Blob { return git2::TreeWalkResult::Skip; }; let file_path = format!( "{}{}{}", parent_directory, entry_name, if is_dir { "/" } else { "" } ); if is_dir { return git2::TreeWalkResult::Ok; } let extension: String = format!( ".{}", std::path::Path::new(entry_name) .extension() .unwrap_or_default() .to_str() .unwrap_or_default() ); if IGNORED_COUNT_EXTENTIONS.contains(&*extension) { return git2::TreeWalkResult::Ok; } if let Some(blob) = entry.to_object(repo).unwrap().as_blob() { // not going to code count a binary anyways if blob.is_binary() { return git2::TreeWalkResult::Ok; } let extension_for_writing = if &extension == "." { "" } else { &extension }; let write_path = format!("{}/{}{}", tmpfs_mount, i, extension_for_writing); if let Err(err) = std::fs::write(write_path, blob.content()) { warn!("err writing write_path {:?} {:?}", err, file_path); }; return git2::TreeWalkResult::Ok; } // blob.content is byte slice, can be written to file. // if let Ok(blob_str) = str::from_utf8(blob.content()) { git2::TreeWalkResult::Ok }, )?; return Ok(tmpfs_mount_clone); } pub fn get_repo_languages( repo: &Repository, username: &str, repo_path: &str, ) -> Result<RepoLanguageStats> { // i.e. languages we are not interesting in counting for code statistics if repo_path.contains("/") { return Err(anyhow!("repo has illegal character")); } if repo_path.contains("\\") { return Err(anyhow!("repo has illegal character")); } let mut statistics = Languages::new(); let config = TokeiConfig::default(); let worktree_path = create_worktree(repo, username, repo_path)?; statistics.get_statistics( &[&worktree_path], &IGNORED_COUNT_EXTENTIONS .iter() .map(|x| x.clone()) .collect::<Vec<_>>(), &config, ); let mut totals: Vec<_> = statistics .into_iter() .filter(|(name, _)| !DATA_LANGUAGES.contains(name)) .map(|(name, stats)| { let total = stats.lines(); LanguageStats { name, total, percentage: 0., } }) .collect(); totals.sort_by(|a, b| b.total.cmp(&a.total)); let repo_loc = totals.iter().fold(0, |acc, curr| acc + curr.total); Ok(RepoLanguageStats { total_loc: repo_loc, languages: totals .into_iter() .map(|x| LanguageStats { percentage: (x.total as f64) / (repo_loc as f64), ..x }) .collect(), }) }