/** Programming languages by file extension, with GitHub linguist colors. */ const LANGUAGES: Record = {}; function define(name: string, color: string, exts: string[]) { for (const ext of exts) LANGUAGES[ext] = { name, color }; } define("TypeScript", "#3178c6", ["ts", "tsx", "mts", "cts"]); define("JavaScript", "#f1e05a", ["js", "jsx", "mjs", "cjs"]); define("CSS", "#663399", ["css"]); define("SCSS", "#c6538c", ["scss", "sass"]); define("HTML", "#e34c26", ["html", "htm"]); define("Vue", "#41b883", ["vue"]); define("Svelte", "#ff3e00", ["svelte"]); define("Astro", "#ff5a03", ["astro"]); define("Python", "#3572a5", ["py"]); define("Go", "#00add8", ["go"]); define("Rust", "#dea584", ["rs"]); define("Swift", "#f05138", ["swift"]); define("Kotlin", "#a97bff", ["kt", "kts"]); define("Java", "#b07219", ["java"]); define("C", "#555555", ["c", "h"]); define("C++", "#f34b7d", ["cpp", "cc", "cxx", "hpp", "hh"]); define("C#", "#178600", ["cs"]); define("Ruby", "#701516", ["rb"]); define("PHP", "#4f5d95", ["php"]); define("Shell", "#89e051", ["sh", "bash", "zsh", "fish"]); define("Lua", "#000080", ["lua"]); define("Zig", "#ec915c", ["zig"]); define("Dart", "#00b4ab", ["dart"]); define("Elixir", "#6e4a7e", ["ex", "exs"]); define("Nix", "#7e7eff", ["nix"]); define("Haskell", "#5e5086", ["hs"]); define("OCaml", "#ef7a08", ["ml", "mli"]); // Files that aren't code but still deserve a recognizable icon color. const OTHER_COLORS: Record = { md: "#519aba", mdx: "#519aba", json: "#cbcb41", jsonc: "#cbcb41", yml: "#cb4b16", yaml: "#cb4b16", toml: "#9c4221", svg: "#ffb13b", png: "#a074c4", jpg: "#a074c4", jpeg: "#a074c4", gif: "#a074c4", webp: "#a074c4", }; const VENDORED = /(^|\/)(node_modules|vendor|dist|build|\.next|third_party)\/|\.min\.(js|css)$/; function extension(filename: string) { const base = filename.split("/").pop() ?? ""; if (base === "Dockerfile" || base.endsWith(".dockerfile")) return "dockerfile"; const dot = base.lastIndexOf("."); return dot > 0 ? base.slice(dot + 1).toLowerCase() : ""; } export function languageFor(filename: string) { const ext = extension(filename); if (ext === "dockerfile") return { name: "Dockerfile", color: "#384d54" }; return LANGUAGES[ext] ?? null; } /** A color for a file's icon, or null for a neutral one. */ export function fileColor(filename: string) { return languageFor(filename)?.color ?? OTHER_COLORS[extension(filename)] ?? null; } export type LanguageShare = { name: string; color: string; percent: number }; /** Turns (path, size) pairs into a language breakdown, largest first. */ export function languageBreakdown(files: { path: string; size: number }[]): LanguageShare[] { const bytes = new Map(); for (const f of files) { if (VENDORED.test(f.path)) continue; const lang = languageFor(f.path); if (!lang) continue; const entry = bytes.get(lang.name) ?? { color: lang.color, size: 0 }; entry.size += f.size; bytes.set(lang.name, entry); } const total = [...bytes.values()].reduce((sum, e) => sum + e.size, 0); if (!total) return []; return [...bytes.entries()] .map(([name, e]) => ({ name, color: e.color, percent: (e.size / total) * 100 })) .sort((a, b) => b.percent - a.percent); }