diff --git a/Makefile b/Makefile index a6c6f74..e2c6674 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,10 @@ docker-test-remote-local: ## Test remote install using local HTTP server repo-intel-build: ## Rebuild stow/bin/repo-intel from src/repo-intel python3 src/repo-intel/build.py stow/bin/repo-intel -repo-intel-dev: ## Run repo-intel from source (reads template.html live; pass args via ARGS=) +repo-intel-techdata: ## Regenerate src/repo-intel/techdata.json from Linguist (needs network) + python3 src/repo-intel/gen_techdata.py + +repo-intel-dev: ## Run repo-intel from source (reads template.html + techdata.json live; pass args via ARGS=) python3 src/repo-intel/repo-intel.py $(ARGS) -.PHONY: help install uninstall brew docker-build docker-test docker-shell docker-setup docker-clean docker-test-remote docker-test-remote-local repo-intel-build repo-intel-dev +.PHONY: help install uninstall brew docker-build docker-test docker-shell docker-setup docker-clean docker-test-remote docker-test-remote-local repo-intel-build repo-intel-techdata repo-intel-dev diff --git a/src/repo-intel/README.md b/src/repo-intel/README.md index 4070e86..a2e1cbf 100644 --- a/src/repo-intel/README.md +++ b/src/repo-intel/README.md @@ -8,7 +8,16 @@ single-file and depends only on Python 3 + `git`. `repo-intel` reads commit history from either the current git repo or a remote GitHub repo and writes a self-contained HTML dashboard showing top contributors, weekly/daily activity, time-of-day patterns, -and per-author commit feeds. +and per-author commit feeds. It also breaks down work by **language** +(per-commit file types in the timeline tooltip, a per-author language +bar in the contributor popover, and a repo-wide "Technologies" section) +and detects **frameworks** from dependency manifests grouped by language. + +The per-file **language** breakdown needs file-level line stats that only +the local and bare-clone paths produce — the token-authenticated GraphQL +remote path omits it, so the Technologies section's language column shows a +short placeholder there instead. **Framework** detection works on every path +(on the remote path the dependency manifests are fetched over the API). The [GitHub CLI (`gh`)](https://cli.github.com/) is optional but recommended: when authenticated (`gh auth login`), `repo-intel` uses @@ -126,11 +135,24 @@ repo-intel --no-cache tyom/dotfiles # bypass cache ## Files -| File | Purpose | -| --------------- | ----------------------------------------------------------------------- | -| `repo-intel.py` | The script. Holds `TEMPLATE = "__TEMPLATE_PLACEHOLDER__"` until bundled | -| `template.html` | Dashboard HTML, with `/*__DATA_INJECTION__*/` for runtime data | -| `build.py` | Substitutes the `TEMPLATE =` line with `template.html` as a `repr()` | +| File | Purpose | +| ----------------- | ---------------------------------------------------------------------------- | +| `repo-intel.py` | The script. Holds `TEMPLATE` + `TECHDATA` placeholders until bundled | +| `template.html` | Dashboard HTML, with `/*__DATA_INJECTION__*/` for runtime data | +| `techdata.json` | Generated language + framework detection data (committed; embedded at build) | +| `gen_techdata.py` | Regenerates `techdata.json` from GitHub Linguist + a curated framework map | +| `build.py` | Substitutes the `TEMPLATE` / `TECHDATA` lines with their data as a `repr()` | + +### Detection data (`techdata.json`) + +Language detection (extension/filename → language, colors, vendored-path noise +filter) is generated from [GitHub Linguist](https://github.com/github-linguist/linguist) +— `languages.yml` (with fine-grained languages folded into their `group`, e.g. +`TSX`→`TypeScript`) and `vendor.yml`. Frameworks are a small curated +dependency → framework map maintained in `gen_techdata.py` (Vercel/Netlify's +lists target deploy presets, not the libraries a repo uses, so they're a poor +fit). `techdata.json` is committed and embedded into the artifact, so the +shipped tool stays offline and single-file. ## Workflows @@ -142,9 +164,18 @@ make repo-intel-build Writes `stow/bin/repo-intel` (chmod 0755). Commit both source and artifact — the artifact is checked in so a fresh clone + `make install` -works without a build step. +works without a build step. `repo-intel-build` reads the committed +`techdata.json`; it is **not** regenerated on every build (that would need +network), so builds stay offline and reproducible. + +**Refresh detection data** (only when bumping Linguist or editing the +framework map — needs network): + +```bash +make repo-intel-techdata # rewrites techdata.json; then commit it + rebuild +``` -**Develop against the template live** (no rebuild needed between edits): +**Develop against the source live** (no rebuild needed between edits): ```bash make repo-intel-dev # uses cwd, top 10 @@ -152,19 +183,22 @@ make repo-intel-dev ARGS="3 facebook/react" # top 3 of a remote repo ``` The source script auto-detects that `TEMPLATE` is still the placeholder -and falls back to reading `template.html` from disk. The built artifact -never hits that branch — it carries the embedded template. +and falls back to reading `template.html` (and `techdata.json`) from disk. +The built artifact never hits that branch — it carries both embedded. ## How the embedding works -`build.py` looks for exactly one occurrence of the line: +`build.py` looks for exactly one occurrence each of: ```python TEMPLATE = "__TEMPLATE_PLACEHOLDER__" +TECHDATA = "__TECHDATA_PLACEHOLDER__" ``` -and replaces it with `TEMPLATE = `. The result is -a valid Python file — one long string literal on line 48 of the -artifact. Templating happens at build time; the runtime substitution of -`/*__DATA_INJECTION__*/` with `window.__DATA__ = {...}` still happens -inside `main()` as before. +and replaces them with `TEMPLATE = ` and +`TECHDATA = `. The result is a valid Python file +carrying both as string literals. Templating happens at build time; the +runtime substitution of `/*__DATA_INJECTION__*/` with +`window.__DATA__ = {...}` still happens inside `main()` as before. When +unbuilt, the script detects the placeholders and reads `template.html` +and `techdata.json` from disk instead. diff --git a/src/repo-intel/build.py b/src/repo-intel/build.py index a357465..466f3ee 100644 --- a/src/repo-intel/build.py +++ b/src/repo-intel/build.py @@ -1,16 +1,17 @@ #!/usr/bin/env python3 """Bundle src/repo-intel into a single-file executable. -Substitutes the TEMPLATE placeholder in repo-intel.py with the contents -of template.html as a Python string literal, then writes the result to -the given output path with mode 0755. +Substitutes two placeholders in repo-intel.py with their data as Python string +literals — TEMPLATE with template.html, TECHDATA with techdata.json — then +writes the result to the given output path with mode 0755. """ import os import sys from pathlib import Path -PLACEHOLDER = 'TEMPLATE = "__TEMPLATE_PLACEHOLDER__"' +TEMPLATE_PLACEHOLDER = 'TEMPLATE = "__TEMPLATE_PLACEHOLDER__"' +TECHDATA_PLACEHOLDER = 'TECHDATA = "__TECHDATA_PLACEHOLDER__"' def main(): @@ -22,10 +23,26 @@ def main(): script = (src_dir / "repo-intel.py").read_text() template = (src_dir / "template.html").read_text() - if script.count(PLACEHOLDER) != 1: - sys.exit(f"error: expected exactly one {PLACEHOLDER!r} line in repo-intel.py") - - bundled = script.replace(PLACEHOLDER, f"TEMPLATE = {template!r}") + techdata_path = src_dir / "techdata.json" + if not techdata_path.exists(): + sys.exit( + f"error: {techdata_path} not found — run `make repo-intel-techdata` " + "(needs network) to generate it, then commit it." + ) + techdata = techdata_path.read_text() + + for name, placeholder in ( + ("template.html", TEMPLATE_PLACEHOLDER), + ("techdata.json", TECHDATA_PLACEHOLDER), + ): + if script.count(placeholder) != 1: + sys.exit(f"error: expected exactly one {placeholder!r} line in repo-intel.py") + + bundled = ( + script + .replace(TEMPLATE_PLACEHOLDER, f"TEMPLATE = {template!r}") + .replace(TECHDATA_PLACEHOLDER, f"TECHDATA = {techdata!r}") + ) out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(bundled) out_path.chmod(0o755) diff --git a/src/repo-intel/gen_techdata.py b/src/repo-intel/gen_techdata.py new file mode 100644 index 0000000..439bd28 --- /dev/null +++ b/src/repo-intel/gen_techdata.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +"""Generate techdata.json — language + framework detection data for repo-intel. + +Languages are generated from GitHub Linguist (the canonical, maintained source +for extension→language mappings and the official colors); frameworks are a +small curated map. We evaluated scraping Vercel's `frameworks.ts` for the JS +side but it targets *deployment-framework CLIs* (`next`, `react-scripts`), not +the libraries a repo uses — `react`/`express`/`vue` aren't in it — so it's the +wrong shape for "what does this repo use" and the curated map stays on-target. + +Writes a single committed JSON snapshot that repo-intel.py loads (and that +build.py embeds into the artifact): + + - Languages: Linguist `languages.yml` (extension/filename → language, colors, + with fine-grained languages folded into their `group`, e.g. TSX→TypeScript) + + `vendor.yml` (vendored-path regexes for the noise filter). + - Frameworks: curated dependency → framework maps (web + backend) below. + +Run via `make repo-intel-techdata` (needs network). Stdlib-only. +""" + +import json +import re +import sys +import urllib.request +from pathlib import Path + +LANGUAGES_YML = "https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/languages.yml" +VENDOR_YML = "https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/vendor.yml" + +OUT = Path(__file__).resolve().parent / "techdata.json" + +TYPE_RANK = {"programming": 0, "markup": 1, "data": 2, "prose": 3, "": 4} + +# Ambiguous extensions claimed by several languages. Linguist resolves these in +# code (a classifier + popularity), not data, so derivation alone mis-assigns +# (e.g. `.md` → "GCC Machine Description"). This tiebreaker layer pins the few +# that users actually notice; the chosen name must be a real Linguist language. +EXT_OVERRIDE = { + "md": "Markdown", "markdown": "Markdown", "h": "C", "m": "Objective-C", + "r": "R", "pl": "Perl", "t": "Perl", "l": "Common Lisp", "v": "Verilog", + "f": "Fortran", "for": "Fortran", "cls": "Apex", "pro": "Prolog", + "ts": "TypeScript", "rs": "Rust", "cs": "C#", "sql": "SQL", +} + +# Generic extensions whose canonical Linguist owner is the colorless "Text" +# language. Because color-less languages are dropped from the tables, a niche +# *colored* claimant would otherwise win the slot (e.g. `.txt` → "Adblock Filter +# List") — Linguist itself resolves these via a content classifier we can't run, +# so we leave them unassigned and let classify_path bucket them as "Other". +EXT_EXCLUDE = {"txt"} + +# Curated web/npm dependency → framework display name. Vercel/Netlify answer a +# different question (deploy presets), so this is maintained directly. +CURATED_WEB = { + "react": "React", "react-dom": "React", "next": "Next.js", + "vue": "Vue", "nuxt": "Nuxt", "@angular/core": "Angular", + "svelte": "Svelte", "@sveltejs/kit": "SvelteKit", + "solid-js": "SolidJS", "preact": "Preact", "astro": "Astro", + "gatsby": "Gatsby", "@remix-run/react": "Remix", + "express": "Express", "koa": "Koa", "fastify": "Fastify", + "@nestjs/core": "NestJS", "@hapi/hapi": "hapi", + "electron": "Electron", "react-native": "React Native", + "expo": "Expo", "@ionic/core": "Ionic", + "vite": "Vite", "webpack": "webpack", "rollup": "Rollup", + "esbuild": "esbuild", "parcel": "Parcel", + "tailwindcss": "Tailwind CSS", "bootstrap": "Bootstrap", + "@mui/material": "MUI", "@chakra-ui/react": "Chakra UI", + "styled-components": "styled-components", + "jest": "Jest", "vitest": "Vitest", "mocha": "Mocha", + "playwright": "Playwright", "@playwright/test": "Playwright", + "cypress": "Cypress", "puppeteer": "Puppeteer", "testcafe": "TestCafe", + "@testing-library/react": "Testing Library", + "@testing-library/vue": "Testing Library", + "@testing-library/dom": "Testing Library", + "eslint": "ESLint", "prettier": "Prettier", "@biomejs/biome": "Biome", + # Storybook ships across many scoped packages; the framework adapters below + # cover both apps that embed it and addons that declare it as a peer dep. + "storybook": "Storybook", "@storybook/react": "Storybook", + "@storybook/vue3": "Storybook", "@storybook/angular": "Storybook", + "@storybook/svelte": "Storybook", "@storybook/html": "Storybook", + "@storybook/web-components": "Storybook", "@storybook/preact": "Storybook", + # Monorepo / task runners. + "turbo": "Turborepo", "nx": "Nx", "@nx/workspace": "Nx", + # Transpilers. + "@swc/core": "SWC", "@babel/core": "Babel", + "redux": "Redux", "@reduxjs/toolkit": "Redux", "zustand": "Zustand", + "@apollo/client": "Apollo", "graphql": "GraphQL", + "@trpc/server": "tRPC", "@trpc/client": "tRPC", + "prisma": "Prisma", "@prisma/client": "Prisma", + "drizzle-orm": "Drizzle", "typeorm": "TypeORM", + "mongoose": "Mongoose", "sequelize": "Sequelize", + "three": "three.js", "d3": "D3", "chart.js": "Chart.js", +} + +# Web/JS sentinel files: basename → framework (assigned to the JS/TS bucket). +CURATED_SENTINELS_JS = [ + ["next.config.js", "Next.js"], ["next.config.ts", "Next.js"], + ["next.config.mjs", "Next.js"], ["nuxt.config.js", "Nuxt"], + ["nuxt.config.ts", "Nuxt"], ["svelte.config.js", "Svelte"], + ["astro.config.mjs", "Astro"], ["vue.config.js", "Vue"], + ["gatsby-config.js", "Gatsby"], ["angular.json", "Angular"], +] + +# Backend frameworks Vercel/Netlify don't cover — keyed by language, then +# dependency name → display name. Matched as whole words in manifest text. +CURATED_BACKEND = { + "Python": { + "django": "Django", "djangorestframework": "Django REST", + "flask": "Flask", "fastapi": "FastAPI", "starlette": "Starlette", + "tornado": "Tornado", "aiohttp": "aiohttp", "sanic": "Sanic", + "pyramid": "Pyramid", "sqlalchemy": "SQLAlchemy", "pydantic": "Pydantic", + "celery": "Celery", "scrapy": "Scrapy", "numpy": "NumPy", + "pandas": "pandas", "scipy": "SciPy", "scikit-learn": "scikit-learn", + "tensorflow": "TensorFlow", "torch": "PyTorch", "keras": "Keras", + "transformers": "Transformers", "matplotlib": "Matplotlib", + "pytest": "pytest", "click": "Click", "typer": "Typer", + "requests": "Requests", "httpx": "HTTPX", + }, + "Ruby": { + "rails": "Rails", "sinatra": "Sinatra", "hanami": "Hanami", + "rspec": "RSpec", "sidekiq": "Sidekiq", "puma": "Puma", "devise": "Devise", + }, + "Go": { + "github.com/gin-gonic/gin": "Gin", "github.com/labstack/echo": "Echo", + "github.com/gofiber/fiber": "Fiber", "github.com/gorilla/mux": "Gorilla", + "gorm.io/gorm": "GORM", "github.com/spf13/cobra": "Cobra", + "github.com/go-chi/chi": "chi", "google.golang.org/grpc": "gRPC", + }, + "Rust": { + "actix-web": "Actix Web", "axum": "Axum", "rocket": "Rocket", + "warp": "warp", "tokio": "Tokio", "serde": "Serde", "diesel": "Diesel", + "tonic": "Tonic", "clap": "clap", "bevy": "Bevy", "tauri": "Tauri", + }, + "PHP": { + "laravel/framework": "Laravel", "symfony/symfony": "Symfony", + "symfony/framework-bundle": "Symfony", "slim/slim": "Slim", + "cakephp/cakephp": "CakePHP", "yiisoft/yii2": "Yii", + }, +} + +# Colors for synthetic framework groups Linguist doesn't define a language for. +# Purple keeps "Tools" distinct from the grey "Other" bucket on the same page. +SYNTHETIC_COLORS = {"Tools": "#a371f7"} + +# Backend / non-JS sentinel files: basename (or sub-path) → (framework, language). +# The "Tools" bucket surfaces build/devops tooling that's present as a config +# file rather than a dependency — it'd otherwise hide in the long tail of the +# language bar (Dockerfile/Makefile are tiny by line count). +CURATED_SENTINELS = [ + ["manage.py", "Django", "Python"], + ["artisan", "Laravel", "PHP"], + ["config/application.rb", "Rails", "Ruby"], + ["Dockerfile", "Docker", "Tools"], + ["docker-compose.yml", "Docker Compose", "Tools"], + ["docker-compose.yaml", "Docker Compose", "Tools"], + ["compose.yml", "Docker Compose", "Tools"], + ["compose.yaml", "Docker Compose", "Tools"], + ["Makefile", "Make", "Tools"], + ["GNUmakefile", "Make", "Tools"], + ["pnpm-lock.yaml", "pnpm", "Tools"], + ["yarn.lock", "Yarn", "Tools"], + ["bun.lockb", "Bun", "Tools"], + ["bun.lock", "Bun", "Tools"], + [".gitlab-ci.yml", "GitLab CI", "Tools"], + ["vercel.json", "Vercel", "Tools"], + ["netlify.toml", "Netlify", "Tools"], + # Trailing slash → directory-prefix match (no single file to key on). + [".github/workflows/", "GitHub Actions", "Tools"], +] + + +def fetch(url): + req = urllib.request.Request(url, headers={"User-Agent": "repo-intel-gen"}) + with urllib.request.urlopen(req, timeout=10) as resp: + return resp.read().decode("utf-8") + + +def parse_languages_yml(text): + """Line-parse Linguist languages.yml (machine-generated, regular). + + Returns {name: {"type", "color", "extensions": [...], "filenames": [...]}}. + """ + langs = {} + cur = None + listkey = None + for raw in text.splitlines(): + if not raw or raw.lstrip().startswith("#"): + continue + if not raw[0].isspace(): # column-0 language header + m = re.match(r'^(?:"([^"]+)"|\'([^\']+)\'|([^:]+)):\s*$', raw) + if m: + name = m.group(1) or m.group(2) or m.group(3) + cur = {"type": "", "color": "", "group": "", + "extensions": [], "filenames": []} + langs[name] = cur + listkey = None + else: + cur = None + continue + if cur is None: + continue + item = re.match(r'^ - (.*)$', raw) + if item and listkey: + val = item.group(1).strip().strip('"').strip("'") + cur[listkey].append(val) + continue + prop = re.match(r'^ (\w+):\s*(.*)$', raw) + if prop: + key, val = prop.group(1), prop.group(2).strip() + if key in ("extensions", "filenames") and val == "": + listkey = key + else: + listkey = None + if key == "color": + cur["color"] = val.strip('"').strip("'") + elif key == "type": + cur["type"] = val + elif key == "group": + cur["group"] = val.strip('"').strip("'") + return langs + + +def build_language_tables(langs): + """ext/filename → language name and name → color, colored languages only. + + Fine-grained languages are folded into their `group` (TSX→TypeScript) so the + bar doesn't fragment; ambiguous extensions are pinned via EXT_OVERRIDE. + """ + name_color = {n: i["color"] for n, i in langs.items() if i.get("color")} + ext_lang, ext_meta, filename_lang = {}, {}, {} + for name, info in langs.items(): + if not info.get("color"): + continue + # Roll fine-grained langs into their parent, but only when that parent + # is itself a colored language — otherwise a group like "Checksums" + # would seed color-less entries. + group = info.get("group") + eff = group if group and group in name_color else name + rank = TYPE_RANK.get(info.get("type", ""), 4) + for idx, ext in enumerate(info.get("extensions", [])): + key = ext[1:].lower() if ext.startswith(".") else ext.lower() + if not key: + continue + primary = idx == 0 + if key not in ext_lang: + ext_lang[key] = eff + ext_meta[key] = (rank, primary) + else: + prank, pprimary = ext_meta[key] + # Prefer better type rank; then a primary extension over secondary. + if rank < prank or (rank == prank and primary and not pprimary): + ext_lang[key] = eff + ext_meta[key] = (rank, primary) + for fn in info.get("filenames", []): + filename_lang.setdefault(fn.lower(), eff) + for ext, lang in EXT_OVERRIDE.items(): + if lang in name_color: + ext_lang[ext] = lang + for ext in EXT_EXCLUDE: + ext_lang.pop(ext, None) + name_color.update(SYNTHETIC_COLORS) # synthetic buckets Linguist doesn't color + return name_color, ext_lang, filename_lang + + +def parse_vendor_yml(text): + out = [] + for line in text.splitlines(): + m = re.match(r'^- (.*)$', line) + if m: + out.append(m.group(1).strip()) + return out + + +def main(): + print("fetching Linguist languages.yml…", file=sys.stderr) + langs = parse_languages_yml(fetch(LANGUAGES_YML)) + name_color, ext_lang, filename_lang = build_language_tables(langs) + print(f" {len(name_color)} colored languages, {len(ext_lang)} extensions", + file=sys.stderr) + + print("fetching Linguist vendor.yml…", file=sys.stderr) + vendor = parse_vendor_yml(fetch(VENDOR_YML)) + print(f" {len(vendor)} vendor patterns", file=sys.stderr) + + fw_deps = {"npm": CURATED_WEB} + fw_deps.update(CURATED_BACKEND) + + data = { + "_source": {"languages": LANGUAGES_YML, "vendor": VENDOR_YML}, + "lang": {"ext": ext_lang, "filename": filename_lang, "color": name_color}, + "vendor": vendor, + "fw_deps": fw_deps, + "fw_sentinels_js": CURATED_SENTINELS_JS, + "fw_sentinels_other": CURATED_SENTINELS, + } + OUT.write_text(json.dumps(data, ensure_ascii=False, indent=1, sort_keys=True)) + print(f"wrote {OUT} ({OUT.stat().st_size:,} bytes)", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/src/repo-intel/repo-intel.py b/src/repo-intel/repo-intel.py index 5e6216a..850d825 100755 --- a/src/repo-intel/repo-intel.py +++ b/src/repo-intel/repo-intel.py @@ -5,7 +5,7 @@ repo-intel — generate a contributor stats dashboard for a git repo. Usage: - repo-intel [N] [REPO] [-o PATH] [--no-open] + repo-intel [N] [REPO] [-o PATH] [--no-open] [--clone] repo-intel -h | --help Arguments: @@ -20,6 +20,9 @@ -o, --output PATH Write the dashboard to PATH instead of /tmp/--.html. --no-open Don't open the result in a browser. --no-cache Ignore the local cache and re-fetch all commits. + --clone For a remote REPO, analyse a bare `git clone` instead of + the GitHub GraphQL API (alias: --bare). Slower to fetch + but unlocks per-author language churn the API can't give. --commits SPEC Filter commits by position. SPEC is either N (last N commits, newest) or A-B (positions [A, B), 0-indexed from oldest, half-open like Python slicing). @@ -34,6 +37,7 @@ repo-intel 15 facebook/react # remote, top 15 repo-intel -o ./stats.html # write to a specific path repo-intel --no-open # generate without launching browser + repo-intel facebook/react --clone # analyse via bare clone, not the API repo-intel --commits 100 # only the last 100 commits repo-intel --commits 0-100 # the first 100 commits repo-intel --commits 400-800 # commits at positions 400..799 (oldest-first) @@ -45,7 +49,8 @@ recommended — when authenticated it unlocks GraphQL remote fetching and author hovercard enrichment (avatar, bio, follower counts). Lookup order: `gh auth token -h github.com`, then $GITHUB_TOKEN. - Falls back to `git clone --bare` into /tmp if neither is available. + Falls back to `git clone --bare` into /tmp if neither is available; + pass --clone to force that bare-clone path even when a token is present. Output: /tmp/--.html (or --output PATH), opened in default browser @@ -173,6 +178,7 @@ def parse_args(argv): sys.stdout.write(HELP) sys.exit(0) top_n, remote, output, no_open, no_cache = 10, None, None, False, False + clone = False commits_filter, since, until = None, None, None i = 0 @@ -198,6 +204,10 @@ def take_value(name): no_cache = True i += 1 continue + if tok in ("--clone", "--bare"): + clone = True + i += 1 + continue if tok == "-o": if i + 1 >= len(argv): sys.stderr.write("repo-intel: -o requires a value\n") @@ -253,7 +263,7 @@ def take_value(name): if since and until and since > until: sys.stderr.write(f"repo-intel: --since {since} is after --until {until}\n") sys.exit(2) - return top_n, remote, output, no_open, no_cache, commits_filter, since, until + return top_n, remote, output, no_open, no_cache, clone, commits_filter, since, until def login_from_email(email): @@ -276,8 +286,363 @@ def iso_week_label(dt): return f"{y}-W{w:02d}" -def git(*args, cwd=None): - return subprocess.check_output(["git", *args], text=True, cwd=cwd) +# Language + framework detection data, generated from GitHub Linguist and a +# curated framework map by gen_techdata.py (see `make repo-intel-techdata`). +# build.py inlines the JSON here; when unbuilt we read the sibling file. Used +# only on the local + bare-clone paths — the GraphQL remote path lacks per-file +# data, so these maps go unused there. +TECHDATA = "__TECHDATA_PLACEHOLDER__" +OTHER_LANG = "Other" +OTHER_COLOR = "#8b949e" + + +def _load_techdata(): + raw = TECHDATA + if raw == "__TECHDATA_PLACEHOLDER__": + sibling = Path(__file__).resolve().parent / "techdata.json" + if not sibling.exists(): + return {} + try: + return json.loads(sibling.read_text()) + except (json.JSONDecodeError, OSError): + return {} + try: + return json.loads(raw) + except (json.JSONDecodeError, ValueError): + return {} + + +_TECH = _load_techdata() +_LANG = _TECH.get("lang", {}) +EXT_LANG = _LANG.get("ext", {}) # extension (no dot, lower) -> language +FILENAME_LANG = _LANG.get("filename", {}) # lowercased filename -> language +NAME_COLOR = _LANG.get("color", {}) # language -> hex color +FW_DEPS = _TECH.get("fw_deps", {}) # {ecosystem: {dependency: framework}} +FW_SENTINELS_JS = _TECH.get("fw_sentinels_js", []) # [[basename, framework]] +FW_SENTINELS_OTHER = _TECH.get("fw_sentinels_other", []) # [[path, framework, lang]] + + +def _compile_vendor(patterns): + """One matcher from Linguist's vendor.yml regexes; skips Python-incompatible + ones (they're Ruby-flavored) so the union still compiles.""" + good = [] + for p in patterns: + try: + re.compile(p) + good.append(p) + except re.error: + continue + try: + return re.compile("|".join(f"(?:{p})" for p in good)) if good else None + except re.error: + return None + + +_VENDOR_RE = _compile_vendor(_TECH.get("vendor", [])) + +# Lockfiles Linguist classifies as *generated* (handled in code, not vendor.yml) +# — kept as a small supplement so they don't dominate the language bar. +NOISE_BASENAMES = frozenset({ + "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "npm-shrinkwrap.json", + "composer.lock", "cargo.lock", "gemfile.lock", "poetry.lock", "go.sum", + "pdm.lock", "uv.lock", "flake.lock", +}) + +# Shebang interpreter → language, for extensionless scripts Linguist can't name +# from a path alone (e.g. `bin/deploy` with `#!/usr/bin/env bash`). A small +# curated map mirroring Linguist's `interpreters:`; trailing version digits are +# stripped (`python3` → `python`) before lookup. Names must be real Linguist +# languages so they pick up a color. +SHEBANG_LANG = { + "sh": "Shell", "bash": "Shell", "zsh": "Shell", "dash": "Shell", + "ksh": "Shell", "fish": "fish", "python": "Python", "ruby": "Ruby", + "node": "JavaScript", "perl": "Perl", "awk": "Awk", "gawk": "Awk", + "lua": "Lua", "php": "PHP", "rscript": "R", "tclsh": "Tcl", + "groovy": "Groovy", "osascript": "AppleScript", +} + + +def shebang_lang(first_line): + """Language for a `#!…` first line, or None. Resolves `env interp` and pins + `python3`→Python by stripping trailing version digits from the interpreter.""" + if not first_line.startswith("#!"): + return None + interp = None + for tok in first_line[2:].split(): + name = tok.rsplit("/", 1)[-1] + if name != "env": # skip the `env` in `#!/usr/bin/env python3` + interp = name + break + if not interp: + return None + interp = interp.lower() + return SHEBANG_LANG.get(interp) or SHEBANG_LANG.get(interp.rstrip("0123456789")) + + +def numstat_newpath(field): + """Resolve a numstat path column to the post-rename path. + + Renames render as `old => new`, or with a shared brace group like + `src/{old => new}/file.js`; plain paths pass through unchanged. + """ + if " => " not in field: + return field + lo = field.find("{") + hi = field.find("}", lo) if lo != -1 else -1 + if lo != -1 and hi != -1 and " => " in field[lo:hi]: + new = field[lo + 1:hi].split(" => ", 1)[1] + return field[:lo] + new + field[hi + 1:] + return field.split(" => ", 1)[1] + + +def classify_path(field, present=None, shebang=None): + """Map a numstat path column to a language name, or None to exclude it. + + `present`: when given, the set of paths at HEAD — files absent from it + (deleted since, or renamed away) are excluded so the bar reflects the repo + as it stands, not churn against files that no longer exist. + `shebang`: {path: language} for extensionless/unknown scripts a `#!` line + identified, so they land in their real language instead of "Other". + """ + path = numstat_newpath(field.strip().strip('"')).replace("\\", "/") + if present is not None and path not in present: + return None # file no longer exists at HEAD — count only survivors + if _VENDOR_RE and _VENDOR_RE.search(path): # Linguist vendored paths + return None + base = path.rsplit("/", 1)[-1].lower() + if base in NOISE_BASENAMES: + return None + if base.endswith((".min.js", ".min.css", ".map")): + return None + if base in FILENAME_LANG: # Dockerfile, Makefile, Rakefile, … + return FILENAME_LANG[base] + dot = base.rfind(".") + if dot > 0: + lang = EXT_LANG.get(base[dot + 1:]) + if lang: + return lang + if shebang and path in shebang: # extensionless/unknown but has a #! line + return shebang[path] + return OTHER_LANG + + +def top_languages(langs, limit=6): + """Build a sorted language-bar list from {name: [added, deleted, files]}. + + Ranks by lines touched (added + deleted); languages past `limit` collapse + into a single grey "Other" segment. Returns [] when nothing qualifies. + """ + items = [(name, a + d, files) for name, (a, d, files) in langs.items()] + total = sum(lines for _, lines, _ in items) + if total <= 0: + return [] + items.sort(key=lambda x: x[1], reverse=True) + out = [ + { + "name": name, + "lines": lines, + "files": files, + "pct": round(lines * 100 / total, 1), + "color": NAME_COLOR.get(name, OTHER_COLOR), + } + for name, lines, files in items[:limit] + ] + overflow = sum(lines for _, lines, _ in items[limit:]) + if overflow > 0: + existing = next((o for o in out if o["name"] == OTHER_LANG), None) + if existing: + existing["lines"] += overflow + existing["pct"] = round(existing["lines"] * 100 / total, 1) + else: + out.append({ + "name": OTHER_LANG, + "lines": overflow, + "files": 0, + "pct": round(overflow * 100 / total, 1), + "color": OTHER_COLOR, + }) + return out + + +def git(*args, cwd=None, quiet=False): + # quiet=True hides git's stderr — for best-effort probes that are expected + # to fail (e.g. work-tree-only commands run against a bare clone). + return subprocess.check_output( + ["git", *args], + text=True, + cwd=cwd, + stderr=subprocess.DEVNULL if quiet else None, + ) + + +def _git_show(path, cwd=None): + """Contents of `path` at HEAD, or "" if missing. Works on bare clones.""" + try: + return git("show", f"HEAD:{path}", cwd=cwd) + except subprocess.CalledProcessError: + return "" + + +def _head_first_line(path, cwd=None): + """First line of `path` at HEAD, decoded leniently, or "". Reads bytes so a + stray binary doesn't crash the utf-8 decode `git(text=True)` would attempt.""" + try: + out = subprocess.run( + ["git", "show", f"HEAD:{path}"], cwd=cwd, capture_output=True + ).stdout + except OSError: + return "" + nl = out.find(b"\n") + return (out if nl < 0 else out[:nl]).decode("utf-8", "replace") + + +def detect_frameworks(paths, cwd=None): + """Detect frameworks at HEAD from a local repo / bare clone. + + `paths`: the HEAD tree (repo-relative), already listed by the caller. + Returns a list grouped by language, ordered by framework count: + [{"language": "TypeScript", "color": "#3178c6", "names": [...]}, ...] + Best-effort and local-only — the GraphQL remote path skips this. + """ + return _frameworks_from_files(paths, lambda p: _git_show(p, cwd)) + + +def _frameworks_from_files(paths, read_file): + """Core framework detection over a file list, driven by techdata maps. + + `paths`: repo-relative paths that exist. `read_file(path)` -> contents + ("" if unavailable; only called for manifests worth parsing). Decoupled + from git so the remote path can supply GraphQL-fetched blobs. + """ + if not FW_DEPS: + return [] + paths = set(paths) + by_base = defaultdict(list) + for p in paths: + by_base[p.rsplit("/", 1)[-1].lower()].append(p) + + found = defaultdict(list) + seen = defaultdict(set) + + def add(language, name): + if name and name not in seen[language]: + seen[language].add(name) + found[language].append(name) + + def present(dep, text): + return re.search(r"(?= 3: + field = cols[2] + if field in lang_cache: + lang = lang_cache[field] + else: + lang = classify_path(field, present=present, shebang=shebang) + lang_cache[field] = lang + if lang: + rec = lang_stats.setdefault(cur, {}).setdefault(lang, [0, 0, 0]) + rec[0] += added + rec[1] += deleted + rec[2] += 1 default_branch = detect_default_branch(cwd=cwd) + extras = {"lang_stats": lang_stats, "frameworks": detect_frameworks(present, cwd=cwd)} return ( repo_name, github_base, @@ -476,6 +873,7 @@ def collect_local(cwd=None, suppress_current_user=False): default_branch, repo_disk_kb(cwd=cwd), collect_local_tags(cwd=cwd), + extras, ) @@ -748,6 +1146,138 @@ def fetch_remote_tags(owner, repo, token): return tags +def gh_rest_get(path, token): + """GET an api.github.com REST endpoint; returns the parsed JSON body.""" + req = urllib.request.Request( + f"https://api.github.com{path}", + headers={ + "Authorization": f"bearer {token}", + "User-Agent": "repo-intel", + "Accept": "application/vnd.github+json", + }, + ) + with urllib.request.urlopen(req) as resp: + return json.loads(resp.read()) + + +# Manifests _frameworks_from_files actually parses (so we only fetch those +# blobs). tsconfig.json / sentinels are presence-only — covered by the tree. +_REMOTE_MANIFEST_BASES = frozenset({ + "package.json", "composer.json", "pyproject.toml", "pipfile", + "setup.py", "setup.cfg", "gemfile", "go.mod", "cargo.toml", +}) + + +def _remote_manifest_paths(paths): + out = [] + for p in paths: + base = p.rsplit("/", 1)[-1].lower() + if base in _REMOTE_MANIFEST_BASES or ( + base.startswith("requirements") and base.endswith(".txt") + ): + out.append(p) + return out + + +def fetch_blob_texts(owner, repo, paths, token): + """HEAD blob text for each path via aliased GraphQL. Returns {path: text}.""" + out = {} + paths = list(paths) + for start in range(0, len(paths), 50): + chunk = paths[start:start + 50] + var_decls = ", ".join(f"$p{i}: String!" for i in range(len(chunk))) + frags = " ".join( + f"b{i}: object(expression: $p{i}) {{ ... on Blob {{ text }} }}" + for i in range(len(chunk)) + ) + query = ( + f"query($owner: String!, $repo: String!, {var_decls}) " + f"{{ repository(owner: $owner, name: $repo) {{ {frags} }} }}" + ) + variables = {"owner": owner, "repo": repo} + for i, p in enumerate(chunk): + variables[f"p{i}"] = f"HEAD:{p}" + try: + body = gh_graphql(query, variables, token) + except urllib.error.URLError as exc: + print(f" warning: manifest fetch failed: {exc}", file=sys.stderr) + continue + node = gh_repository(body) + for i, p in enumerate(chunk): + blob = node.get(f"b{i}") + if blob and blob.get("text") is not None: + out[p] = blob["text"] + return out + + +def fetch_frameworks_remote(owner, repo, token): + """Detect frameworks on the GraphQL path without a clone. + + Lists the repo tree (REST, recursive — manifests can be nested) and fetches + just the manifest blobs (GraphQL), then runs the shared detection core. + Per-file *languages* stay local-only (too expensive over the network), but + manifests are cheap, so frameworks work here too. + """ + if not token: + return [] + try: + tree = gh_rest_get(f"/repos/{owner}/{repo}/git/trees/HEAD?recursive=1", token) + except urllib.error.URLError as exc: + print(f" warning: framework tree fetch failed: {exc}", file=sys.stderr) + return [] + if tree.get("truncated"): + # GitHub caps the recursive tree at ~100k entries / 7MB; deep manifests + # past the cap are dropped, so detection may miss frameworks silently. + print( + " warning: repo tree truncated by GitHub — framework detection " + "may be incomplete", + file=sys.stderr, + ) + paths = [e["path"] for e in (tree.get("tree") or []) if e.get("type") == "blob"] + if not paths: + return [] + contents = fetch_blob_texts(owner, repo, _remote_manifest_paths(paths), token) + return _frameworks_from_files(paths, lambda p: contents.get(p, "")) + + +def fetch_languages_remote(owner, repo, token): + """Repo-wide language breakdown on the GraphQL path, no clone needed. + + GitHub runs Linguist itself and exposes the result as bytes-per-language at + HEAD. That's a composition snapshot, not the per-commit line churn the local + path tracks — so it can only fill the repo-wide bar, never per-author or + per-commit language stats. Reuses `top_languages` (ranking by the first + slot, here byte size) so colors and overflow collapsing match local runs. + Returns [] on error or when the repo has no detected languages. + """ + if not token: + return [] + query = """ +query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + languages(first: 50, orderBy: {field: SIZE, direction: DESC}) { + edges { size node { name } } + } + } +} +""".strip() + try: + body = gh_graphql(query, {"owner": owner, "repo": repo}, token) + except urllib.error.URLError as exc: + print(f" warning: language fetch failed: {exc}", file=sys.stderr) + return [] + if "errors" in body: + return [] + edges = ((gh_repository(body).get("languages") or {}).get("edges")) or [] + langs = {} + for e in edges: + name = ((e.get("node") or {}).get("name") or "").strip() + size = e.get("size") or 0 + if name and size > 0: + langs[name] = [size, 0, 0] + return top_languages(langs) + + def _paginate_history(fetch_page, cached_oids, last_n, since, have_count_baseline, label, skip_first=False): """Walk a Commit.history connection page by page. @@ -810,6 +1340,7 @@ def collect_remote(slug, token, no_cache=False, commits_filter=None, since=None, default_branch, repo_size_kb, tags, + extras, ) = collect_local(cwd=clone_dir, suppress_current_user=True) if not github_base: github_base = f"https://github.com/{owner}/{repo}" @@ -824,6 +1355,7 @@ def collect_remote(slug, token, no_cache=False, commits_filter=None, since=None, default_branch, repo_size_kb, tags, + extras, ) history_block = """ @@ -888,6 +1420,17 @@ def top_fetch_page(cursor): repo_meta["branch"] = branch_ref.get("name") or repo_meta["branch"] return branch_ref["target"]["history"] + def bail_partial(nodes): + """Persist a contiguous partial run after a fetch failure, then exit so + the next run resumes from its tail. Saved as incomplete on purpose.""" + if not no_cache and nodes: + save_cache(slug, nodes, False) + print( + f" cached {len(nodes)} commits so far — re-run to resume", + file=sys.stderr, + ) + sys.exit("error: GitHub fetch failed after repeated retries; aborting.") + new_nodes, top_reason = _paginate_history( top_fetch_page, cached_oids, last_n, since, have_count_baseline=len(cached_nodes), label="new", @@ -897,13 +1440,7 @@ def top_fetch_page(cursor): # new_nodes is a contiguous run from HEAD. We never reached the old # cache, so merging would leave a gap — persist just the fresh prefix # (the next run resumes its tail via the older-fetch) and bail out. - if not no_cache and new_nodes: - save_cache(slug, new_nodes, False) - print( - f" cached {len(new_nodes)} commits so far — re-run to resume", - file=sys.stderr, - ) - sys.exit("error: GitHub fetch failed after repeated retries; aborting.") + bail_partial(new_nodes) if top_reason == "page_end" and cached_oids: print( @@ -964,13 +1501,7 @@ def bottom_fetch_page(cursor): if bottom_reason == "fetch_failed": # new + cached + older are contiguous, so the partial run is a valid # prefix to persist; the next run extends from its tail. - if not no_cache and nodes: - save_cache(slug, nodes, False) - print( - f" cached {len(nodes)} commits so far — re-run to resume", - file=sys.stderr, - ) - sys.exit("error: GitHub fetch failed after repeated retries; aborting.") + bail_partial(nodes) if bottom_reason is None: new_complete = top_reason == "page_end" or loaded_complete else: @@ -997,6 +1528,12 @@ def bottom_fetch_page(cursor): logins[email] = user["login"] tags = fetch_remote_tags(owner, repo, token) + # Per-commit/per-author language churn needs a clone, so `lang_stats` stays + # empty here. But the repo-wide bar and frameworks both come straight from + # the API: GitHub runs Linguist for `repo_languages`, and manifests are + # cheap to fetch for frameworks. + frameworks = fetch_frameworks_remote(owner, repo, token) + repo_languages = fetch_languages_remote(owner, repo, token) return ( repo_name, repo_url, @@ -1008,6 +1545,7 @@ def bottom_fetch_page(cursor): default_branch, repo_size_kb, tags, + {"lang_stats": {}, "frameworks": frameworks, "repo_languages": repo_languages}, ) @@ -1060,7 +1598,15 @@ def build_data( default_branch, repo_size_kb, tags, + extras, ): + lang_stats = (extras or {}).get("lang_stats", {}) + frameworks = (extras or {}).get("frameworks", []) + # Remote runs ship a precomputed repo-wide bar (bytes at HEAD); local/bare + # runs build it below from per-commit line churn. `repo_languages` being a + # non-empty list signals the former. + repo_languages = (extras or {}).get("repo_languages") or [] + repo_langs = {} authors = {} daily_by_author = defaultdict(lambda: defaultdict(int)) hourly_by_author = defaultdict(lambda: [0] * 24) @@ -1095,6 +1641,7 @@ def build_data( "deleted": 0, "dates": set(), "daily_counts": defaultdict(int), + "langs": {}, "first": d_key, "last": d_key, }, @@ -1104,6 +1651,15 @@ def build_data( rec["deleted"] += d rec["dates"].add(d_key) rec["daily_counts"][d_key] += 1 + for lang, (la, ld, lf) in lang_stats.get(h, {}).items(): + agg = rec["langs"].setdefault(lang, [0, 0, 0]) + agg[0] += la + agg[1] += ld + agg[2] += lf + repo = repo_langs.setdefault(lang, [0, 0, 0]) + repo[0] += la + repo[1] += ld + repo[2] += lf if d_key < rec["first"]: rec["first"] = d_key if d_key > rec["last"]: @@ -1143,6 +1699,7 @@ def build_data( "busiestCount": busiest_count, "avatarUrl": avatar_url(r["email"], override=avatars.get(r["email"])), "highlight": bool(current_email) and r["email"] == current_email, + "languages": top_languages(r["langs"]), } ) @@ -1160,16 +1717,23 @@ def build_data( if meta["email"] not in top_emails: continue a, d = line_stats.get(h, [0, 0]) - commits_list.append( - { - "h": h[:7], - "s": (meta["subject"] or "")[:120], - "e": meta["email"], - "d": meta.get("iso") or "", - "a": a, - "l": d, - } - ) + entry = { + "h": h[:7], + "s": (meta["subject"] or "")[:120], + "e": meta["email"], + "d": meta.get("iso") or "", + "a": a, + "l": d, + } + cl = lang_stats.get(h) + if cl: + ftypes = sorted( + ([name, NAME_COLOR.get(name, OTHER_COLOR), files] + for name, (_, _, files) in cl.items()), + key=lambda x: x[2], reverse=True, + ) + entry["f"] = ftypes[:4] + commits_list.append(entry) date_range = ( {"start": min(all_dates), "end": max(all_dates)} @@ -1196,6 +1760,9 @@ def build_data( "dowData": dow_data, "commits": commits_list, "tags": tags or [], + "repoLanguages": repo_languages or top_languages(repo_langs), + "repoLanguagesBasis": "size" if repo_languages else "churn", + "frameworks": frameworks or [], } @@ -1250,7 +1817,7 @@ def enrich_contributor_profiles(contributors, commits_meta, github_base, token=N def main(): - top_n, remote, output, no_open, no_cache, commits_filter, since, until = parse_args( + top_n, remote, output, no_open, no_cache, clone, commits_filter, since, until = parse_args( sys.argv[1:] ) @@ -1258,8 +1825,13 @@ def main(): if remote: owner, repo = remote.split("/", 1) token = get_github_token() + # --clone forces the bare-clone path even when a token is present: a + # local clone unlocks per-author language churn the GraphQL history API + # can't provide. The token (if any) is still used below for hovercard + # enrichment, so `use_graphql` — not `token` — gates the API path. + use_graphql = bool(token) and not clone - if not token: + if not use_graphql and not clone: print("No GitHub token — falling back to bare clone.", file=sys.stderr) # Subset prompt only in the GraphQL path: probing total via the API is @@ -1267,7 +1839,7 @@ def main(): # bare-clone path the full clone runs regardless, so the prompt would # only trim local display — pass `--commits` / `--since` for that. if ( - token + use_graphql and not (commits_filter or since or until) and sys.stdin.isatty() and sys.stderr.isatty() @@ -1277,8 +1849,10 @@ def main(): if total and total > 1000: commits_filter, since, until = prompt_subset(total) - if token: + if use_graphql: print(f"Fetching {remote} via GitHub GraphQL…", file=sys.stderr) + else: + print(f"Cloning {remote} (bare) for local analysis…", file=sys.stderr) ( repo_name, github_base, @@ -1290,9 +1864,10 @@ def main(): default_branch, repo_size_kb, tags, + extras, ) = collect_remote( remote, - token, + token if use_graphql else None, no_cache=no_cache, commits_filter=commits_filter, since=since, @@ -1318,6 +1893,7 @@ def main(): default_branch, repo_size_kb, tags, + extras, ) = collect_local() if not commits_meta: @@ -1348,6 +1924,7 @@ def main(): default_branch, repo_size_kb, tags, + extras, ) enrich_contributor_profiles(data["contributors"], commits_meta, github_base, token=token) diff --git a/src/repo-intel/techdata.json b/src/repo-intel/techdata.json new file mode 100644 index 0000000..5eba008 --- /dev/null +++ b/src/repo-intel/techdata.json @@ -0,0 +1,2749 @@ +{ + "_source": { + "languages": "https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/languages.yml", + "vendor": "https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/vendor.yml" + }, + "fw_deps": { + "Go": { + "github.com/gin-gonic/gin": "Gin", + "github.com/go-chi/chi": "chi", + "github.com/gofiber/fiber": "Fiber", + "github.com/gorilla/mux": "Gorilla", + "github.com/labstack/echo": "Echo", + "github.com/spf13/cobra": "Cobra", + "google.golang.org/grpc": "gRPC", + "gorm.io/gorm": "GORM" + }, + "PHP": { + "cakephp/cakephp": "CakePHP", + "laravel/framework": "Laravel", + "slim/slim": "Slim", + "symfony/framework-bundle": "Symfony", + "symfony/symfony": "Symfony", + "yiisoft/yii2": "Yii" + }, + "Python": { + "aiohttp": "aiohttp", + "celery": "Celery", + "click": "Click", + "django": "Django", + "djangorestframework": "Django REST", + "fastapi": "FastAPI", + "flask": "Flask", + "httpx": "HTTPX", + "keras": "Keras", + "matplotlib": "Matplotlib", + "numpy": "NumPy", + "pandas": "pandas", + "pydantic": "Pydantic", + "pyramid": "Pyramid", + "pytest": "pytest", + "requests": "Requests", + "sanic": "Sanic", + "scikit-learn": "scikit-learn", + "scipy": "SciPy", + "scrapy": "Scrapy", + "sqlalchemy": "SQLAlchemy", + "starlette": "Starlette", + "tensorflow": "TensorFlow", + "torch": "PyTorch", + "tornado": "Tornado", + "transformers": "Transformers", + "typer": "Typer" + }, + "Ruby": { + "devise": "Devise", + "hanami": "Hanami", + "puma": "Puma", + "rails": "Rails", + "rspec": "RSpec", + "sidekiq": "Sidekiq", + "sinatra": "Sinatra" + }, + "Rust": { + "actix-web": "Actix Web", + "axum": "Axum", + "bevy": "Bevy", + "clap": "clap", + "diesel": "Diesel", + "rocket": "Rocket", + "serde": "Serde", + "tauri": "Tauri", + "tokio": "Tokio", + "tonic": "Tonic", + "warp": "warp" + }, + "npm": { + "@angular/core": "Angular", + "@apollo/client": "Apollo", + "@babel/core": "Babel", + "@biomejs/biome": "Biome", + "@chakra-ui/react": "Chakra UI", + "@hapi/hapi": "hapi", + "@ionic/core": "Ionic", + "@mui/material": "MUI", + "@nestjs/core": "NestJS", + "@nx/workspace": "Nx", + "@playwright/test": "Playwright", + "@prisma/client": "Prisma", + "@reduxjs/toolkit": "Redux", + "@remix-run/react": "Remix", + "@storybook/angular": "Storybook", + "@storybook/html": "Storybook", + "@storybook/preact": "Storybook", + "@storybook/react": "Storybook", + "@storybook/svelte": "Storybook", + "@storybook/vue3": "Storybook", + "@storybook/web-components": "Storybook", + "@sveltejs/kit": "SvelteKit", + "@swc/core": "SWC", + "@testing-library/dom": "Testing Library", + "@testing-library/react": "Testing Library", + "@testing-library/vue": "Testing Library", + "@trpc/client": "tRPC", + "@trpc/server": "tRPC", + "astro": "Astro", + "bootstrap": "Bootstrap", + "chart.js": "Chart.js", + "cypress": "Cypress", + "d3": "D3", + "drizzle-orm": "Drizzle", + "electron": "Electron", + "esbuild": "esbuild", + "eslint": "ESLint", + "expo": "Expo", + "express": "Express", + "fastify": "Fastify", + "gatsby": "Gatsby", + "graphql": "GraphQL", + "jest": "Jest", + "koa": "Koa", + "mocha": "Mocha", + "mongoose": "Mongoose", + "next": "Next.js", + "nuxt": "Nuxt", + "nx": "Nx", + "parcel": "Parcel", + "playwright": "Playwright", + "preact": "Preact", + "prettier": "Prettier", + "prisma": "Prisma", + "puppeteer": "Puppeteer", + "react": "React", + "react-dom": "React", + "react-native": "React Native", + "redux": "Redux", + "rollup": "Rollup", + "sequelize": "Sequelize", + "solid-js": "SolidJS", + "storybook": "Storybook", + "styled-components": "styled-components", + "svelte": "Svelte", + "tailwindcss": "Tailwind CSS", + "testcafe": "TestCafe", + "three": "three.js", + "turbo": "Turborepo", + "typeorm": "TypeORM", + "vite": "Vite", + "vitest": "Vitest", + "vue": "Vue", + "webpack": "webpack", + "zustand": "Zustand" + } + }, + "fw_sentinels_js": [ + [ + "next.config.js", + "Next.js" + ], + [ + "next.config.ts", + "Next.js" + ], + [ + "next.config.mjs", + "Next.js" + ], + [ + "nuxt.config.js", + "Nuxt" + ], + [ + "nuxt.config.ts", + "Nuxt" + ], + [ + "svelte.config.js", + "Svelte" + ], + [ + "astro.config.mjs", + "Astro" + ], + [ + "vue.config.js", + "Vue" + ], + [ + "gatsby-config.js", + "Gatsby" + ], + [ + "angular.json", + "Angular" + ] + ], + "fw_sentinels_other": [ + [ + "manage.py", + "Django", + "Python" + ], + [ + "artisan", + "Laravel", + "PHP" + ], + [ + "config/application.rb", + "Rails", + "Ruby" + ], + [ + "Dockerfile", + "Docker", + "Tools" + ], + [ + "docker-compose.yml", + "Docker Compose", + "Tools" + ], + [ + "docker-compose.yaml", + "Docker Compose", + "Tools" + ], + [ + "compose.yml", + "Docker Compose", + "Tools" + ], + [ + "compose.yaml", + "Docker Compose", + "Tools" + ], + [ + "Makefile", + "Make", + "Tools" + ], + [ + "GNUmakefile", + "Make", + "Tools" + ], + [ + "pnpm-lock.yaml", + "pnpm", + "Tools" + ], + [ + "yarn.lock", + "Yarn", + "Tools" + ], + [ + "bun.lockb", + "Bun", + "Tools" + ], + [ + "bun.lock", + "Bun", + "Tools" + ], + [ + ".gitlab-ci.yml", + "GitLab CI", + "Tools" + ], + [ + "vercel.json", + "Vercel", + "Tools" + ], + [ + "netlify.toml", + "Netlify", + "Tools" + ], + [ + ".github/workflows/", + "GitHub Actions", + "Tools" + ] + ], + "lang": { + "color": { + "1C Enterprise": "#814CCC", + "2-Dimensional Array": "#38761D", + "4D": "#004289", + "ABAP": "#E8274B", + "ABAP CDS": "#555e25", + "AGS Script": "#B9D9FF", + "AIDL": "#34EB6B", + "AL": "#3AA2B5", + "ALGOL": "#D1E0DB", + "AMPL": "#E6EFBB", + "ANTLR": "#9DC3FF", + "API Blueprint": "#2ACCA8", + "APL": "#5A8164", + "ASP.NET": "#9400ff", + "ATS": "#1ac620", + "ActionScript": "#882B0F", + "Ada": "#02f88c", + "Adblock Filter List": "#800000", + "Adobe Font Metrics": "#fa0f00", + "Agda": "#315665", + "Aiken": "#640ff8", + "Alloy": "#64C800", + "Alpine Abuild": "#0D597F", + "Altium Designer": "#A89663", + "AngelScript": "#C7D7DC", + "Answer Set Programming": "#A9CC29", + "Ant Build System": "#A9157E", + "Antlers": "#ff269e", + "ApacheConf": "#d12127", + "Apex": "#1797c0", + "Apollo Guidance Computer": "#0B3D91", + "AppleScript": "#101F1F", + "Arc": "#aa2afe", + "AsciiDoc": "#73a0c5", + "AspectJ": "#a957b0", + "Assembly": "#6E4C13", + "Astro": "#ff5a03", + "Asymptote": "#ff0000", + "Augeas": "#9CC134", + "AutoHotkey": "#6594b9", + "AutoIt": "#1C3552", + "Avro IDL": "#0040FF", + "Awk": "#c30e9b", + "B (Formal Method)": "#8aa8c5", + "B4X": "#00e4ff", + "BASIC": "#ff0000", + "BQN": "#2b7067", + "Ballerina": "#FF5000", + "Batchfile": "#C1F12E", + "Beef": "#a52f4e", + "Berry": "#15A13C", + "BibTeX": "#778899", + "Bicep": "#519aba", + "Bikeshed": "#5562ac", + "Bison": "#6A463F", + "BitBake": "#00bce4", + "Blade": "#f7523f", + "BlitzBasic": "#00FFAE", + "BlitzMax": "#cd6400", + "Bluespec": "#12223c", + "Bluespec BH": "#12223c", + "Boo": "#d4bec1", + "Boogie": "#c80fa0", + "Brainfuck": "#2F2530", + "BrighterScript": "#66AABB", + "Brightscript": "#662D91", + "Browserslist": "#ffd539", + "Bru": "#F4AA41", + "BuildStream": "#006bff", + "C": "#555555", + "C#": "#178600", + "C++": "#f34b7d", + "C3": "#2563eb", + "CAP CDS": "#0092d1", + "CLIPS": "#00A300", + "CMake": "#DA3434", + "COLLADA": "#F1A42B", + "CQL": "#006091", + "CSON": "#244776", + "CSS": "#663399", + "CSV": "#237346", + "CUE": "#5886E1", + "CWeb": "#00007a", + "Cabal Config": "#483465", + "Caddyfile": "#22b638", + "Cadence": "#00ef8b", + "Cairo": "#ff4a48", + "Cairo Zero": "#ff4a48", + "CameLIGO": "#3be133", + "Cangjie": "#00868B", + "Cap'n Proto": "#c42727", + "Carbon": "#222222", + "Ceylon": "#dfa535", + "Chapel": "#8dc63f", + "ChucK": "#3f8000", + "Circom": "#707575", + "Cirru": "#ccccff", + "Clarion": "#db901e", + "Clarity": "#5546ff", + "Classic ASP": "#6a40fd", + "Clean": "#3F85AF", + "Click": "#E4E6F3", + "Clojure": "#db5855", + "Closure Templates": "#0d948f", + "Cloud Firestore Security Rules": "#FFA000", + "Clue": "#0009b5", + "CodeQL": "#140f46", + "CoffeeScript": "#244776", + "ColdFusion": "#ed2cd6", + "ColdFusion CFC": "#ed2cd6", + "Common Lisp": "#3fb68b", + "Common Workflow Language": "#B5314C", + "Component Pascal": "#B0CE4E", + "Cooklang": "#E15A29", + "Crystal": "#000100", + "Csound": "#1a1a1a", + "Csound Document": "#1a1a1a", + "Csound Score": "#1a1a1a", + "Cuda": "#3A4E3A", + "Curry": "#531242", + "Cylc": "#00b3fd", + "Cypher": "#34c0eb", + "Cython": "#fedf5b", + "D": "#ba595e", + "D2": "#526ee8", + "DM": "#447265", + "Dafny": "#FFEC25", + "Darcs Patch": "#8eff23", + "Dart": "#00B4AB", + "Daslang": "#d3d3d3", + "DataWeave": "#003a52", + "Debian Package Control File": "#D70751", + "DenizenScript": "#FBEE96", + "Dhall": "#dfafff", + "DirectX 3D File": "#aace60", + "Dockerfile": "#384d54", + "Dogescript": "#cca760", + "Dotenv": "#e5d559", + "Dune": "#89421e", + "Dylan": "#6c616e", + "E": "#ccce35", + "ECL": "#8a1267", + "ECLiPSe": "#001d9d", + "EJS": "#a91e50", + "EQ": "#a78649", + "Earthly": "#2af0ff", + "Easybuild": "#069406", + "Ecere Projects": "#913960", + "Ecmarkup": "#eb8131", + "Edge": "#0dffe0", + "EdgeQL": "#31A7FF", + "EditorConfig": "#fff1f2", + "Eiffel": "#4d6977", + "Elixir": "#6e4a7e", + "Elm": "#60B5CC", + "Elvish": "#55BB55", + "Elvish Transcript": "#55BB55", + "Emacs Lisp": "#c065db", + "EmberScript": "#FFF4F3", + "Erlang": "#B83998", + "Euphoria": "#FF790B", + "F#": "#b845fc", + "F*": "#572e30", + "FIGlet Font": "#FFDDBB", + "FIRRTL": "#2f632f", + "FLUX": "#88ccff", + "Factor": "#636746", + "Fancy": "#7b9db4", + "Fantom": "#14253c", + "Faust": "#c37240", + "Fennel": "#fff3d7", + "Filebench WML": "#F6B900", + "FlatBuffers": "#ed284a", + "Flix": "#d44a45", + "Fluent": "#ffcc33", + "Forth": "#341708", + "Fortran": "#4d41b1", + "Fortran Free Form": "#4d41b1", + "FreeBASIC": "#141AC9", + "FreeMarker": "#0050b2", + "Frege": "#00cafe", + "Futhark": "#5f021f", + "G-code": "#D08CF2", + "GAML": "#FFC766", + "GAMS": "#f49a22", + "GAP": "#0000cc", + "GCC Machine Description": "#FFCFAB", + "GDScript": "#355570", + "GDShader": "#478CBF", + "GEDCOM": "#003058", + "GLSL": "#5686a5", + "GSC": "#FF6800", + "Game Maker Language": "#71b417", + "Gemfile.lock": "#701516", + "Gemini": "#ff6900", + "Genero 4gl": "#63408e", + "Genero per": "#d8df39", + "Genie": "#fb855d", + "Genshi": "#951531", + "Gentoo Ebuild": "#9400ff", + "Gentoo Eclass": "#9400ff", + "Gerber Image": "#d20b00", + "Gherkin": "#5B2063", + "Git Attributes": "#F44D27", + "Git Commit": "#F44D27", + "Git Config": "#F44D27", + "Git Revision List": "#F44D27", + "Gleam": "#ffaff3", + "Glimmer JS": "#F5835F", + "Glimmer TS": "#3178c6", + "Glyph": "#c1ac7f", + "Gnuplot": "#f0a9f0", + "Go": "#00ADD8", + "Go Checksums": "#00ADD8", + "Go Module": "#00ADD8", + "Go Template": "#00ADD8", + "Go Workspace": "#00ADD8", + "Godot Resource": "#355570", + "Golo": "#88562A", + "Gosu": "#82937f", + "Grace": "#615f8b", + "Gradle": "#02303a", + "Gradle Kotlin DSL": "#02303a", + "Grammatical Framework": "#ff0000", + "GraphQL": "#e10098", + "Graphviz (DOT)": "#2596be", + "Groovy": "#4298b8", + "Groovy Server Pages": "#4298b8", + "HAProxy": "#106da9", + "HCL": "#844FBA", + "HIP": "#4F3A4F", + "HLSL": "#aace60", + "HOCON": "#9ff8ee", + "HTML": "#e34c26", + "HTML+ECR": "#2e1052", + "HTML+EEX": "#6e4a7e", + "HTML+ERB": "#701516", + "HTML+PHP": "#4f5d95", + "HTML+Razor": "#512be4", + "HTTP": "#005C9C", + "HXML": "#f68712", + "Hack": "#878787", + "Haml": "#ece2a9", + "Handlebars": "#f7931e", + "Harbour": "#0e60e3", + "Hare": "#9d7424", + "Haskell": "#5e5086", + "Haxe": "#df7900", + "HiveQL": "#dce200", + "HolyC": "#ffefaf", + "Hosts File": "#308888", + "Hurl": "#FF0288", + "Hy": "#7790B2", + "IDL": "#a3522f", + "IGOR Pro": "#0000cc", + "IL Assembly": "#512BD4", + "INI": "#d1dbe0", + "ISPC": "#2D68B1", + "Idris": "#b30000", + "Ignore List": "#000000", + "ImageJ Macro": "#99AAFF", + "Imba": "#16cec6", + "Inno Setup": "#264b99", + "Io": "#a9188d", + "Ioke": "#078193", + "Isabelle": "#FEFE00", + "Isabelle ROOT": "#FEFE00", + "J": "#9EEDFF", + "JAR Manifest": "#b07219", + "JCL": "#d90e09", + "JFlex": "#DBCA00", + "JSON": "#292929", + "JSON with Comments": "#292929", + "JSON5": "#267CB9", + "JSONLD": "#0c479c", + "JSONiq": "#40d47e", + "Jac": "#FC792D", + "Jai": "#ab8b4b", + "Janet": "#0886a5", + "Jasmin": "#d03600", + "Java": "#b07219", + "Java Properties": "#2A6277", + "Java Server Pages": "#2A6277", + "Java Template Engine": "#2A6277", + "JavaScript": "#f1e05a", + "JavaScript+ERB": "#f1e05a", + "Jest Snapshot": "#15c213", + "JetBrains MPS": "#21D789", + "Jinja": "#a52a22", + "Jison": "#56b3cb", + "Jison Lex": "#56b3cb", + "Jolie": "#843179", + "Jsonnet": "#0064bd", + "Julia": "#a270ba", + "Julia REPL": "#a270ba", + "Jupyter Notebook": "#DA5B0B", + "Just": "#384d54", + "KCL": "#7ABABF", + "KDL": "#ffb3b3", + "KFramework": "#4195c5", + "KRL": "#28430A", + "Kaitai Struct": "#773b37", + "KakouneScript": "#6f8042", + "KerboScript": "#41adf0", + "KiCad Layout": "#2f4aab", + "KiCad Legacy Layout": "#2f4aab", + "KiCad Schematic": "#2f4aab", + "KoLmafia ASH": "#B9D9B9", + "Koka": "#215166", + "Kotlin": "#A97BFF", + "LFE": "#4C3023", + "LLVM": "#185619", + "LOLCODE": "#cc9900", + "LSL": "#3d9970", + "LabVIEW": "#fede06", + "Lambdapi": "#8027a3", + "Langium": "#2c8c87", + "Lark": "#2980B9", + "Lasso": "#999999", + "Latte": "#f2a542", + "Leo": "#C4FFC2", + "Less": "#1d365d", + "Lex": "#DBCA00", + "LigoLANG": "#0e74ff", + "LilyPond": "#9ccc7c", + "Liquid": "#67b8de", + "Liquidsoap": "#990066", + "Literate Agda": "#315665", + "Literate CoffeeScript": "#244776", + "Literate Haskell": "#5e5086", + "LiveCode Script": "#0c5ba5", + "LiveScript": "#499886", + "Logtalk": "#295b9a", + "LookML": "#652B81", + "Lua": "#000080", + "Luau": "#00A2FF", + "M3U": "#179C7D", + "MATLAB": "#e16737", + "MAXScript": "#00a6a6", + "MDX": "#fcb32c", + "MLIR": "#5EC8DB", + "MQL4": "#62A8D6", + "MQL5": "#4A76B8", + "MTML": "#b7e1f4", + "Macaulay2": "#d8ffff", + "Makefile": "#427819", + "Mako": "#7e858d", + "Markdown": "#083fa1", + "Marko": "#42bff2", + "Mask": "#f97732", + "Mathematical Programming System": "#0530ad", + "Max": "#c4a79c", + "MeTTa": "#6a5acd", + "Mercury": "#ff2b2b", + "Mermaid": "#ff3670", + "Meson": "#007800", + "Metal": "#8f14e9", + "MiniYAML": "#ff1111", + "MiniZinc": "#06a9e6", + "Mint": "#02b046", + "Mirah": "#c7a938", + "Modelica": "#de1d31", + "Modula-2": "#10253f", + "Modula-3": "#223388", + "Mojo": "#ff4c1f", + "Monkey C": "#8D6747", + "MoonBit": "#b92381", + "MoonScript": "#ff4585", + "Motoko": "#fbb03b", + "Motorola 68K Assembly": "#005daa", + "Move": "#4a137a", + "Mustache": "#724b3b", + "NCL": "#28431f", + "NMODL": "#00356B", + "NPM Config": "#cb3837", + "NWScript": "#111522", + "Nasal": "#1d2c4e", + "Nearley": "#990000", + "Nemerle": "#3d3c6e", + "NetLinx": "#0aa0ff", + "NetLinx+ERB": "#747faa", + "NetLogo": "#ff6375", + "NewLisp": "#87AED7", + "Nextflow": "#3ac486", + "Nginx": "#009639", + "Nickel": "#E0C3FC", + "Nim": "#ffc200", + "Nit": "#009917", + "Nix": "#7e7eff", + "Noir": "#2f1f49", + "Nu": "#c9df40", + "NumPy": "#9C8AF9", + "Nunjucks": "#3d8137", + "Nushell": "#4E9906", + "OASv2-json": "#85ea2d", + "OASv2-yaml": "#85ea2d", + "OASv3-json": "#85ea2d", + "OASv3-yaml": "#85ea2d", + "OCaml": "#ef7a08", + "OMNeT++ MSG": "#a0e0a0", + "OMNeT++ NED": "#08607c", + "ObjectScript": "#424893", + "Objective-C": "#438eff", + "Objective-C++": "#6866fb", + "Objective-J": "#ff0c5a", + "Odin": "#60AFFE", + "Omgrofl": "#cabbff", + "Opal": "#f7ede0", + "Open Policy Agent": "#7d9199", + "OpenAPI Specification v2": "#85ea2d", + "OpenAPI Specification v3": "#85ea2d", + "OpenCL": "#ed2e2d", + "OpenEdge ABL": "#5ce600", + "OpenQASM": "#AA70FF", + "OpenSCAD": "#e5cd45", + "Option List": "#476732", + "Org": "#77aa99", + "OverpassQL": "#cce2aa", + "Oxygene": "#cdd0e3", + "Oz": "#fab738", + "P4": "#7055b5", + "PDDL": "#0d00ff", + "PEG.js": "#234d6b", + "PHP": "#4F5D95", + "PLSQL": "#dad8d8", + "PLpgSQL": "#336790", + "POV-Ray SDL": "#6bac65", + "Pact": "#F7A8B8", + "Pan": "#cc0000", + "Papyrus": "#6600cc", + "Parrot": "#f3ca0a", + "Pascal": "#E3F171", + "Pawn": "#dbb284", + "Pep8": "#C76F5B", + "Perl": "#0298c3", + "PicoLisp": "#6067af", + "PigLatin": "#fcd7de", + "Pike": "#005390", + "Pip Requirements": "#FFD343", + "Pkl": "#6b9543", + "PlantUML": "#fbbd16", + "PogoScript": "#d80074", + "Polar": "#ae81ff", + "Portugol": "#f8bd00", + "PostCSS": "#dc3a0c", + "PostScript": "#da291c", + "PowerBuilder": "#8f0f8d", + "PowerShell": "#012456", + "Praat": "#c8506d", + "Prisma": "#0c344b", + "Processing": "#0096D8", + "Procfile": "#3B2F63", + "Prolog": "#74283c", + "Promela": "#de0000", + "Propeller Spin": "#7fa2a7", + "Pug": "#a86454", + "Puppet": "#302B6D", + "PureBasic": "#5a6986", + "PureScript": "#1D222D", + "Pyret": "#ee1e10", + "Python": "#3572A5", + "Python console": "#3572A5", + "Python traceback": "#3572A5", + "Q#": "#fed659", + "QML": "#44a51c", + "Qt Script": "#00b841", + "Quake": "#882233", + "QuakeC": "#975777", + "QuickBASIC": "#008080", + "Quint": "#9d6ce5", + "R": "#198CE7", + "RAML": "#77d9fb", + "RAScript": "#2C97FA", + "RBS": "#701516", + "RDoc": "#701516", + "REXX": "#d90e09", + "RMarkdown": "#198ce7", + "RON": "#a62c00", + "ROS Interface": "#22314e", + "RPGLE": "#2BDE21", + "RUNOFF": "#665a4e", + "Racket": "#3c5caa", + "Ragel": "#9d5200", + "Raku": "#0000fb", + "Rascal": "#fffaa0", + "ReScript": "#ed5051", + "Reason": "#ff5847", + "ReasonLIGO": "#ff5847", + "Rebol": "#358a5b", + "Record Jar": "#0673ba", + "Red": "#f50000", + "Regular Expression": "#009a00", + "Ren'Py": "#ff7f7f", + "Rez": "#FFDAB3", + "Ring": "#2D54CB", + "Riot": "#A71E49", + "RobotFramework": "#00c0b5", + "Roc": "#7c38f5", + "Rocq Prover": "#d0b68c", + "Roff": "#ecdebe", + "Roff Manpage": "#ecdebe", + "Rouge": "#cc0088", + "RouterOS Script": "#DE3941", + "Ruby": "#701516", + "Rust": "#dea584", + "SAS": "#B34936", + "SCSS": "#c6538c", + "SPARQL": "#0C4597", + "SQF": "#3F3F3F", + "SQL": "#e38c00", + "SQLPL": "#e38c00", + "SRecode Template": "#348a34", + "STL": "#373b5e", + "SVG": "#ff9900", + "Sail": "#259dd5", + "SaltStack": "#646464", + "Sass": "#a53b70", + "Scala": "#c22d40", + "Scaml": "#bd181a", + "Scenic": "#fdc700", + "Scheme": "#1e4aec", + "Scilab": "#ca0f21", + "Self": "#0579aa", + "ShaderLab": "#222c37", + "Shell": "#89e051", + "ShellCheck Config": "#cecfcb", + "Shen": "#120F14", + "Simple File Verification": "#C9BFED", + "Singularity": "#64E6AD", + "Slang": "#1fbec9", + "Slash": "#007eff", + "Slice": "#003fa2", + "Slim": "#2b2b2b", + "Slint": "#2379F4", + "SmPL": "#c94949", + "Smalltalk": "#596706", + "Smarty": "#f0c040", + "Smithy": "#c44536", + "Snakemake": "#419179", + "Solidity": "#AA6746", + "SourcePawn": "#f69e1d", + "SpiceDB Schema": "#a5318a", + "Squirrel": "#800000", + "Stan": "#b2011d", + "Standard ML": "#dc566d", + "Starlark": "#76d275", + "Stata": "#1a5f91", + "StringTemplate": "#3fb34f", + "Stylus": "#ff6347", + "SubRip Text": "#9e0101", + "SugarSS": "#2fcc9f", + "SuperCollider": "#46390b", + "SurrealQL": "#ff00a0", + "Survex data": "#ffcc99", + "Svelte": "#ff3e00", + "Sway": "#00F58C", + "Sweave": "#198ce7", + "Swift": "#F05138", + "SystemVerilog": "#DAE1C2", + "TI Program": "#A0AA87", + "TL-Verilog": "#C40023", + "TLA": "#4b0079", + "TMDL": "#f0c913", + "TOML": "#9c4221", + "TSQL": "#e38c00", + "TSV": "#237346", + "TSX": "#3178c6", + "TXL": "#0178b8", + "Tact": "#48b5ff", + "Talon": "#333333", + "Tcl": "#e4cc98", + "TeX": "#3D6117", + "Teal": "#00B1BC", + "Terra": "#00004c", + "Terraform Template": "#7b42bb", + "TextGrid": "#c8506d", + "TextMate Properties": "#df66e4", + "Textile": "#ffe7ac", + "Thrift": "#D12127", + "Toit": "#c2c9fb", + "Tools": "#a371f7", + "Tor Config": "#59316b", + "Tree-sitter Query": "#8ea64c", + "Turing": "#cf142b", + "Twig": "#c1d026", + "TypeScript": "#3178c6", + "TypeSpec": "#4A3665", + "Typst": "#239dad", + "Unified Parallel C": "#4e3617", + "Unity3D Asset": "#222c37", + "Uno": "#9933cc", + "UnrealScript": "#a54c4d", + "Untyped Plutus Core": "#36adbd", + "UrWeb": "#ccccee", + "V": "#4f87c4", + "VBA": "#867db1", + "VBScript": "#15dcdc", + "VCL": "#148AA8", + "VHDL": "#adb2cb", + "Vala": "#a56de2", + "Valve Data Format": "#f26025", + "Velocity Template Language": "#507cff", + "Vento": "#ff0080", + "Verilog": "#b2b7f8", + "Vim Help File": "#199f4b", + "Vim Script": "#199f4b", + "Vim Snippet": "#199f4b", + "Visual Basic .NET": "#945db7", + "Visual Basic 6.0": "#2c6353", + "Volt": "#1F1F1F", + "Vue": "#41b883", + "Vyper": "#9F4CF2", + "WDL": "#42f1f4", + "WGSL": "#1a5e9a", + "Web Ontology Language": "#5b70bd", + "WebAssembly": "#04133b", + "WebAssembly Interface Type": "#6250e7", + "Whiley": "#d5c397", + "Wikitext": "#fc5757", + "Windows Registry Entries": "#52d5ff", + "Witcher Script": "#ff0000", + "Wolfram Language": "#dd1100", + "Wollok": "#a23738", + "World of Warcraft Addon Data": "#f7e43f", + "Wren": "#383838", + "X10": "#4B6BEF", + "XC": "#99DA07", + "XML": "#0060ac", + "XML Property List": "#0060ac", + "XQuery": "#5232e7", + "XSLT": "#EB8CEB", + "Xmake": "#22a079", + "Xojo": "#81bd41", + "Xonsh": "#285EEF", + "Xtend": "#24255d", + "YAML": "#cb171e", + "YARA": "#220000", + "YASnippet": "#32AB90", + "Yacc": "#4B6C4B", + "Yul": "#794932", + "ZAP": "#0d665e", + "ZIL": "#dc75e5", + "ZenScript": "#00BCD1", + "Zephir": "#118f9e", + "Zig": "#ec915c", + "Zimpl": "#d67711", + "Zmodel": "#ff7100", + "crontab": "#ead7ac", + "eC": "#913960", + "fish": "#4aae47", + "hoon": "#00b171", + "iCalendar": "#ec564c", + "jq": "#c7254e", + "kvlang": "#1da6e0", + "mIRC Script": "#3d57c3", + "mcfunction": "#E22837", + "mdsvex": "#5f9ea0", + "mupad": "#244963", + "nanorc": "#2d004d", + "nesC": "#94B0C7", + "ooc": "#b0b77e", + "q": "#0040cd", + "reStructuredText": "#141414", + "sed": "#64b970", + "templ": "#66D0DD", + "vCard": "#ee2647", + "wisp": "#7582D1", + "xBase": "#403a40" + }, + "ext": { + "1": "Roff", + "1in": "Roff", + "1m": "Roff", + "1x": "Roff", + "2": "Roff", + "2da": "2-Dimensional Array", + "3": "Roff", + "3in": "Roff", + "3m": "Roff", + "3p": "Roff", + "3pm": "Roff", + "3qt": "Roff", + "3x": "Roff", + "4": "Roff", + "4dform": "JSON", + "4dm": "4D", + "4dproject": "JSON", + "4gl": "Genero 4gl", + "4th": "Forth", + "5": "Roff", + "6": "Roff", + "6pl": "Raku", + "6pm": "Raku", + "7": "Roff", + "8": "Roff", + "8xp": "TI Program", + "8xp.txt": "TI Program", + "9": "Roff", + "_coffee": "CoffeeScript", + "_js": "JavaScript", + "_ls": "LiveScript", + "a51": "Assembly", + "abap": "ABAP", + "action": "ROS Interface", + "ada": "Ada", + "adb": "Ada", + "adml": "XML", + "admx": "XML", + "ado": "Stata", + "adoc": "AsciiDoc", + "adp": "Tcl", + "ads": "Ada", + "afm": "Adobe Font Metrics", + "agc": "Assembly", + "agda": "Agda", + "ahk": "AutoHotkey", + "ahkl": "AutoHotkey", + "aidl": "AIDL", + "aj": "AspectJ", + "ak": "Aiken", + "al": "AL", + "alg": "ALGOL", + "als": "Alloy", + "ampl": "AMPL", + "angelscript": "AngelScript", + "anim": "Unity3D Asset", + "ant": "XML", + "antlers.html": "Antlers", + "antlers.php": "Antlers", + "antlers.xml": "Antlers", + "apacheconf": "ApacheConf", + "apex": "Apex", + "apib": "API Blueprint", + "apl": "APL", + "app": "Erlang", + "app.src": "Erlang", + "applescript": "AppleScript", + "arc": "Arc", + "arr": "Pyret", + "as": "ActionScript", + "asax": "ASP.NET", + "asc": "AGS Script", + "asciidoc": "AsciiDoc", + "ascx": "ASP.NET", + "asd": "Common Lisp", + "asddls": "ABAP CDS", + "ash": "KoLmafia ASH", + "ashx": "ASP.NET", + "asm": "Assembly", + "asmx": "ASP.NET", + "asp": "Classic ASP", + "aspx": "ASP.NET", + "asset": "Unity3D Asset", + "astro": "Astro", + "asy": "Asymptote", + "au3": "AutoIt", + "aug": "Augeas", + "auk": "Awk", + "aux": "TeX", + "avdl": "Avro IDL", + "avsc": "JSON", + "aw": "PHP", + "awk": "Awk", + "axaml": "XML", + "axd": "ASP.NET", + "axi": "NetLinx", + "axi.erb": "NetLinx+ERB", + "axml": "XML", + "axs": "NetLinx", + "axs.erb": "NetLinx+ERB", + "b": "Brainfuck", + "bal": "Ballerina", + "bas": "B4X", + "bash": "Shell", + "bat": "Batchfile", + "bats": "Shell", + "bb": "BitBake", + "bbappend": "BitBake", + "bbclass": "BitBake", + "bbx": "TeX", + "bdy": "PLSQL", + "be": "Berry", + "bf": "Beef", + "bi": "FreeBASIC", + "bib": "TeX", + "bibtex": "TeX", + "bicep": "Bicep", + "bicepparam": "Bicep", + "bison": "Yacc", + "blade": "Blade", + "blade.php": "Blade", + "bmx": "BlitzMax", + "bones": "JavaScript", + "boo": "Boo", + "boot": "Clojure", + "bpl": "Boogie", + "bqn": "BQN", + "brd": "KiCad Legacy Layout", + "brs": "Brightscript", + "bru": "Bru", + "bs": "Bluespec", + "bsl": "1C Enterprise", + "bst": "BuildStream", + "bsv": "Bluespec", + "builder": "Ruby", + "builds": "XML", + "bzl": "Starlark", + "c": "C", + "c++": "C++", + "c3": "C3", + "cabal": "Cabal Config", + "caddyfile": "Caddyfile", + "cairo": "Cairo", + "cake": "C#", + "capnp": "Cap'n Proto", + "carbon": "Carbon", + "cats": "C", + "cbx": "TeX", + "cc": "C++", + "ccproj": "XML", + "ccxml": "XML", + "cdc": "Cadence", + "cdf": "Wolfram Language", + "cds": "CAP CDS", + "ceylon": "Ceylon", + "cfc": "ColdFusion", + "cfg": "HAProxy", + "cfm": "ColdFusion", + "cfml": "ColdFusion", + "cgi": "Perl", + "cginc": "HLSL", + "ch": "xBase", + "chpl": "Chapel", + "circom": "Circom", + "cirru": "Cirru", + "cj": "Cangjie", + "cjs": "JavaScript", + "cjsx": "CoffeeScript", + "ck": "ChucK", + "cl": "C", + "cl2": "Clojure", + "clar": "Clarity", + "click": "Click", + "clixml": "XML", + "clj": "Clojure", + "cljc": "Clojure", + "cljs": "Clojure", + "cljs.hl": "Clojure", + "cljscm": "Clojure", + "cljx": "Clojure", + "clp": "CLIPS", + "cls": "Apex", + "clue": "Clue", + "clw": "Clarion", + "cmake": "CMake", + "cmake.in": "CMake", + "cmd": "Batchfile", + "cmp": "Gerber Image", + "cnc": "G-code", + "cnf": "INI", + "cocci": "SmPL", + "code-snippets": "JSON", + "code-workspace": "JSON", + "coffee": "CoffeeScript", + "coffee.md": "CoffeeScript", + "command": "Shell", + "containerfile": "Dockerfile", + "cook": "Cooklang", + "coq": "Rocq Prover", + "cp": "Component Pascal", + "cpp": "C++", + "cppm": "C++", + "cproject": "XML", + "cps": "Component Pascal", + "cql": "CQL", + "cr": "Crystal", + "cs": "C#", + "cs.pp": "C#", + "csc": "GSC", + "cscfg": "XML", + "csd": "Csound Document", + "csdef": "XML", + "cshtml": "HTML", + "csl": "XML", + "cson": "CSON", + "csproj": "XML", + "css": "CSS", + "csv": "CSV", + "csx": "C#", + "ct": "XML", + "ctl": "Visual Basic 6.0", + "ctp": "PHP", + "cts": "TypeScript", + "cu": "Cuda", + "cue": "CUE", + "cuh": "Cuda", + "curry": "Curry", + "cwl": "Common Workflow Language", + "cxx": "C++", + "cylc": "INI", + "cyp": "Cypher", + "cypher": "Cypher", + "d": "D", + "d2": "D2", + "dae": "COLLADA", + "darcspatch": "Darcs Patch", + "dart": "Dart", + "das": "Daslang", + "dats": "ATS", + "db2": "SQLPL", + "dcl": "Clean", + "ddl": "PLSQL", + "decls": "BlitzBasic", + "depproj": "XML", + "dfm": "Pascal", + "dfy": "Dafny", + "dhall": "Dhall", + "di": "D", + "dita": "XML", + "ditamap": "XML", + "ditaval": "XML", + "djs": "Dogescript", + "dll.config": "XML", + "dlm": "IDL", + "dm": "DM", + "do": "Stata", + "dockerfile": "Dockerfile", + "dof": "INI", + "doh": "Stata", + "dot": "Graphviz (DOT)", + "dotsettings": "XML", + "dpatch": "Darcs Patch", + "dpr": "Pascal", + "druby": "Mirah", + "dsc": "DenizenScript", + "dsp": "Faust", + "dsr": "Visual Basic 6.0", + "dtx": "TeX", + "duby": "Mirah", + "dwl": "DataWeave", + "dyalog": "APL", + "dyl": "Dylan", + "dylan": "Dylan", + "e": "E", + "eb": "Python", + "ebuild": "Shell", + "ec": "eC", + "ecl": "ECL", + "eclass": "Shell", + "eclxml": "ECL", + "ecr": "HTML", + "ect": "EJS", + "edge": "Edge", + "edgeql": "EdgeQL", + "editorconfig": "INI", + "eh": "eC", + "ejs": "EJS", + "ejs.t": "EJS", + "el": "Emacs Lisp", + "eliom": "OCaml", + "eliomi": "OCaml", + "elm": "Elm", + "elv": "Elvish", + "em": "EmberScript", + "emacs": "Emacs Lisp", + "emacs.desktop": "Emacs Lisp", + "emberscript": "EmberScript", + "env": "Dotenv", + "epj": "JavaScript", + "eps": "PostScript", + "epsi": "PostScript", + "eq": "EQ", + "erb": "HTML", + "erb.deface": "HTML", + "erl": "Erlang", + "es": "Erlang", + "es6": "JavaScript", + "escript": "Erlang", + "esdl": "EdgeQL", + "ex": "Elixir", + "exs": "Elixir", + "eye": "Ruby", + "f": "Fortran", + "f03": "Fortran", + "f08": "Fortran", + "f77": "Fortran", + "f90": "Fortran", + "f95": "Fortran", + "factor": "Factor", + "fan": "Fantom", + "fancypack": "Fancy", + "fbs": "FlatBuffers", + "fcgi": "Lua", + "feature": "Gherkin", + "filters": "XML", + "fir": "FIRRTL", + "fish": "Shell", + "flex": "Lex", + "flf": "FIGlet Font", + "flix": "Flix", + "flux": "FLUX", + "fnc": "PLSQL", + "fnl": "Fennel", + "for": "Fortran", + "forth": "Forth", + "fp": "GLSL", + "fpp": "Fortran", + "fr": "Frege", + "frag": "GLSL", + "frg": "GLSL", + "frm": "VBA", + "frt": "Forth", + "fs": "F#", + "fsh": "GLSL", + "fshader": "GLSL", + "fsi": "F#", + "fsproj": "XML", + "fst": "F*", + "fsti": "F*", + "fsx": "F#", + "fth": "Forth", + "ftl": "Fluent", + "ftlh": "FreeMarker", + "fun": "Standard ML", + "fut": "Futhark", + "fx": "FLUX", + "fxh": "HLSL", + "fxml": "XML", + "fy": "Fancy", + "g": "G-code", + "g4": "ANTLR", + "gaml": "GAML", + "gap": "GAP", + "gawk": "Awk", + "gbl": "Gerber Image", + "gbo": "Gerber Image", + "gbp": "Gerber Image", + "gbr": "Gerber Image", + "gbs": "Gerber Image", + "gco": "G-code", + "gcode": "G-code", + "gd": "GDScript", + "gdnlib": "Godot Resource", + "gdns": "Godot Resource", + "gdshader": "GDShader", + "gdshaderinc": "GDShader", + "ged": "GEDCOM", + "gemspec": "Ruby", + "geo": "GLSL", + "geojson": "JSON", + "geom": "GLSL", + "gf": "Grammatical Framework", + "gi": "GAP", + "gitconfig": "INI", + "gitignore": "Ignore List", + "gjs": "JavaScript", + "gko": "Gerber Image", + "glade": "XML", + "gleam": "Gleam", + "glf": "Glyph", + "glsl": "GLSL", + "glslf": "GLSL", + "glslv": "GLSL", + "gltf": "JSON", + "gmi": "Gemini", + "gml": "Game Maker Language", + "gms": "GAMS", + "gmx": "XML", + "gnu": "Gnuplot", + "gnuplot": "Gnuplot", + "go": "Go", + "god": "Ruby", + "gohtml": "Go Template", + "golo": "Golo", + "gotmpl": "Go Template", + "gp": "Gnuplot", + "gpb": "Gerber Image", + "gpt": "Gerber Image", + "gpx": "XML", + "gql": "GraphQL", + "grace": "Grace", + "gradle": "Gradle", + "gradle.kts": "Gradle", + "graphql": "GraphQL", + "graphqls": "GraphQL", + "groovy": "Groovy", + "grt": "Groovy", + "grxml": "XML", + "gs": "Genie", + "gsc": "GSC", + "gsh": "GSC", + "gshader": "GLSL", + "gsp": "Groovy", + "gst": "Gosu", + "gsx": "Gosu", + "gtl": "Gerber Image", + "gto": "Gerber Image", + "gtp": "Gerber Image", + "gtpl": "Groovy", + "gts": "TypeScript", + "gv": "Graphviz (DOT)", + "gvy": "Groovy", + "gyp": "Python", + "gypi": "Python", + "h": "C", + "h++": "C++", + "h.in": "C", + "ha": "Hare", + "hack": "Hack", + "haml": "Haml", + "haml.deface": "Haml", + "handlebars": "Handlebars", + "har": "JSON", + "hats": "ATS", + "hb": "Harbour", + "hbs": "Handlebars", + "hc": "HolyC", + "hcl": "HCL", + "heex": "HTML", + "hh": "C++", + "hhi": "Hack", + "hic": "Clojure", + "hip": "HIP", + "hlsl": "HLSL", + "hlsli": "HLSL", + "hocon": "HOCON", + "hoon": "hoon", + "hpp": "C++", + "hqf": "SQF", + "hql": "HiveQL", + "hrl": "Erlang", + "hs": "Haskell", + "hs-boot": "Haskell", + "hsc": "Haskell", + "hta": "HTML", + "htm": "HTML", + "html": "HTML", + "html.eex": "HTML", + "html.hl": "HTML", + "html.tmpl": "Go Template", + "http": "HTTP", + "hurl": "Hurl", + "hx": "Haxe", + "hxml": "HXML", + "hxsl": "Haxe", + "hxx": "C++", + "hy": "Hy", + "hzp": "XML", + "i": "Assembly", + "i3": "Modula-3", + "ical": "iCalendar", + "ice": "Slice", + "iced": "CoffeeScript", + "icl": "Clean", + "icls": "XML", + "ics": "iCalendar", + "idc": "C", + "idr": "Idris", + "ig": "Modula-3", + "ihlp": "Stata", + "ijm": "ImageJ Macro", + "ijs": "J", + "ik": "Ioke", + "il": "IL Assembly", + "ily": "LilyPond", + "imba": "Imba", + "iml": "XML", + "inc": "Assembly", + "ini": "INI", + "inl": "C++", + "ino": "C++", + "ins": "TeX", + "intr": "Dylan", + "io": "Io", + "iol": "Jolie", + "ipf": "IGOR Pro", + "ipp": "C++", + "ipynb": "Jupyter Notebook", + "isl": "Inno Setup", + "ispc": "ISPC", + "iss": "Inno Setup", + "iuml": "PlantUML", + "ivy": "XML", + "ixx": "C++", + "j": "Jasmin", + "j2": "Jinja", + "jac": "Jac", + "jade": "Pug", + "jai": "Jai", + "jake": "JavaScript", + "janet": "Janet", + "jav": "Java", + "java": "Java", + "javascript": "JavaScript", + "jbuilder": "Ruby", + "jcl": "JCL", + "jelly": "XML", + "jflex": "Lex", + "jinja": "Jinja", + "jinja2": "Jinja", + "jison": "Yacc", + "jisonlex": "Lex", + "jl": "Julia", + "jq": "JSONiq", + "js": "JavaScript", + "js.erb": "JavaScript", + "jsb": "JavaScript", + "jscad": "JavaScript", + "jsfl": "JavaScript", + "jsh": "Java", + "jslib": "JavaScript", + "jsm": "JavaScript", + "json": "JSON", + "json-tmlanguage": "JSON", + "json.example": "JSON", + "json5": "JSON5", + "jsonc": "JSON", + "jsonl": "JSON", + "jsonld": "JSONLD", + "jsonnet": "Jsonnet", + "jsp": "Java", + "jspre": "JavaScript", + "jsproj": "XML", + "jss": "JavaScript", + "jst": "EJS", + "jsx": "JavaScript", + "jte": "Java", + "just": "Just", + "k": "KCL", + "kak": "KakouneScript", + "kdl": "KDL", + "kicad_mod": "KiCad Layout", + "kicad_pcb": "KiCad Layout", + "kicad_sch": "KiCad Schematic", + "kicad_sym": "KiCad Schematic", + "kicad_wks": "KiCad Layout", + "kid": "Genshi", + "kk": "Koka", + "kml": "XML", + "kojo": "Scala", + "krl": "KRL", + "ks": "KerboScript", + "ksh": "Shell", + "ksy": "Kaitai Struct", + "kt": "Kotlin", + "ktm": "Kotlin", + "kts": "Kotlin", + "kv": "kvlang", + "l": "Common Lisp", + "lagda": "Agda", + "langium": "Langium", + "lark": "Lark", + "las": "Lasso", + "lasso": "Lasso", + "lasso8": "Lasso", + "lasso9": "Lasso", + "latte": "Latte", + "launch": "XML", + "lbx": "TeX", + "leex": "HTML", + "lektorproject": "INI", + "leo": "Leo", + "less": "Less", + "lex": "Lex", + "lfe": "LFE", + "lgt": "Logtalk", + "lhs": "Haskell", + "libsonnet": "Jsonnet", + "lid": "Dylan", + "lidr": "Idris", + "ligo": "LigoLANG", + "linq": "C#", + "liq": "Liquidsoap", + "liquid": "Liquid", + "lisp": "Common Lisp", + "litcoffee": "CoffeeScript", + "livecodescript": "LiveCode Script", + "livemd": "Markdown", + "lkml": "LookML", + "ll": "LLVM", + "lmi": "Python", + "logtalk": "Logtalk", + "lol": "LOLCODE", + "lookml": "LookML", + "lp": "Answer Set Programming", + "lpr": "Pascal", + "ls": "LiveScript", + "lsl": "LSL", + "lslp": "LSL", + "lsp": "Common Lisp", + "ltx": "TeX", + "lua": "Lua", + "luau": "Luau", + "lvclass": "LabVIEW", + "lvlib": "LabVIEW", + "lvproj": "LabVIEW", + "ly": "LilyPond", + "m": "Objective-C", + "m2": "Macaulay2", + "m3": "Modula-3", + "m3u": "M3U", + "m3u8": "M3U", + "ma": "Wolfram Language", + "mak": "Makefile", + "make": "Makefile", + "makefile": "Makefile", + "mako": "Mako", + "man": "Roff", + "mao": "Mako", + "markdown": "Markdown", + "marko": "Marko", + "mask": "Mask", + "mat": "Unity3D Asset", + "mata": "Stata", + "matah": "Stata", + "mathematica": "Wolfram Language", + "matlab": "MATLAB", + "mawk": "Awk", + "maxhelp": "Max", + "maxpat": "Max", + "maxproj": "Max", + "mbt": "MoonBit", + "mc": "Monkey C", + "mcfunction": "mcfunction", + "mch": "B (Formal Method)", + "mcmeta": "JSON", + "mcr": "MAXScript", + "md": "Markdown", + "mdoc": "Roff", + "mdown": "Markdown", + "mdpolicy": "XML", + "mdwn": "Markdown", + "mdx": "MDX", + "me": "Roff", + "mediawiki": "Wikitext", + "mermaid": "Mermaid", + "meta": "Unity3D Asset", + "metal": "Metal", + "metta": "MeTTa", + "mg": "Modula-3", + "mint": "Mint", + "mir": "YAML", + "mirah": "Mirah", + "mjml": "XML", + "mjs": "JavaScript", + "mk": "Makefile", + "mkd": "Markdown", + "mkdn": "Markdown", + "mkdown": "Markdown", + "mkfile": "Makefile", + "mkii": "TeX", + "mkiv": "TeX", + "mkvi": "TeX", + "ml": "OCaml", + "ml4": "OCaml", + "mli": "OCaml", + "mligo": "LigoLANG", + "mlir": "MLIR", + "mll": "OCaml", + "mly": "OCaml", + "mm": "Objective-C++", + "mmd": "Mermaid", + "mo": "Modelica", + "mod": "Modula-2", + "mojo": "Mojo", + "moo": "Mercury", + "moon": "MoonScript", + "move": "Move", + "mpl": "JetBrains MPS", + "mps": "JetBrains MPS", + "mq4": "MQL4", + "mq5": "MQL5", + "mqh": "MQL4", + "mrc": "mIRC Script", + "ms": "MAXScript", + "msd": "JetBrains MPS", + "msg": "OMNeT++ MSG", + "mspec": "Ruby", + "mt": "Wolfram Language", + "mtml": "MTML", + "mts": "TypeScript", + "mu": "mupad", + "mud": "ZIL", + "mustache": "Mustache", + "mxml": "XML", + "mxt": "Max", + "mysql": "SQL", + "mzn": "MiniZinc", + "n": "Nemerle", + "nanorc": "INI", + "nas": "Nasal", + "nasm": "Assembly", + "natvis": "XML", + "nawk": "Awk", + "nb": "Wolfram Language", + "nbp": "Wolfram Language", + "nc": "nesC", + "ncl": "NCL", + "ndproj": "XML", + "ne": "Nearley", + "nearley": "Nearley", + "ned": "OMNeT++ NED", + "nf": "Nextflow", + "nginx": "Nginx", + "nginxconf": "Nginx", + "nim": "Nim", + "nim.cfg": "Nim", + "nimble": "Nim", + "nimrod": "Nim", + "nims": "Nim", + "nit": "Nit", + "nix": "Nix", + "njk": "Nunjucks", + "njs": "JavaScript", + "nl": "NewLisp", + "nlogo": "NetLogo", + "nomad": "HCL", + "nproj": "XML", + "nqp": "Raku", + "nr": "Noir", + "nse": "Lua", + "nss": "NWScript", + "nu": "Nu", + "numpy": "Python", + "numpyw": "Python", + "numsc": "Python", + "nuspec": "XML", + "nut": "Squirrel", + "ny": "Common Lisp", + "odd": "XML", + "odin": "Odin", + "ol": "Jolie", + "omgrofl": "Omgrofl", + "ooc": "ooc", + "opal": "Opal", + "opencl": "C", + "orc": "Csound", + "org": "Org", + "os": "1C Enterprise", + "osm": "XML", + "outjob": "Altium Designer", + "overpassql": "OverpassQL", + "owl": "Web Ontology Language", + "oxygene": "Oxygene", + "oz": "Oz", + "p": "OpenEdge ABL", + "p4": "P4", + "p6": "Raku", + "p6l": "Raku", + "p6m": "Raku", + "p8": "Lua", + "pac": "JavaScript", + "pact": "Pact", + "pan": "Pan", + "parrot": "Parrot", + "pas": "Pascal", + "pascal": "Pascal", + "pat": "Max", + "pb": "PureBasic", + "pbi": "PureBasic", + "pbt": "PowerBuilder", + "pcbdoc": "Altium Designer", + "pck": "PLSQL", + "pcss": "CSS", + "pd_lua": "Lua", + "pddl": "PDDL", + "pde": "Processing", + "peggy": "PEG.js", + "pegjs": "PEG.js", + "pep": "Pep8", + "per": "Genero per", + "perl": "Perl", + "pfa": "PostScript", + "pgsql": "PLpgSQL", + "ph": "Perl", + "php": "PHP", + "php3": "PHP", + "php4": "PHP", + "php5": "PHP", + "phps": "PHP", + "phpt": "PHP", + "phtml": "HTML", + "pig": "PigLatin", + "pike": "Pike", + "pkb": "PLSQL", + "pkgproj": "XML", + "pkl": "Pkl", + "pks": "PLSQL", + "pl": "Perl", + "pl6": "Raku", + "plantuml": "PlantUML", + "plb": "PLSQL", + "plist": "XML", + "plot": "Gnuplot", + "pls": "PLSQL", + "plsql": "PLSQL", + "plt": "Gnuplot", + "pluginspec": "Ruby", + "plx": "Perl", + "pm": "Perl", + "pm6": "Raku", + "pml": "Promela", + "pmod": "Pike", + "podsl": "Common Lisp", + "podspec": "Ruby", + "pogo": "PogoScript", + "polar": "Polar", + "por": "Portugol", + "postcss": "CSS", + "pov": "POV-Ray SDL", + "pp": "Puppet", + "pprx": "REXX", + "praat": "Praat", + "prawn": "Ruby", + "prc": "PLSQL", + "prefab": "Unity3D Asset", + "prefs": "INI", + "prg": "xBase", + "prisma": "Prisma", + "prjpcb": "Altium Designer", + "pro": "Prolog", + "proj": "XML", + "prolog": "Prolog", + "properties": "Java Properties", + "props": "XML", + "prw": "xBase", + "ps": "PostScript", + "ps1": "PowerShell", + "ps1xml": "XML", + "psc": "Papyrus", + "psc1": "XML", + "psd1": "PowerShell", + "psgi": "Perl", + "psm1": "PowerShell", + "pt": "XML", + "pubxml": "XML", + "pug": "Pug", + "puml": "PlantUML", + "purs": "PureScript", + "pwn": "Pawn", + "pxd": "Cython", + "pxi": "Cython", + "py": "Python", + "py3": "Python", + "pyde": "Python", + "pyi": "Python", + "pyp": "Python", + "pyt": "Python", + "pytb": "Python", + "pyw": "Python", + "pyx": "Cython", + "q": "HiveQL", + "qasm": "OpenQASM", + "qbs": "QML", + "qc": "QuakeC", + "qhelp": "XML", + "ql": "CodeQL", + "qll": "CodeQL", + "qmd": "RMarkdown", + "qml": "QML", + "qnt": "Quint", + "qs": "Q#", + "r": "R", + "r2": "Rebol", + "r3": "Rebol", + "rabl": "Ruby", + "rake": "Ruby", + "raku": "Raku", + "rakumod": "Raku", + "raml": "RAML", + "rascript": "RAScript", + "razor": "HTML", + "rb": "Ruby", + "rbi": "Ruby", + "rbs": "Ruby", + "rbuild": "Ruby", + "rbw": "Ruby", + "rbx": "Ruby", + "rbxs": "Lua", + "rchit": "GLSL", + "rd": "R", + "rdf": "XML", + "rdoc": "RDoc", + "re": "Reason", + "reb": "Rebol", + "rebol": "Rebol", + "red": "Red", + "reds": "Red", + "reek": "YAML", + "reg": "Windows Registry Entries", + "regex": "Regular Expression", + "regexp": "Regular Expression", + "rego": "Open Policy Agent", + "rei": "Reason", + "religo": "LigoLANG", + "res": "ReScript", + "resi": "ReScript", + "resource": "RobotFramework", + "rest": "reStructuredText", + "rest.txt": "reStructuredText", + "resx": "XML", + "rex": "REXX", + "rexx": "REXX", + "rg": "Rouge", + "rhtml": "HTML", + "ring": "Ring", + "riot": "Riot", + "rkt": "Racket", + "rktd": "Racket", + "rktl": "Racket", + "rl": "Ragel", + "rmd": "RMarkdown", + "rmiss": "GLSL", + "rnh": "RUNOFF", + "rno": "RUNOFF", + "rnw": "Sweave", + "robot": "RobotFramework", + "roc": "Roc", + "rockspec": "Lua", + "roff": "Roff", + "ron": "RON", + "ronn": "Markdown", + "rpgle": "RPGLE", + "rpy": "Ren'Py", + "rq": "SPARQL", + "rs": "Rust", + "rs.in": "Rust", + "rsc": "Rascal", + "rss": "XML", + "rst": "reStructuredText", + "rst.txt": "reStructuredText", + "rsx": "R", + "ru": "Ruby", + "ruby": "Ruby", + "rviz": "YAML", + "s": "Assembly", + "sail": "Sail", + "sarif": "JSON", + "sas": "SAS", + "sass": "Sass", + "sats": "ATS", + "sbatch": "Shell", + "sbt": "Scala", + "sc": "SuperCollider", + "scad": "OpenSCAD", + "scala": "Scala", + "scaml": "Scaml", + "scd": "SuperCollider", + "sce": "Scilab", + "scenic": "Scenic", + "sch": "Scheme", + "schdoc": "Altium Designer", + "sci": "Scilab", + "scm": "Scheme", + "sco": "Csound Score", + "scpt": "AppleScript", + "scrbl": "Racket", + "scss": "SCSS", + "scxml": "XML", + "sdc": "Tcl", + "sed": "sed", + "self": "Self", + "sexp": "Common Lisp", + "sfproj": "XML", + "sfv": "Simple File Verification", + "sh": "Shell", + "sh.in": "Shell", + "shader": "ShaderLab", + "shen": "Shen", + "shproj": "XML", + "sig": "Standard ML", + "sj": "Objective-J", + "sjs": "JavaScript", + "sl": "Slash", + "slang": "Slang", + "sld": "Scheme", + "slim": "Slim", + "slint": "Slint", + "slnx": "XML", + "sls": "SaltStack", + "slurm": "Shell", + "sma": "Pawn", + "smithy": "Smithy", + "smk": "Python", + "sml": "Standard ML", + "snakefile": "Python", + "snap": "Jest Snapshot", + "snip": "Vim Snippet", + "snippet": "Vim Snippet", + "snippets": "Vim Snippet", + "sol": "Solidity", + "soy": "Closure Templates", + "sp": "SourcePawn", + "sparql": "SPARQL", + "spc": "PLSQL", + "spec": "Python", + "spin": "Propeller Spin", + "sps": "Scheme", + "sqf": "SQF", + "sql": "SQL", + "sqlrpgle": "RPGLE", + "sra": "PowerBuilder", + "srdf": "XML", + "srt": "SRecode Template", + "sru": "PowerBuilder", + "srv": "ROS Interface", + "srw": "PowerBuilder", + "ss": "Scheme", + "ssjs": "JavaScript", + "sss": "SugarSS", + "st": "Smalltalk", + "stan": "Stan", + "star": "Starlark", + "sthlp": "Stata", + "stl": "STL", + "story": "Gherkin", + "storyboard": "XML", + "sttheme": "XML", + "sty": "TeX", + "styl": "Stylus", + "sublime-build": "JSON", + "sublime-color-scheme": "JSON", + "sublime-commands": "JSON", + "sublime-completions": "JSON", + "sublime-keymap": "JSON", + "sublime-macro": "JSON", + "sublime-menu": "JSON", + "sublime-mousemap": "JSON", + "sublime-project": "JSON", + "sublime-settings": "JSON", + "sublime-snippet": "XML", + "sublime-syntax": "YAML", + "sublime-theme": "JSON", + "sublime-workspace": "JSON", + "sublime_metrics": "JSON", + "sublime_session": "JSON", + "surql": "SurrealQL", + "sv": "SystemVerilog", + "svelte": "Svelte", + "svg": "SVG", + "svh": "SystemVerilog", + "svx": "mdsvex", + "sw": "Sway", + "swift": "Swift", + "syntax": "YAML", + "t": "Perl", + "tab": "SQL", + "tac": "Python", + "tact": "Tact", + "tag": "Java", + "talon": "Talon", + "targets": "XML", + "tcc": "C++", + "tcl": "Tcl", + "tcl.in": "Tcl", + "templ": "templ", + "tesc": "GLSL", + "tese": "GLSL", + "tex": "TeX", + "textgrid": "TextGrid", + "textile": "Textile", + "tf": "HCL", + "tfstate": "JSON", + "tfstate.backup": "JSON", + "tftpl": "HCL", + "tfvars": "HCL", + "thor": "Ruby", + "thrift": "Thrift", + "thy": "Isabelle", + "tl": "Teal", + "tla": "TLA", + "tlv": "TL-Verilog", + "tm": "Tcl", + "tmac": "Roff", + "tmcommand": "XML", + "tmdl": "TMDL", + "tml": "XML", + "tmlanguage": "XML", + "tmpl": "Go Template", + "tmpreferences": "XML", + "tmsnippet": "XML", + "tmtheme": "XML", + "tmux": "Shell", + "toc": "TeX", + "tofu": "HCL", + "toit": "Toit", + "toml": "TOML", + "toml.example": "TOML", + "tool": "Shell", + "topojson": "JSON", + "tpb": "PLSQL", + "tpl": "Smarty", + "tpp": "C++", + "tps": "PLSQL", + "tres": "Godot Resource", + "trg": "PLSQL", + "trigger": "Apex", + "ts": "TypeScript", + "tscn": "Godot Resource", + "tsconfig.json": "JSON", + "tsp": "TypeSpec", + "tst": "GAP", + "tsv": "TSV", + "tsx": "TypeScript", + "tu": "Turing", + "twig": "Twig", + "txl": "TXL", + "txx": "C++", + "typ": "Typst", + "uc": "UnrealScript", + "udf": "SQL", + "udo": "Csound", + "ui": "XML", + "unity": "Unity3D Asset", + "uno": "Uno", + "upc": "C", + "uplc": "Untyped Plutus Core", + "ur": "UrWeb", + "urdf": "XML", + "url": "INI", + "urs": "UrWeb", + "ux": "XML", + "v": "Verilog", + "vala": "Vala", + "vapi": "Vala", + "vark": "Gosu", + "vb": "Visual Basic .NET", + "vba": "VBA", + "vbhtml": "Visual Basic .NET", + "vbproj": "XML", + "vbs": "VBScript", + "vcf": "vCard", + "vcl": "VCL", + "vcxproj": "XML", + "vdf": "Valve Data Format", + "veo": "Verilog", + "vert": "GLSL", + "vh": "SystemVerilog", + "vhd": "VHDL", + "vhdl": "VHDL", + "vhf": "VHDL", + "vhi": "VHDL", + "vho": "VHDL", + "vhost": "ApacheConf", + "vhs": "VHDL", + "vht": "VHDL", + "vhw": "VHDL", + "vim": "Vim Script", + "vimrc": "Vim Script", + "viw": "SQL", + "vmb": "Vim Script", + "volt": "Volt", + "vrx": "GLSL", + "vs": "GLSL", + "vsh": "GLSL", + "vshader": "GLSL", + "vsixmanifest": "XML", + "vssettings": "XML", + "vstemplate": "XML", + "vtl": "Velocity Template Language", + "vto": "Vento", + "vue": "Vue", + "vw": "PLSQL", + "vxml": "XML", + "vy": "Vyper", + "w": "CWeb", + "wast": "WebAssembly", + "wat": "WebAssembly", + "watchr": "Ruby", + "wdl": "WDL", + "webapp": "JSON", + "webmanifest": "JSON", + "wgsl": "WGSL", + "whiley": "Whiley", + "wiki": "Wikitext", + "wikitext": "Wikitext", + "wisp": "wisp", + "wit": "WebAssembly Interface Type", + "wixproj": "XML", + "wl": "Wolfram Language", + "wlk": "Wollok", + "wls": "Wolfram Language", + "wlt": "Wolfram Language", + "wlua": "Lua", + "workbook": "Markdown", + "workflow": "HCL", + "wren": "Wren", + "ws": "Witcher Script", + "wsdl": "XML", + "wsf": "XML", + "wsgi": "Python", + "wxi": "XML", + "wxl": "XML", + "wxs": "XML", + "x": "DirectX 3D File", + "x10": "X10", + "x3d": "XML", + "x68": "Assembly", + "xacro": "XML", + "xaml": "XML", + "xc": "XC", + "xdc": "Tcl", + "xht": "HTML", + "xhtml": "HTML", + "xib": "XML", + "xlf": "XML", + "xliff": "XML", + "xmi": "XML", + "xml": "XML", + "xml.dist": "XML", + "xmp": "XML", + "xojo_code": "Xojo", + "xojo_menu": "Xojo", + "xojo_report": "Xojo", + "xojo_script": "Xojo", + "xojo_toolbar": "Xojo", + "xojo_window": "Xojo", + "xproj": "XML", + "xpy": "Python", + "xq": "XQuery", + "xql": "XQuery", + "xqm": "XQuery", + "xquery": "XQuery", + "xqy": "XQuery", + "xrl": "Erlang", + "xsd": "XML", + "xsh": "Xonsh", + "xsjs": "JavaScript", + "xsjslib": "JavaScript", + "xsl": "XSLT", + "xslt": "XSLT", + "xspec": "XML", + "xtend": "Xtend", + "xul": "XML", + "xzap": "ZAP", + "y": "Yacc", + "yacc": "Yacc", + "yaml": "MiniYAML", + "yaml-tmlanguage": "YAML", + "yaml.sed": "YAML", + "yap": "Prolog", + "yar": "YARA", + "yara": "YARA", + "yasnippet": "YASnippet", + "yml": "YAML", + "yml.mysql": "YAML", + "yrl": "Erlang", + "yul": "Yul", + "yy": "Yacc", + "yyp": "JSON", + "zap": "ZAP", + "zcml": "XML", + "zed": "SpiceDB Schema", + "zep": "Zephir", + "zig": "Zig", + "zig.zon": "Zig", + "zil": "ZIL", + "zimpl": "Zimpl", + "zmodel": "Zmodel", + "zmpl": "Zimpl", + "zpl": "Zimpl", + "zs": "ZenScript", + "zsh": "Shell", + "zsh-theme": "Shell" + }, + "filename": { + ".abbrev_defs": "Emacs Lisp", + ".ackrc": "Option List", + ".all-contributorsrc": "JSON", + ".arcconfig": "JSON", + ".atomignore": "Ignore List", + ".auto-changelog": "JSON", + ".babelignore": "Ignore List", + ".babelrc": "JSON", + ".bash_aliases": "Shell", + ".bash_functions": "Shell", + ".bash_history": "Shell", + ".bash_logout": "Shell", + ".bash_profile": "Shell", + ".bashrc": "Shell", + ".browserslistrc": "Browserslist", + ".buckconfig": "INI", + ".bzrignore": "Ignore List", + ".c8rc": "JSON", + ".clang-format": "YAML", + ".clang-tidy": "YAML", + ".clangd": "YAML", + ".classpath": "XML", + ".coffeelintignore": "Ignore List", + ".coveragerc": "INI", + ".cproject": "XML", + ".cshrc": "Shell", + ".cvsignore": "Ignore List", + ".devcontainer.json": "JSON", + ".dockerignore": "Ignore List", + ".easignore": "Ignore List", + ".editorconfig": "INI", + ".eleventyignore": "Ignore List", + ".emacs": "Emacs Lisp", + ".emacs.desktop": "Emacs Lisp", + ".env": "Dotenv", + ".env.ci": "Dotenv", + ".env.dev": "Dotenv", + ".env.development": "Dotenv", + ".env.development.local": "Dotenv", + ".env.example": "Dotenv", + ".env.local": "Dotenv", + ".env.prod": "Dotenv", + ".env.production": "Dotenv", + ".env.sample": "Dotenv", + ".env.staging": "Dotenv", + ".env.template": "Dotenv", + ".env.test": "Dotenv", + ".env.testing": "Dotenv", + ".envrc": "Shell", + ".eslintignore": "Ignore List", + ".eslintrc.json": "JSON", + ".exrc": "Vim Script", + ".factor-boot-rc": "Factor", + ".factor-rc": "Factor", + ".flake8": "INI", + ".flaskenv": "Shell", + ".gclient": "Python", + ".gemrc": "YAML", + ".git-blame-ignore-revs": "Git Revision List", + ".gitattributes": "Git Attributes", + ".gitconfig": "INI", + ".gitignore": "Ignore List", + ".gitmodules": "INI", + ".gnus": "Emacs Lisp", + ".gvimrc": "Vim Script", + ".htaccess": "ApacheConf", + ".htmlhintrc": "JSON", + ".ignore": "Ignore List", + ".imgbotconfig": "JSON", + ".irbrc": "Ruby", + ".jscsrc": "JSON", + ".jshintrc": "JSON", + ".jslintrc": "JSON", + ".justfile": "Just", + ".kshrc": "Shell", + ".latexmkrc": "Perl", + ".login": "Shell", + ".luacheckrc": "Lua", + ".markdownlintignore": "Ignore List", + ".nanorc": "INI", + ".nodemonignore": "Ignore List", + ".npmignore": "Ignore List", + ".npmrc": "INI", + ".nvimrc": "Vim Script", + ".nycrc": "JSON", + ".oxlintrc.json": "JSON", + ".php": "PHP", + ".php_cs": "PHP", + ".php_cs.dist": "PHP", + ".prettierignore": "Ignore List", + ".profile": "Shell", + ".project": "XML", + ".pryrc": "Ruby", + ".pylintrc": "INI", + ".rprofile": "R", + ".rspec": "Option List", + ".scalafix.conf": "HOCON", + ".scalafmt.conf": "HOCON", + ".shellcheckrc": "ShellCheck Config", + ".simplecov": "Ruby", + ".spacemacs": "Emacs Lisp", + ".stylelintignore": "Ignore List", + ".swcrc": "JSON", + ".tern-config": "JSON", + ".tern-project": "JSON", + ".tm_properties": "TextMate Properties", + ".tmux.conf": "Shell", + ".vercelignore": "Ignore List", + ".vimrc": "Vim Script", + ".viper": "Emacs Lisp", + ".vscodeignore": "Ignore List", + ".watchmanconfig": "JSON", + ".xinitrc": "Shell", + ".xsession": "Shell", + ".yardopts": "Option List", + ".zlogin": "Shell", + ".zlogout": "Shell", + ".zprofile": "Shell", + ".zshenv": "Shell", + ".zshrc": "Shell", + "9fs": "Shell", + "_emacs": "Emacs Lisp", + "_helpers.tpl": "Go Template", + "_vimrc": "Vim Script", + "abbrev_defs": "Emacs Lisp", + "ack": "Perl", + "ackrc": "Option List", + "ant.xml": "Ant Build System", + "apache2.conf": "ApacheConf", + "api-extractor.json": "JSON", + "apkbuild": "Shell", + "app.config": "XML", + "appraisals": "Ruby", + "bash_aliases": "Shell", + "bash_logout": "Shell", + "bash_profile": "Shell", + "bashrc": "Shell", + "berksfile": "Ruby", + "brewfile": "Ruby", + "browserslist": "Browserslist", + "bsdmakefile": "Makefile", + "buck": "Starlark", + "build": "Starlark", + "build.bazel": "Starlark", + "build.xml": "Ant Build System", + "buildfile": "Ruby", + "buildozer.spec": "INI", + "bun.lock": "JSON", + "cabal.config": "Cabal Config", + "cabal.project": "Cabal Config", + "caddyfile": "Caddyfile", + "cakefile": "CoffeeScript", + "capfile": "Ruby", + "cargo.lock": "TOML", + "cargo.toml.orig": "TOML", + "cask": "Emacs Lisp", + "citation.cff": "YAML", + "cmakelists.txt": "CMake", + "commit_editmsg": "Git Commit", + "composer.lock": "JSON", + "containerfile": "Dockerfile", + "contents.lr": "Markdown", + "cpanfile": "Perl", + "crontab": "crontab", + "cshrc": "Shell", + "dangerfile": "Ruby", + "deliverfile": "Ruby", + "deno.lock": "JSON", + "deps": "Python", + "dev-requirements.txt": "Pip Requirements", + "devcontainer.json": "JSON", + "dockerfile": "Dockerfile", + "dune-project": "Dune", + "earthfile": "Earthly", + "eask": "Emacs Lisp", + "emakefile": "Erlang", + "eqnrc": "Roff", + "expr-dist": "R", + "fakefile": "Fancy", + "fastfile": "Ruby", + "firestore.rules": "Cloud Firestore Security Rules", + "flake.lock": "JSON", + "fp-lib-table": "KiCad Layout", + "gemfile": "Ruby", + "gemfile.lock": "Gemfile.lock", + "gitignore-global": "Ignore List", + "gitignore_global": "Ignore List", + "glide.lock": "YAML", + "gnumakefile": "Makefile", + "go.mod": "Go Module", + "go.sum": "Go Checksums", + "go.work": "Go Workspace", + "go.work.sum": "Go Checksums", + "gopkg.lock": "TOML", + "gradlew": "Shell", + "gradlew.bat": "Batchfile", + "guardfile": "Ruby", + "gvimrc": "Vim Script", + "haproxy.cfg": "HAProxy", + "hosts": "Hosts File", + "hosts.txt": "Hosts File", + "httpd.conf": "ApacheConf", + "installscript.qs": "Qt Script", + "jakefile": "JavaScript", + "jarfile": "Ruby", + "jenkinsfile": "Groovy", + "jsconfig.json": "JSON", + "justfile": "Just", + "kakrc": "KakouneScript", + "kbuild": "Makefile", + "kcl.mod": "KCL", + "kcl.mod.lock": "KCL", + "kshrc": "Shell", + "language-configuration.json": "JSON", + "language-subtag-registry.txt": "Record Jar", + "latexmkrc": "Perl", + "lexer.x": "Lex", + "login": "Shell", + "m3makefile": "Quake", + "m3overrides": "Quake", + "makefile": "Makefile", + "makefile.am": "Makefile", + "makefile.boot": "Makefile", + "makefile.frag": "Makefile", + "makefile.in": "Makefile", + "makefile.inc": "Makefile", + "makefile.pl": "Perl", + "makefile.sco": "Makefile", + "makefile.wat": "Makefile", + "man": "Shell", + "manifest.mf": "JAR Manifest", + "mavenfile": "Ruby", + "mcmod.info": "JSON", + "meson.build": "Meson", + "meson_options.txt": "Meson", + "mise.local.lock": "TOML", + "mise.lock": "TOML", + "mix.lock": "Elixir", + "mkfile": "Makefile", + "mmn": "Roff", + "mmt": "Roff", + "mocha.opts": "Option List", + "module.bazel": "Starlark", + "module.bazel.lock": "JSON", + "modulefile": "Puppet", + "mvnw": "Shell", + "mvnw.cmd": "Batchfile", + "nanorc": "INI", + "nextflow.config": "Nextflow", + "nginx.conf": "Nginx", + "nim.cfg": "Nim", + "notebook": "Jupyter Notebook", + "nuget.config": "XML", + "nukefile": "Nu", + "nvimrc": "Vim Script", + "owh": "Tcl", + "package.resolved": "JSON", + "packages.config": "XML", + "pdm.lock": "TOML", + "phakefile": "PHP", + "pipfile": "TOML", + "pipfile.lock": "JSON", + "pixi.lock": "YAML", + "pkgbuild": "Shell", + "podfile": "Ruby", + "poetry.lock": "TOML", + "procfile": "Procfile", + "profile": "Shell", + "project.ede": "Emacs Lisp", + "project.godot": "Godot Resource", + "puppetfile": "Ruby", + "pylintrc": "INI", + "rakefile": "Ruby", + "rebar.config": "Erlang", + "rebar.config.lock": "Erlang", + "rebar.lock": "Erlang", + "requirements-dev.txt": "Pip Requirements", + "requirements.lock.txt": "Pip Requirements", + "requirements.txt": "Pip Requirements", + "rexfile": "Perl", + "riemann.config": "Clojure", + "root": "Isabelle", + "sconscript": "Python", + "sconstruct": "Python", + "settings.stylecop": "XML", + "singularity": "Singularity", + "slakefile": "LiveScript", + "snakefile": "Python", + "snapfile": "Ruby", + "starfield": "Tcl", + "steepfile": "Ruby", + "suite.rc": "INI", + "thorfile": "Ruby", + "tiltfile": "Starlark", + "tmux.conf": "Shell", + "toolchain_installscript.qs": "Qt Script", + "torrc": "Tor Config", + "troffrc": "Roff", + "troffrc-end": "Roff", + "tsconfig.json": "JSON", + "tslint.json": "JSON", + "uv.lock": "TOML", + "vagrantfile": "Ruby", + "vimrc": "Vim Script", + "vlcrc": "INI", + "web.config": "XML", + "web.debug.config": "XML", + "web.release.config": "XML", + "workspace": "Starlark", + "workspace.bazel": "Starlark", + "workspace.bzlmod": "Starlark", + "wscript": "Python", + "xinitrc": "Shell", + "xmake.lua": "Xmake", + "xsession": "Shell", + "yarn.lock": "YAML", + "zlogin": "Shell", + "zlogout": "Shell", + "zprofile": "Shell", + "zshenv": "Shell", + "zshrc": "Shell" + } + }, + "vendor": [ + "(^|/)cache/", + "^[Dd]ependencies/", + "(^|/)dist/", + "^deps/", + "(^|/)configure$", + "(^|/)config\\.guess$", + "(^|/)config\\.sub$", + "(^|/)aclocal\\.m4", + "(^|/)libtool\\.m4", + "(^|/)ltoptions\\.m4", + "(^|/)ltsugar\\.m4", + "(^|/)ltversion\\.m4", + "(^|/)lt~obsolete\\.m4", + "(^|/)dotnet-install\\.(ps1|sh)$", + "(^|/)cpplint\\.py", + "(^|/)node_modules/", + "(^|/)\\.yarn/releases/", + "(^|/)\\.yarn/plugins/", + "(^|/)\\.yarn/sdks/", + "(^|/)\\.yarn/versions/", + "(^|/)\\.yarn/unplugged/", + "(^|/)_esy$", + "(^|/)bower_components/", + "^rebar$", + "(^|/)erlang\\.mk", + "(^|/)Godeps/_workspace/", + "(^|/)testdata/", + "(^|/)\\.indent\\.pro", + "(\\.|-)min\\.(js|css)$", + "([^\\s]*)import\\.(css|less|scss|styl)$", + "(^|/)bootstrap([^/.]*)(\\..*)?\\.(js|css|less|scss|styl)$", + "(^|/)custom\\.bootstrap([^\\s]*)(js|css|less|scss|styl)$", + "(^|/)font-?awesome\\.(css|less|scss|styl)$", + "(^|/)font-?awesome/.*\\.(css|less|scss|styl)$", + "(^|/)foundation\\.(css|less|scss|styl)$", + "(^|/)normalize\\.(css|less|scss|styl)$", + "(^|/)skeleton\\.(css|less|scss|styl)$", + "(^|/)[Bb]ourbon/.*\\.(css|less|scss|styl)$", + "(^|/)animate\\.(css|less|scss|styl)$", + "(^|/)materialize\\.(css|less|scss|styl|js)$", + "(^|/)select2/.*\\.(css|scss|js)$", + "(^|/)bulma\\.(css|sass|scss)$", + "(3rd|[Tt]hird)[-_]?[Pp]arty/", + "(^|/)vendors?/", + "(^|/)[Ee]xtern(als?)?/", + "(^|/)[Vv]+endor/", + "^debian/", + "(^|/)run\\.n$", + "(^|/)bootstrap-datepicker/", + "(^|/)jquery([^.]*)\\.js$", + "(^|/)jquery\\-\\d\\.\\d+(\\.\\d+)?\\.js$", + "(^|/)jquery\\-ui(\\-\\d\\.\\d+(\\.\\d+)?)?(\\.\\w+)?\\.(js|css)$", + "(^|/)jquery\\.(ui|effects)\\.([^.]*)\\.(js|css)$", + "(^|/)jquery\\.fn\\.gantt\\.js", + "(^|/)jquery\\.fancybox\\.(js|css)", + "(^|/)fuelux\\.js", + "(^|/)jquery\\.fileupload(-\\w+)?\\.js$", + "(^|/)jquery\\.dataTables\\.js", + "(^|/)bootbox\\.js", + "(^|/)pdf\\.worker\\.js", + "(^|/)slick\\.\\w+.js$", + "(^|/)Leaflet\\.Coordinates-\\d+\\.\\d+\\.\\d+\\.src\\.js$", + "(^|/)leaflet\\.draw-src\\.js", + "(^|/)leaflet\\.draw\\.css", + "(^|/)Control\\.FullScreen\\.css", + "(^|/)Control\\.FullScreen\\.js", + "(^|/)leaflet\\.spin\\.js", + "(^|/)wicket-leaflet\\.js", + "(^|/)\\.sublime-project", + "(^|/)\\.sublime-workspace", + "(^|/)\\.vscode/", + "(^|/)prototype(.*)\\.js$", + "(^|/)effects\\.js$", + "(^|/)controls\\.js$", + "(^|/)dragdrop\\.js$", + "(.*?)\\.d\\.ts$", + "(^|/)mootools([^.]*)\\d+\\.\\d+.\\d+([^.]*)\\.js$", + "(^|/)dojo\\.js$", + "(^|/)MochiKit\\.js$", + "(^|/)yahoo-([^.]*)\\.js$", + "(^|/)yui([^.]*)\\.js$", + "(^|/)ckeditor\\.js$", + "(^|/)tiny_mce([^.]*)\\.js$", + "(^|/)tiny_mce/(langs|plugins|themes|utils)", + "(^|/)ace-builds/", + "(^|/)fontello(.*?)\\.css$", + "(^|/)MathJax/", + "(^|/)Chart\\.js$", + "(^|/)[Cc]ode[Mm]irror/(\\d+\\.\\d+/)?(lib|mode|theme|addon|keymap|demo)", + "(^|/)shBrush([^.]*)\\.js$", + "(^|/)shCore\\.js$", + "(^|/)shLegacy\\.js$", + "(^|/)angular([^.]*)\\.js$", + "(^|\\/)d3(\\.v\\d+)?([^.]*)\\.js$", + "(^|/)react(-[^.]*)?\\.js$", + "(^|/)flow-typed/.*\\.js$", + "(^|/)modernizr\\-\\d\\.\\d+(\\.\\d+)?\\.js$", + "(^|/)modernizr\\.custom\\.\\d+\\.js$", + "(^|/)knockout-(\\d+\\.){3}(debug\\.)?js$", + "(^|/)docs?/_?(build|themes?|templates?|static)/", + "(^|/)admin_media/", + "(^|/)env/", + "(^|/)fabfile\\.py$", + "(^|/)waf$", + "(^|/)\\.osx$", + "\\.xctemplate/", + "\\.imageset/", + "(^|/)Carthage/", + "(^|/)Sparkle/", + "(^|/)Crashlytics\\.framework/", + "(^|/)Fabric\\.framework/", + "(^|/)BuddyBuildSDK\\.framework/", + "(^|/)Realm\\.framework", + "(^|/)RealmSwift\\.framework", + "(^|/)\\.gitattributes$", + "(^|/)\\.gitignore$", + "(^|/)\\.gitmodules$", + "(^|/)gradlew$", + "(^|/)gradlew\\.bat$", + "(^|/)gradle/wrapper/", + "(^|/)mvnw$", + "(^|/)mvnw\\.cmd$", + "(^|/)\\.mvn/wrapper/", + "-vsdoc\\.js$", + "\\.intellisense\\.js$", + "(^|/)jquery([^.]*)\\.validate(\\.unobtrusive)?\\.js$", + "(^|/)jquery([^.]*)\\.unobtrusive\\-ajax\\.js$", + "(^|/)[Mm]icrosoft([Mm]vc)?([Aa]jax|[Vv]alidation)(\\.debug)?\\.js$", + "(^|/)[Pp]ackages\\/.+\\.\\d+\\/", + "(^|/)extjs/.*?\\.js$", + "(^|/)extjs/.*?\\.xml$", + "(^|/)extjs/.*?\\.txt$", + "(^|/)extjs/.*?\\.html$", + "(^|/)extjs/.*?\\.properties$", + "(^|/)extjs/\\.sencha/", + "(^|/)extjs/docs/", + "(^|/)extjs/builds/", + "(^|/)extjs/cmd/", + "(^|/)extjs/examples/", + "(^|/)extjs/locale/", + "(^|/)extjs/packages/", + "(^|/)extjs/plugins/", + "(^|/)extjs/resources/", + "(^|/)extjs/src/", + "(^|/)extjs/welcome/", + "(^|/)html5shiv\\.js$", + "(^|/)[Tt]ests?/fixtures/", + "(^|/)[Ss]pecs?/fixtures/", + "(^|/)cordova([^.]*)\\.js$", + "(^|/)cordova\\-\\d\\.\\d(\\.\\d)?\\.js$", + "(^|/)foundation(\\..*)?\\.js$", + "(^|/)Vagrantfile$", + "(^|/)\\.[Dd][Ss]_[Ss]tore$", + "(^|/)inst/extdata/", + "(^|/)octicons\\.css", + "(^|/)sprockets-octicons\\.scss", + "(^|/)activator$", + "(^|/)activator\\.bat$", + "(^|/)proguard\\.pro$", + "(^|/)proguard-rules\\.pro$", + "(^|/)puphpet/", + "(^|/)\\.google_apis/", + "(^|/)Jenkinsfile$", + "(^|/)\\.gitpod\\.Dockerfile$", + "(^|/)\\.github/", + "(^|/)\\.obsidian/", + "(^|/)\\.teamcity/", + "(^|/)xvba_modules/" + ] +} \ No newline at end of file diff --git a/src/repo-intel/template.html b/src/repo-intel/template.html index 98aa55d..b2c6dd6 100644 --- a/src/repo-intel/template.html +++ b/src/repo-intel/template.html @@ -134,6 +134,7 @@ .timeline-scroll { flex: 1; overflow-x: auto; cursor: grab; -webkit-user-select: none; user-select: none; min-width: 0; padding-bottom: 0; scrollbar-width: none; } .timeline-scroll::-webkit-scrollbar { display: none; width: 0; height: 0; } .timeline-scroll.dragging { cursor: grabbing; scroll-behavior: auto; } + .timeline-scroll.zoom-mode { cursor: crosshair; } .timeline-inner { position: relative; overflow-x: clip; } .timeline-axis { position: relative; font-size: 0.65rem; color: var(--text-muted); } .timeline-axis span { position: absolute; transform: translateX(-50%); top: 0; white-space: nowrap; } @@ -141,9 +142,12 @@ .timeline-axis .timeline-tick { transform: none; top: 0; bottom: 0; width: 1px; background: rgba(255,255,255,0.06); pointer-events: none; } .timeline-canvas { display: block; position: sticky; left: 0; max-height: none; } .timeline-histogram { margin-top: 10px; opacity: 1; cursor: pointer; touch-action: none; } - .timeline-tags { margin-top: 6px; touch-action: none; } + .timeline-tags { touch-action: none; } + .timeline-years { position: relative; font-size: 0.68rem; color: var(--text-muted); font-variant-numeric: tabular-nums; } + .timeline-years .year-seg { position: absolute; top: 0; bottom: 0; border-left: 1px solid rgba(255,255,255,0.14); background: rgba(255,255,255,0.035); } + .timeline-years .year-seg.odd { background: rgba(255,255,255,0.085); } + .timeline-years .year-label { position: absolute; top: 0; line-height: 18px; transform: translateX(-50%); white-space: nowrap; pointer-events: none; } .timeline-labels .histogram-label { display: flex; align-items: center; justify-content: center; font-size: 0.7rem; color: var(--text-muted); margin-top: 10px; } - .timeline-labels .tags-label { display: flex; align-items: center; justify-content: flex-end; font-size: 0.65rem; color: var(--text-muted); margin-top: 6px; padding-right: 2px; letter-spacing: 0.04em; text-transform: uppercase; } .timeline-tooltip { position: fixed; z-index: 100; pointer-events: none; background: rgba(13,17,23,0.96); color: var(--text-primary); font-size: 0.75rem; @@ -164,6 +168,7 @@ .timeline-tooltip .tt-bundle-item:first-child { padding-top: 0; border-top: none; } .timeline-tooltip .tt-bundle-subject { color: var(--text-primary); word-break: break-word; } .timeline-tooltip .tt-bundle-meta { color: var(--text-muted); font-size: 0.7rem; margin-top: 2px; } + .timeline-tooltip .tt-tag-kicker { font-size: 0.62rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); margin-right: 6px; } .timeline-tooltip .tt-tag-name { font-family: ui-monospace, SFMono-Regular, monospace; font-weight: 600; color: var(--text-primary); } .timeline-tooltip .tt-tag-icon { display: inline-block; width: 8px; height: 8px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.9); margin-right: 6px; vertical-align: middle; } .timeline-labels .lane-label:hover { color: var(--text-primary); } @@ -197,6 +202,49 @@ .lane-popover .lp-stats .add { color: var(--color-added); } .lane-popover .lp-stats .del { color: var(--color-deleted); } .lane-popover .lp-period { color: var(--text-muted); font-size: 0.72rem; margin-top: 4px; } + /* Language bar — shared by the author popover and the Technologies section. */ + .langbar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--bg-badge); margin-top: 10px; } + .langbar > span { display: block; height: 100%; min-width: 2px; } + .lang-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font-size: 0.74rem; color: var(--text-secondary); } + .lang-legend .lang-item { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; } + .lang-legend a.lang-item { color: inherit; text-decoration: none; } + .lang-legend a.lang-item:hover { text-decoration: underline; } + .lang-legend .lang-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; } + .lang-legend .lang-pct { color: var(--text-muted); font-variant-numeric: tabular-nums; } + .lane-popover .langbar { margin-top: 12px; } + .lane-popover .lang-legend { font-size: 0.72rem; gap: 3px 10px; } + /* Commit-bucket popover — opened by clicking a bar in the pattern charts. */ + .commit-popover { + position: fixed; z-index: 120; pointer-events: auto; + background: rgba(13,17,23,0.98); color: var(--text-primary); + border: 1px solid var(--border-default); border-radius: 6px; + padding: 12px 14px; min-width: 280px; max-width: 380px; + box-shadow: 0 8px 28px rgba(0,0,0,0.5); + opacity: 0; transition: opacity 0.12s; visibility: hidden; + } + .commit-popover.visible { opacity: 1; visibility: visible; } + .commit-popover .cp-title { font-weight: 600; font-size: 0.9rem; line-height: 1.2; } + .commit-popover .cp-sub { color: var(--text-muted); font-size: 0.76rem; margin: 2px 0 10px; } + .commit-popover .cp-list { max-height: 280px; overflow-y: auto; margin: 0 -14px; padding: 0; } + .commit-popover .cp-row { display: flex; gap: 9px; align-items: baseline; padding: 5px 14px; text-decoration: none; color: var(--text-secondary); } + .commit-popover .cp-row:hover { background: var(--bg-badge); } + .commit-popover .cp-hash { font-family: ui-monospace, monospace; font-size: 0.72rem; color: var(--text-muted); flex-shrink: 0; } + .commit-popover .cp-msg { font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; } + .commit-popover .cp-date { font-size: 0.72rem; color: var(--text-muted); flex-shrink: 0; font-variant-numeric: tabular-nums; } + .commit-popover .cp-more { color: var(--text-muted); font-size: 0.74rem; margin-top: 8px; text-align: center; } + /* Technologies section. */ + #tech .tech-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; } + #tech .tech-bar-label { font-size: 0.78rem; color: var(--text-muted); margin: 0 0 4px; } + #tech .frameworks { display: flex; flex-direction: column; } + #tech .fw-group { display: grid; grid-template-columns: 130px 1fr; gap: 10px; align-items: baseline; padding: 9px 0; border-top: 1px solid var(--border-default); } + #tech .fw-group:first-child { border-top: none; } + #tech .fw-lang { display: inline-flex; align-items: center; gap: 7px; font-size: 0.82rem; color: var(--text-secondary); font-weight: 600; } + #tech .fw-lang .lang-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } + #tech .fw-items { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.5; } + #tech .tech-empty { color: var(--text-muted); font-size: 0.8rem; font-style: italic; } + .timeline-tooltip .tt-ftypes { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 5px; } + .timeline-tooltip .tt-ftype { display: inline-flex; align-items: center; gap: 5px; font-size: 0.7rem; color: var(--text-secondary); } + .timeline-tooltip .tt-ftype .tt-fdot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; } * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) var(--bg-primary); } ::-webkit-scrollbar { height: 8px; width: 8px; } ::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 4px; } @@ -205,6 +253,7 @@ @media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } .grid-5 { grid-template-columns: repeat(2, 1fr); } + #tech .tech-grid { grid-template-columns: 1fr; } .layout { flex-direction: column; } .sidebar { position: static; width: 100%; } .sidebar nav { flex-direction: row; flex-wrap: wrap; } @@ -221,6 +270,7 @@