diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-12 00:30:11 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-12 00:30:11 +0200 |
| commit | 32329d9a384642dd231ed1f5a16aa6c85b8d0ef0 (patch) | |
| tree | 4b2f793909a1f97e52213467cc9f60e810a886df /bin/project-ctags | |
| parent | a2151bcd260a50f697220abf75f522623b0960c6 (diff) | |
| download | emacs-32329d9a384642dd231ed1f5a16aa6c85b8d0ef0.tar.gz | |
relic: 2026-09-12T00:30:15+02:00
Diffstat (limited to 'bin/project-ctags')
| -rwxr-xr-x | bin/project-ctags | 69 |
1 files changed, 68 insertions, 1 deletions
diff --git a/bin/project-ctags b/bin/project-ctags index 46c8561..47b1fe5 100755 --- a/bin/project-ctags +++ b/bin/project-ctags @@ -6,6 +6,7 @@ import functools import hashlib import json import os +import re import shlex import shutil import subprocess @@ -35,6 +36,11 @@ IGNORED_DIRECTORIES = { } PYTHON_SUFFIXES = {".py", ".pyi"} RUST_SUFFIXES = {".rs"} +GO_SUFFIXES = {".go", ".templ"} +TEMPL_CTAGS_OPTIONS = [ + "--map-Go=+.templ", + r"--regex-Go=/^[[:space:]]*(templ|css|script)[[:space:]]+([[:alpha:]_][[:alnum:]_]*)[[:space:]]*\(/\2/f/", +] C_SOURCE_SUFFIXES = {".c", ".cc", ".cpp", ".cxx", ".m", ".mm"} C_HEADER_SUFFIXES = {".h", ".hh", ".hpp", ".hxx"} C3_SUFFIXES = {".c3", ".c3i", ".c3t"} @@ -108,11 +114,13 @@ def parse_arguments(): parser.add_argument("--python") parser.add_argument("--python-command") parser.add_argument("--cargo") + parser.add_argument("--go") parser.add_argument("--c3c") parser.add_argument("--c3-stdlib") parser.add_argument("--source", action="append", default=[]) parser.add_argument("--no-python-deps", action="store_true") parser.add_argument("--no-rust-deps", action="store_true") + parser.add_argument("--no-go-deps", action="store_true") parser.add_argument("--no-c-deps", action="store_true") parser.add_argument("--no-c3-deps", action="store_true") parser.add_argument("--cache-dir", help="Maintain project and dependency indexes here") @@ -342,6 +350,51 @@ def rust_dependency_files(args, root): return sorted(files), cargo +def go_dependency_files(args, root, files): + """Resolve imported Go packages, including the standard library, from local sources.""" + if args.no_go_deps or not any(path.suffix.lower() in GO_SUFFIXES for path in files): + return [], None + value = args.go or os.environ.get("PROJECT_CTAGS_GO") or shutil.which("go") + if not value: + print("Go dependencies skipped: go is unavailable in the project environment", file=sys.stderr) + return [], None + go = shutil.which(value) or str(resolved_path(value, root)) + environment = dict(os.environ, GOPROXY="off", GOSUMDB="off", GOTOOLCHAIN="local") + result = subprocess.run( + [go, "list", "-e", "-deps", "-json", "-mod=readonly", "./..."], + cwd=root, + env=environment, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if result.stderr.strip(): + print(result.stderr.rstrip(), file=sys.stderr) + dependencies = set() + decoder = json.JSONDecoder() + remaining = result.stdout.lstrip() + while remaining: + try: + package, end = decoder.raw_decode(remaining) + except json.JSONDecodeError: + print("Could not read Go package metadata", file=sys.stderr) + break + remaining = remaining[end:].lstrip() + if error := package.get("Error"): + print(f"go: {error.get('Err', error)}", file=sys.stderr) + if not package.get("Dir"): + continue + directory = Path(package["Dir"]).resolve() + if directory.is_relative_to(root): + continue + for name in package.get("GoFiles", []) + package.get("CgoFiles", []): + path = directory / name + if path.is_file(): + dependencies.add(path.resolve()) + return sorted(dependencies), go + + def compilation_database(root): path = root / "compile_commands.json" if not path.is_file(): @@ -494,6 +547,7 @@ def generate_tags(ctags, output, files, mappings=None): "--options=NONE", "--output-format=e-ctags", *NIX_CTAGS_OPTIONS, + *TEMPL_CTAGS_OPTIONS, *c3_ctags_options(ctags), "--languages=all", "--kinds-all=*", @@ -580,8 +634,15 @@ def dependency_signature(args, root, files): "Cargo.toml", "Cargo.lock", "project.json", "pyproject.toml", "uv.lock", "poetry.lock", "pixi.lock", "pixi.toml", "requirements.txt", "compile_commands.json", "flake.lock", "flake.nix", ".envrc", + "go.mod", "go.sum", "go.work", "go.work.sum", }: declarations.append((str(path), file_stamp(path))) + elif path.suffix.lower() in GO_SUFFIXES: + try: + imports = re.findall(r'(?m)^\s*import\s*(?:\([^)]*\)|[^\n]+)', path.read_text(errors="replace")) + declarations.append((str(path), imports)) + except OSError: + pass elif path.suffix.lower() in C_SOURCE_SUFFIXES | C_HEADER_SUFFIXES: try: includes = [line.strip() for line in path.read_text(errors="replace").splitlines() if line.lstrip().startswith(("#include", "# include"))] @@ -596,6 +657,8 @@ def dependency_signature(args, root, files): "PATH", "VIRTUAL_ENV", "CC", "CFLAGS", "CPPFLAGS", "PROJECT_CTAGS_PYTHON_COMMAND", "PROJECT_CTAGS_CARGO", "PROJECT_CTAGS_C3C", "C3_STDLIB", "PROJECT_CTAGS_SOURCES", + "PROJECT_CTAGS_GO", "GOROOT", "GOPATH", "GOMODCACHE", + "GOWORK", "GOOS", "GOARCH", "CGO_ENABLED", "GOFLAGS", ) }, [(str(root / name), file_stamp(root / name)) for name in (".venv", ".pixi/envs/default", ".devenv/state/venv")]] @@ -634,6 +697,7 @@ def refresh_cache(args, root, ctags): dependency_files.extend(python_dependency_files(args, root, files)[0]) if not args.no_rust_deps: dependency_files.extend(rust_dependency_files(args, root)[0]) + dependency_files.extend(go_dependency_files(args, root, files)[0]) if not args.no_c_deps: dependency_files.extend(c_dependency_files(root, files)) dependency_files.extend(extra_source_files(args, root)) @@ -692,8 +756,9 @@ def main(): c_headers = c_dependency_files(root, local_files) extra_files = extra_source_files(args, root) c3_files = c3_dependency_files(args, root, local_files) + go_files, go = go_dependency_files(args, root, local_files) all_files = sorted( - set(local_files + python_files + rust_files + c_headers + c3_files + extra_files) + set(local_files + python_files + rust_files + go_files + c_headers + c3_files + extra_files) ) if not all_files: raise SystemExit("No source files found") @@ -708,6 +773,8 @@ def main(): print(f"rust: {cargo}; {len(rust_files)} dependency files") if c_headers: print(f"c: {len(c_headers)} dependency headers") + if go: + print(f"go: {go}; {len(go_files)} dependency files") if c3_files: print(f"c3: {len(c3_files)} standard-library files") if extra_files: |
