feat: add Erlang language extractor

This commit is contained in:
Abdullah committed 2026-09-21 17:08:35 +05:30
1 parent 20a20d30d8
commit 3a92853329
9 files changed
+541 -3

No files matched your search

+1
View File
@@ -41,6 +41,7 @@ _LANG_FAMILY: dict[str, str] = {
**{e: "dotnet" for e in (".cs",)},
**{e: "php" for e in (".php",)},
**{e: "r" for e in (".r",)},
**{e: "erlang" for e in (".erl", ".hrl", ".escript")},
}
+1 -1
View File
@@ -41,7 +41,7 @@ _MANIFEST_PATH = str(out_path("manifest.json"))
_MTIME_COARSE_S = 2.0
_MTIME_SUBSECOND_S = 0.05
CODE_EXTENSIONS = {'.py', '.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.ejs', '.ets', '.go', '.rs', '.java', '.groovy', '.gradle', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.cu', '.cuh', '.metal', '.rb', '.rake', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.luau', '.toc', '.zig', '.ps1', '.psm1', '.psd1', '.ex', '.exs', '.m', '.mm', '.ml', '.mli', '.jl', '.vue', '.svelte', '.astro', '.dart', '.v', '.sv', '.svh', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08', '.pas', '.pp', '.dpr', '.dpk', '.lpr', '.inc', '.dfm', '.lfm', '.lpk', '.sh', '.bash', '.json', '.tf', '.tfvars', '.hcl', '.dm', '.dme', '.dmi', '.dmm', '.dmf', '.sln', '.slnx', '.csproj', '.fsproj', '.vbproj', '.xaml', '.razor', '.cshtml', '.cls', '.trigger', '.lisp', '.cl', '.lsp', '.asd', '.robot', '.resource'}
CODE_EXTENSIONS = {'.py', '.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.ejs', '.ets', '.go', '.rs', '.erl', '.hrl', '.escript', '.java', '.groovy', '.gradle', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.cu', '.cuh', '.metal', '.rb', '.rake', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.luau', '.toc', '.zig', '.ps1', '.psm1', '.psd1', '.ex', '.exs', '.m', '.mm', '.ml', '.mli', '.jl', '.vue', '.svelte', '.astro', '.dart', '.v', '.sv', '.svh', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08', '.pas', '.pp', '.dpr', '.dpk', '.lpr', '.inc', '.dfm', '.lfm', '.lpk', '.sh', '.bash', '.json', '.tf', '.tfvars', '.hcl', '.dm', '.dme', '.dmi', '.dmm', '.dmf', '.sln', '.slnx', '.csproj', '.fsproj', '.vbproj', '.xaml', '.razor', '.cshtml', '.cls', '.trigger', '.lisp', '.cl', '.lsp', '.asd', '.robot', '.resource'}
DOC_EXTENSIONS = {'.md', '.mdx', '.qmd', '.skill', '.txt', '.rst', '.html', '.yaml', '.yml'}
PAPER_EXTENSIONS = {'.pdf'}
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
+15
View File
@@ -45,6 +45,7 @@ from graphify.extractors.csharp import (
from graphify.extractors.dart import extract_dart # noqa: F401
from graphify.extractors.dm import extract_dm, extract_dmf, extract_dmi, extract_dmm # noqa: F401
from graphify.extractors.elixir import extract_elixir # noqa: F401
from graphify.extractors.erlang import extract_erlang, resolve_erlang_remote_calls # noqa: F401
from graphify.extractors.fortran import _cpp_preprocess, extract_fortran # noqa: F401
from graphify.extractors.go import _GO_PREDECLARED_FUNCS, extract_go # noqa: F401
from graphify.extractors.json_config import extract_json # noqa: F401
@@ -2763,6 +2764,7 @@ _LANG_FAMILY_BY_EXT: dict[str, str] = {
".py": "python",
".go": "go",
".rs": "rust",
".erl": "erlang", ".hrl": "erlang", ".escript": "erlang",
".rb": "ruby", ".rake": "ruby",
".php": "php", ".phtml": "php", ".php3": "php", ".php4": "php",
".php5": "php", ".php7": "php", ".phps": "php",
@@ -5194,6 +5196,13 @@ register_language_resolver(
register_language_resolver(
LanguageResolver("rust_self_member_calls", frozenset({".rs"}), _resolve_rust_self_member_calls)
)
register_language_resolver(
LanguageResolver(
"erlang_remote_calls",
frozenset({".erl", ".hrl", ".escript"}),
resolve_erlang_remote_calls,
)
)
register_language_resolver(
LanguageResolver(
"elixir_import_targets",
@@ -6307,6 +6316,9 @@ _DISPATCH: dict[str, Any] = {
".psd1": extract_powershell_manifest,
".ex": extract_elixir,
".exs": extract_elixir,
".erl": extract_erlang,
".hrl": extract_erlang,
".escript": extract_erlang,
".m": extract_objc,
".mm": extract_objc,
".jl": extract_julia,
@@ -6378,6 +6390,9 @@ _DISPATCH: dict[str, Any] = {
# rather than falling back like Pascal does. Used by the #1745 warning in
# extract() to tell the user which extra restores the language.
_EXTRA_FOR_EXTENSION = {
".erl": "erlang",
".hrl": "erlang",
".escript": "erlang",
".sql": "sql",
".tf": "terraform",
".tfvars": "terraform",
+2
View File
@@ -17,6 +17,7 @@ from graphify.extractors.commonlisp import extract_commonlisp
from graphify.extractors.dart import extract_dart
from graphify.extractors.dm import extract_dm, extract_dmf, extract_dmi, extract_dmm
from graphify.extractors.elixir import extract_elixir
from graphify.extractors.erlang import extract_erlang
from graphify.extractors.fortran import extract_fortran
from graphify.extractors.go import extract_go
from graphify.extractors.json_config import extract_json
@@ -46,6 +47,7 @@ LANGUAGE_EXTRACTORS: dict[str, Callable[[Path], dict]] = {
"dmi": extract_dmi,
"dmm": extract_dmm,
"elixir": extract_elixir,
"erlang": extract_erlang,
"fortran": extract_fortran,
"go": extract_go,
"json": extract_json,
+333
View File
@@ -0,0 +1,333 @@
"""Structural extraction for Erlang source and header files."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Iterable
from tree_sitter import Node
from graphify.extractors.base import _file_stem, _make_id, _read_text
def _atom(value: str) -> str:
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] == "'":
return value[1:-1]
return value
def _descendants(node: Node) -> Iterable[Node]:
for child in node.named_children:
yield child
yield from _descendants(child)
def resolve_erlang_remote_calls(
per_file: list[dict], all_nodes: list[dict], all_edges: list[dict]
) -> None:
"""Resolve module-qualified calls by exact module, name, and arity."""
functions: dict[tuple[str, str, int], list[str]] = {}
for node in all_nodes:
metadata = node.get("metadata")
if not isinstance(metadata, dict) or metadata.get("language") != "erlang":
continue
if metadata.get("kind") != "function":
continue
module = metadata.get("module")
name = metadata.get("name")
arity = metadata.get("arity")
if isinstance(module, str) and isinstance(name, str) and isinstance(arity, int):
functions.setdefault((module, name, arity), []).append(node["id"])
existing = {
(edge.get("source"), edge.get("target"))
for edge in all_edges
if edge.get("relation") == "calls"
}
for result in per_file:
for call in result.get("raw_calls", []):
if call.get("language") != "erlang" or not call.get("remote_module"):
continue
key = (
str(call["remote_module"]),
str(call.get("callee", "")),
int(call.get("arity", -1)),
)
candidates = functions.get(key, [])
caller = call.get("caller_nid")
if len(candidates) != 1 or candidates[0] == caller:
continue
pair = (caller, candidates[0])
if pair in existing:
continue
existing.add(pair)
all_edges.append({
"source": caller,
"target": candidates[0],
"relation": "calls",
"context": "remote_call",
"confidence": "EXTRACTED",
"confidence_score": 1.0,
"source_file": call.get("source_file", ""),
"source_location": call.get("source_location"),
"weight": 1.0,
})
def extract_erlang(path: Path) -> dict:
try:
from tree_sitter import Parser
from tree_sitter_language_pack import get_language
except ImportError:
return {"nodes": [], "edges": [], "error": "tree-sitter-language-pack not installed"}
try:
source = path.read_bytes()
root = Parser(get_language("erlang")).parse(source).root_node
except Exception as exc:
return {"nodes": [], "edges": [], "error": f"Erlang grammar failed to load: {exc}"}
source_file = str(path)
stem = _file_stem(path)
file_id = _make_id(source_file)
nodes: list[dict[str, Any]] = []
edges: list[dict[str, Any]] = []
raw_calls: list[dict[str, Any]] = []
seen_ids: set[str] = set()
seen_edges: set[tuple[str, str, str]] = set()
def add_node(
nid: str,
label: str,
node: Node,
*,
kind: str,
source_backed: bool = True,
callable_node: bool = False,
metadata: dict[str, Any] | None = None,
) -> str:
if nid not in seen_ids:
seen_ids.add(nid)
details: dict[str, Any] = {"language": "erlang", "kind": kind}
if metadata:
details.update(metadata)
item: dict[str, Any] = {
"id": nid,
"label": label,
"file_type": "code",
"source_location": f"L{node.start_point[0] + 1}",
"metadata": details,
}
if source_backed:
item["source_file"] = source_file
if callable_node:
item["_callable"] = True
nodes.append(item)
return nid
def add_edge(source_id: str, target_id: str, relation: str, node: Node) -> None:
key = (source_id, target_id, relation)
if not source_id or not target_id or source_id == target_id or key in seen_edges:
return
seen_edges.add(key)
edges.append({
"source": source_id,
"target": target_id,
"relation": relation,
"confidence": "EXTRACTED",
"source_file": source_file,
"source_location": f"L{node.start_point[0] + 1}",
"weight": 1.0,
})
add_node(file_id, path.name, root, kind="file")
module_name = path.stem
module_declaration = next(
(node for node in root.named_children if node.type == "module_attribute"),
None,
)
module_id = file_id
if module_declaration is not None:
name_node = module_declaration.child_by_field_name("name")
if name_node is not None:
module_name = _atom(_read_text(name_node, source))
module_id = add_node(
_make_id(stem, "module", module_name),
module_name,
module_declaration,
kind="module",
metadata={"module": module_name},
)
add_edge(file_id, module_id, "contains", module_declaration)
functions: dict[tuple[str, int], str] = {}
bodies: list[tuple[Node, str]] = []
export_specs: list[tuple[str, int, Node]] = []
for node in root.named_children:
if node.type == "export_attribute":
for fa in (item for item in _descendants(node) if item.type == "fa"):
atom_node = next((item for item in _descendants(fa) if item.type == "atom"), None)
integer = next((item for item in _descendants(fa) if item.type == "integer"), None)
if atom_node is not None and integer is not None:
export_specs.append((
_atom(_read_text(atom_node, source)),
int(_read_text(integer, source)),
fa,
))
continue
if node.type == "behaviour_attribute":
name_node = node.child_by_field_name("name")
if name_node is not None:
name = _atom(_read_text(name_node, source))
target = add_node(
_make_id("erlang", "behaviour", name),
name,
name_node,
kind="behaviour",
source_backed=False,
)
add_edge(module_id, target, "implements", node)
continue
if node.type in {"pp_include", "pp_include_lib"}:
string_node = next(
(item for item in node.named_children if item.type == "string"), None
)
if string_node is None:
continue
include = _read_text(string_node, source).strip("\"")
if node.type == "pp_include":
target = path.parent / include
target_id = _make_id(str(target))
edge = {
"source": module_id,
"target": target_id,
"relation": "imports_from",
"confidence": "EXTRACTED",
"source_file": source_file,
"source_location": f"L{node.start_point[0] + 1}",
"weight": 1.0,
"target_file": str(target),
}
key = (module_id, target_id, "imports_from")
if key not in seen_edges:
seen_edges.add(key)
edges.append(edge)
else:
target = add_node(
_make_id("erlang", "include_lib", include),
include,
string_node,
kind="include_lib",
source_backed=False,
)
add_edge(module_id, target, "imports", node)
continue
declaration_kinds = {
"record_decl": ("record", "name"),
"type_alias": ("type", "name"),
"opaque": ("type", "name"),
"pp_define": ("macro", "lhs"),
}
if node.type in declaration_kinds:
kind, field = declaration_kinds[node.type]
name_container = node.child_by_field_name(field)
if name_container is None:
continue
if kind == "type":
name_node = name_container.child_by_field_name("name")
name = _atom(_read_text(name_node or name_container, source))
args = name_container.child_by_field_name("args")
arity = len(args.named_children) if args is not None else 0
label = f"{name}/{arity}"
elif kind == "macro":
name_node = name_container.child_by_field_name("name")
name = _read_text(name_node or name_container, source)
label = name
else:
name = _atom(_read_text(name_container, source))
label = name
declaration_id = add_node(
_make_id(module_id, kind, name),
label,
node,
kind=kind,
metadata={"module": module_name, "name": name},
)
add_edge(module_id, declaration_id, "contains", node)
continue
if node.type != "fun_decl":
continue
clauses = [
child for child in node.named_children if child.type == "function_clause"
]
for clause in clauses:
name_node = clause.child_by_field_name("name")
args = clause.child_by_field_name("args")
if name_node is None or args is None:
continue
name = _atom(_read_text(name_node, source))
arity = len(args.named_children)
key = (name, arity)
function_id = functions.get(key)
if function_id is None:
function_id = add_node(
_make_id(module_id, "function", name, str(arity)),
f"{name}/{arity}",
clause,
kind="function",
callable_node=True,
metadata={"module": module_name, "name": name, "arity": arity},
)
functions[key] = function_id
add_edge(module_id, function_id, "contains", clause)
body = clause.child_by_field_name("body")
if body is not None:
bodies.append((body, function_id))
for name, arity, spec in export_specs:
target = functions.get((name, arity))
if target is not None:
add_edge(module_id, target, "exports", spec)
for body, caller_id in bodies:
stack = [body]
while stack:
node = stack.pop()
if node.type == "call":
callee_node = node.child_by_field_name("expr")
args = node.child_by_field_name("args")
if callee_node is not None and args is not None:
arity = len(args.named_children)
if callee_node.type == "atom":
name = _atom(_read_text(callee_node, source))
target = functions.get((name, arity))
if target is not None:
add_edge(caller_id, target, "calls", node)
elif callee_node.type == "remote":
written = _read_text(callee_node, source)
module, separator, name = written.partition(":")
if separator:
raw_calls.append({
"caller_nid": caller_id,
"callee": _atom(name),
"arity": arity,
"remote_module": _atom(module),
"is_member_call": True,
"language": "erlang",
"source_file": source_file,
"source_location": f"L{node.start_point[0] + 1}",
})
stack.extend(reversed(node.named_children))
clean_edges = [
edge for edge in edges
if edge["source"] in seen_ids
and (edge["target"] in seen_ids or edge["relation"] == "imports_from")
]
return {"nodes": nodes, "edges": clean_edges, "raw_calls": raw_calls}
+3 -1
View File
@@ -105,7 +105,9 @@ commonlisp = ["tree-sitter-commonlisp"]
# every platform, no C toolchain; optional because Robot Framework corpora are
# QA-automation specific.
robot = ["robotframework>=4.0"]
all = ["mcp>=1,<3", "starlette>=1.3.1,<2", "neo4j", "falkordb", "pypdf>=6.16.1", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "graspologic-native>=1.3.1,<2; python_version >= '3.13'", "python-docx", "openpyxl", "psycopg[binary]", "faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.7.4", "matplotlib", "pillow>=12.3.0", "numpy>=2.0; python_version >= '3.13'", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "jieba; python_version < '3.12'", "jieba-py>=0.46.12,<1; python_version >= '3.12'", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-pascal", "tree-sitter-ocaml", "tree-sitter-commonlisp", "robotframework>=4.0"]
# The language pack bundles the Erlang grammar in an offline-compatible wheel.
erlang = ["tree-sitter-language-pack==0.11.0"]
all = ["mcp>=1,<3", "starlette>=1.3.1,<2", "neo4j", "falkordb", "pypdf>=6.16.1", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "graspologic-native>=1.3.1,<2; python_version >= '3.13'", "python-docx", "openpyxl", "psycopg[binary]", "faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.7.4", "matplotlib", "pillow>=12.3.0", "numpy>=2.0; python_version >= '3.13'", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "jieba; python_version < '3.12'", "jieba-py>=0.46.12,<1; python_version >= '3.12'", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-pascal", "tree-sitter-ocaml", "tree-sitter-commonlisp", "robotframework>=4.0", "tree-sitter-language-pack==0.11.0"]
[project.scripts]
graphify = "graphify.__main__:main"
+5
View File
@@ -0,0 +1,5 @@
-module(sample).
-export([run/0]).
run() -> helper().
helper() -> ok.
+124
View File
@@ -0,0 +1,124 @@
"""Extraction coverage for erlang."""
from __future__ import annotations
import sys
from pathlib import Path
from graphify.extract import extract
FIXTURE = Path(__file__).parent / "fixtures" / "new_languages" / "sample.erl"
def _edge_labels(result: dict, relation: str) -> set[tuple[str, str]]:
labels = {node["id"]: node["label"] for node in result["nodes"]}
return {
(labels.get(edge["source"], edge["source"]), labels.get(edge["target"], edge["target"]))
for edge in result["edges"]
if edge["relation"] == relation
}
def test_erlang_functions_resolve_by_name_and_arity(tmp_path):
source = tmp_path / "worker.erl"
source.write_text(
"-module(worker).\n"
"-export([run/1]).\n"
"run(X) -> helper(X).\n"
"helper(X) -> X.\n"
"helper(X, Y) -> X + Y.\n",
encoding="utf-8",
)
result = extract([source], cache_root=tmp_path)
labels = {node["label"] for node in result["nodes"]}
assert {"worker", "run/1", "helper/1", "helper/2"} <= labels
assert ("run/1", "helper/1") in _edge_labels(result, "calls")
assert ("run/1", "helper/2") not in _edge_labels(result, "calls")
def test_erlang_attributes_includes_and_remote_calls(tmp_path):
header = tmp_path / "worker.hrl"
header.write_text("-define(HEADER_VALUE, 2).\n", encoding="utf-8")
other = tmp_path / "other.erl"
other.write_text(
"-module(other).\n-export([go/1]).\ngo(X) -> X.\n", encoding="utf-8"
)
source = tmp_path / "worker.escript"
source.write_text(
"#!/usr/bin/env escript\n"
"-module('worker').\n"
"-behaviour(gen_server).\n"
'-include("worker.hrl").\n'
'-include_lib("stdlib/include/assert.hrl").\n'
"-export([run/1, 'quoted'/0]).\n"
"-record(state, {value}).\n"
"-type result() :: ok | error.\n"
"-define(DEFAULT, 1).\n"
"run(X) -> helper(X), other:go(X), io:format(\"x\").\n"
"helper(X) -> X.\n"
"'quoted'() -> ok.\n",
encoding="utf-8",
)
result = extract([source, other, header], cache_root=tmp_path)
labels = {node["label"] for node in result["nodes"]}
assert {
"worker", "other", "run/1", "helper/1", "quoted/0", "go/1",
"state", "result/0", "DEFAULT", "HEADER_VALUE", "gen_server",
} <= labels
assert ("run/1", "helper/1") in _edge_labels(result, "calls")
assert ("run/1", "go/1") in _edge_labels(result, "calls")
assert ("worker", "run/1") in _edge_labels(result, "exports")
assert ("worker", "gen_server") in _edge_labels(result, "implements")
assert any(edge["relation"] == "imports_from" for edge in result["edges"])
def test_erlang_fixture_uses_normal_extract_path(tmp_path):
result = extract([FIXTURE], cache_root=tmp_path)
labels = {node["label"] for node in result["nodes"]}
assert {'run/0', 'helper/0', 'sample'} <= labels
assert ('run/0', 'helper/0') in _edge_labels(result, "calls")
def test_erlang_malformed_tail_comments_and_strings_do_not_create_phantoms(tmp_path):
source = tmp_path / 'broken.erl'
source.write_text('-module(broken).\nvalid() -> ok.\n% ghost() -> ok.\ninvalid(\n', encoding="utf-8")
result = extract([source], cache_root=tmp_path)
labels = {node["label"].casefold() for node in result["nodes"]}
assert 'valid/0' in labels
assert labels.isdisjoint({'ghost/0'})
def test_erlang_missing_parser_reports_install_hint(tmp_path, monkeypatch, capsys):
source = tmp_path / "missing.erl"
source.write_text('-module(missing).\n', encoding="utf-8")
monkeypatch.setitem(sys.modules, 'tree_sitter_language_pack', None)
result = extract([source], cache_root=tmp_path)
assert result["nodes"] == []
assert 'pip install "graphifyy[erlang]"' in capsys.readouterr().err
Generated
+57 -1
View File
@@ -1160,6 +1160,7 @@ all = [
{ name = "tree-sitter-commonlisp" },
{ name = "tree-sitter-dm" },
{ name = "tree-sitter-hcl" },
{ name = "tree-sitter-language-pack" },
{ name = "tree-sitter-ocaml" },
{ name = "tree-sitter-pascal" },
{ name = "tree-sitter-sql" },
@@ -1182,6 +1183,9 @@ commonlisp = [
dm = [
{ name = "tree-sitter-dm" },
]
erlang = [
{ name = "tree-sitter-language-pack" },
]
falkordb = [
{ name = "falkordb" },
]
@@ -1351,6 +1355,8 @@ requires-dist = [
{ name = "tree-sitter-json", specifier = ">=0.23,<0.26" },
{ name = "tree-sitter-julia", specifier = ">=0.23,<0.25" },
{ name = "tree-sitter-kotlin", specifier = ">=1.0,<2.0" },
{ name = "tree-sitter-language-pack", marker = "extra == 'all'", specifier = "==0.11.0" },
{ name = "tree-sitter-language-pack", marker = "extra == 'erlang'", specifier = "==0.11.0" },
{ name = "tree-sitter-lua", specifier = ">=0.2,<0.6" },
{ name = "tree-sitter-objc", specifier = ">=3.0,<4.0" },
{ name = "tree-sitter-ocaml", marker = "extra == 'all'" },
@@ -1375,7 +1381,7 @@ requires-dist = [
{ name = "yt-dlp", marker = "extra == 'all'", specifier = ">=2026.7.4" },
{ name = "yt-dlp", marker = "extra == 'video'", specifier = ">=2026.7.4" },
]
provides-extras = ["mcp", "neo4j", "falkordb", "pdf", "watch", "svg", "leiden", "office", "google", "postgres", "video", "kimi", "ollama", "bedrock", "anthropic", "gemini", "openai", "chinese", "sql", "pascal", "dm", "terraform", "ocaml", "commonlisp", "robot", "all"]
provides-extras = ["mcp", "neo4j", "falkordb", "pdf", "watch", "svg", "leiden", "office", "google", "postgres", "video", "kimi", "ollama", "bedrock", "anthropic", "gemini", "openai", "chinese", "sql", "pascal", "dm", "terraform", "ocaml", "commonlisp", "robot", "erlang", "all"]
[package.metadata.requires-dev]
dev = [
@@ -4698,6 +4704,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/f2/711baae88f98e3a30efee9383fbcb603a3188c20941643c71d3d3b936d66/tree_sitter_elixir-0.3.5-cp39-abi3-win_arm64.whl", hash = "sha256:fee42b90962e1e131cc31720f3038410291b2196ed231e00c1721597fc0567df", size = 164003, upload-time = "2026-03-02T13:31:08.013Z" },
]
[[package]]
name = "tree-sitter-embedded-template"
version = "0.25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fd/a7/77729fefab8b1b5690cfc54328f2f629d1c076d16daf32c96ba39d3a3a3a/tree_sitter_embedded_template-0.25.0.tar.gz", hash = "sha256:7d72d5e8a1d1d501a7c90e841b51f1449a90cc240be050e4fb85c22dab991d50", size = 14114, upload-time = "2025-08-29T00:42:51.078Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/9d/3e3c8ee0c019d3bace728300a1ca807c03df39e66cc51e9a5e7c9d1e1909/tree_sitter_embedded_template-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fa0d06467199aeb33fb3d6fa0665bf9b7d5a32621ffdaf37fd8249f8a8050649", size = 10266, upload-time = "2025-08-29T00:42:44.148Z" },
{ url = "https://files.pythonhosted.org/packages/e8/ab/6d4e43b736b2a895d13baea3791dc8ce7245bedf4677df9e7deb22e23a2a/tree_sitter_embedded_template-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc7aacbc2985a5d7e7fe7334f44dffe24c38fb0a8295c4188a04cf21a3d64a73", size = 10650, upload-time = "2025-08-29T00:42:45.147Z" },
{ url = "https://files.pythonhosted.org/packages/9f/97/ea3d1ea4b320fe66e0468b9f6602966e544c9fe641882484f9105e50ee0c/tree_sitter_embedded_template-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7c88c3dd8b94b3c9efe8ae071ff6b1b936a27ac5f6e651845c3b9631fa4c1c2", size = 18268, upload-time = "2025-08-29T00:42:46.03Z" },
{ url = "https://files.pythonhosted.org/packages/64/40/0f42ca894a8f7c298cf336080046ccc14c10e8f4ea46d455f640193181b2/tree_sitter_embedded_template-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:025f7ca84218dcd8455efc901bdbcc2689fb694f3a636c0448e322a23d4bc96b", size = 19068, upload-time = "2025-08-29T00:42:46.699Z" },
{ url = "https://files.pythonhosted.org/packages/d0/2a/0b720bcae7c2dd0a44889c09e800a2f8eb08c496dede9f2b97683506c4c3/tree_sitter_embedded_template-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b5dc1aef6ffa3fae621fe037d85dd98948b597afba20df29d779c426be813ee5", size = 18518, upload-time = "2025-08-29T00:42:47.694Z" },
{ url = "https://files.pythonhosted.org/packages/14/8a/d745071afa5e8bdf5b381cf84c4dc6be6c79dee6af8e0ff07476c3d8e4aa/tree_sitter_embedded_template-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d0a35cfe634c44981a516243bc039874580e02a2990669313730187ce83a5bc6", size = 18267, upload-time = "2025-08-29T00:42:48.635Z" },
{ url = "https://files.pythonhosted.org/packages/5d/74/728355e594fca140f793f234fdfec195366b6956b35754d00ea97ca18b21/tree_sitter_embedded_template-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e05a4ac013d54505e75ae48e1a0e9db9aab19949fe15d9f4c7345b11a84a069", size = 13049, upload-time = "2025-08-29T00:42:49.589Z" },
{ url = "https://files.pythonhosted.org/packages/d8/de/afac475e694d0e626b0808f3c86339c349cd15c5163a6a16a53cc11cf892/tree_sitter_embedded_template-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:2751d402179ac0e83f2065b249d8fe6df0718153f1636bcb6a02bde3e5730db9", size = 11978, upload-time = "2025-08-29T00:42:50.226Z" },
]
[[package]]
name = "tree-sitter-fortran"
version = "0.6.0"
@@ -4836,6 +4858,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/b9/12fa97f63d2b7517c6f5d16938f0c5bfe84d925c652c75ff1c5e29bf6a44/tree_sitter_kotlin-1.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:e030f127a7d07952907adb9070248bd42fb86dc76fd92744727551b50e131ee7", size = 310414, upload-time = "2025-01-09T19:02:16.23Z" },
]
[[package]]
name = "tree-sitter-language-pack"
version = "0.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tree-sitter" },
{ name = "tree-sitter-c-sharp" },
{ name = "tree-sitter-embedded-template" },
{ name = "tree-sitter-yaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/40/20/b21748c3386effa68bc7ab6d2bd9b78eabe391adc5797b7d37b24930f3b3/tree_sitter_language_pack-0.11.0.tar.gz", hash = "sha256:228901ce64c2b08feabfeeb687d6eb9450825bb8bd8cdf9caffffb9e7939614a", size = 49131889, upload-time = "2025-11-12T07:56:27.958Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/2c/33ec4d1458db4173d49b7f273b62ed7ce2c173eee1d0331ead912d93ee6f/tree_sitter_language_pack-0.11.0-cp310-abi3-macosx_10_15_universal2.whl", hash = "sha256:58b0351dd43e6a91261ac1264e9794a83c20eb78baa839e42699499ece1667dc", size = 31473144, upload-time = "2025-11-12T07:56:15.017Z" },
{ url = "https://files.pythonhosted.org/packages/67/d9/c8335131075f84cbac955f521b5194655dd7340574b87d3611ed8583ec20/tree_sitter_language_pack-0.11.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:b4205958666c597d8a1b74eb4c845764c3aebd5db3c2484c2a5d9701b3feef42", size = 19345909, upload-time = "2025-11-12T07:56:18.361Z" },
{ url = "https://files.pythonhosted.org/packages/f0/9f/ae964c062274507225e5f35262919c8d2f186eaec4ea76d1b67ba9c2f696/tree_sitter_language_pack-0.11.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:d029f9dc282f66a25d35b13d23ff2cd7108cd0170b426e0c1f755892d022e68c", size = 19203423, upload-time = "2025-11-12T07:56:21.472Z" },
{ url = "https://files.pythonhosted.org/packages/82/bb/17ee0bafb1b9e24075fe528b7f8963c6855dac56ccead44bf7855af3fab8/tree_sitter_language_pack-0.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:5cab767ba535c8bb09ac4c2db4b672f0ff67135083355f50713e7052fa37e6ef", size = 16188199, upload-time = "2025-11-12T07:56:24.185Z" },
]
[[package]]
name = "tree-sitter-lua"
version = "0.5.0"
@@ -5057,6 +5097,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/a3/229851168ec3997f1ced60b93edbeb294a0c2b3af2d71143469371c05851/tree_sitter_verilog-1.0.3-cp39-abi3-win_arm64.whl", hash = "sha256:11576eaa43f89266ab8869fb8d2fb1c22c8da74aa8dc82e67259d6560635c68f", size = 749282, upload-time = "2024-11-10T23:35:30.602Z" },
]
[[package]]
name = "tree-sitter-yaml"
version = "0.7.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" },
{ url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" },
{ url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" },
{ url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" },
{ url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" },
{ url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" },
{ url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" },
{ url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" },
]
[[package]]
name = "tree-sitter-zig"
version = "1.1.2"