summaryrefslogtreecommitdiff
path: root/Omni/Ide/MakeTags.py
blob: 7f09a4e36c74c975eab0e8c7a54808954edb01bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/usr/bin/env python
"""
Make tags for internal or external code.

This should run fast, and be executable with just Python, meaning it does not
require a build step.
"""

# : out maketags
# : run universal-ctags
import argparse
import os
import pathlib
import subprocess
import tarfile
import zipfile


def main() -> None:
    """Run ctags on internal or external source code.

    Raises:
        ValueError: if CODEROOT is not set
        ArgumentError: when explicit paths aren't provided
    """
    coderoot = os.environ.get("CODEROOT")
    if coderoot is None:
        msg = "CODEROOT not set"
        raise ValueError(msg)
    cabsrc = pathlib.Path(coderoot) / "_" / "src"
    cli = argparse.ArgumentParser()
    cli.add_argument(
        "paths",
        nargs="*",
        default=".",
        help="List of paths to run ctags on. Defaults to '.'",
    )
    cli.add_argument(
        "-x",
        "--external",
        action="store_true",
        help=" ".join([
            "Use this when `paths` is a list of external packages,",
            f"they will be extracted or linked into {cabsrc}",
        ]),
    )
    args = cli.parse_args()
    if args.external and args.paths == ".":
        msg = "requires explicit paths"
        raise argparse.ArgumentError(argument=args.external, message=msg)
    if args.external:
        extract_and_copy(cabsrc, args.paths)
        ctags(["--recurse=yes"], cwd=cabsrc)
    else:
        ctags(["--exclude=*_/*", "--recurse=yes"], cwd=pathlib.Path(coderoot))


def strip_nix_hash(path: str) -> str:
    """Remove the /nix/store/ and hash prefix from a path."""
    hash_len = 33
    return path.removeprefix("/nix/store/")[hash_len:]


def extract_and_copy(cabsrc: pathlib.Path, paths: list[str]) -> None:
    """
    Extract and copy or link sources.

    Loop over `paths`, if the path is an archive, extract it into `cabsrc`. If
    its a directory, just symlink the directory into `cabsrc`. Either way, we
    end up with a directory full of source trees for running ctags on.
    """
    for path in paths:
        outpath: pathlib.Path = cabsrc / strip_nix_hash(path)
        if outpath.exists():
            continue
        if path.endswith(".zip"):
            out = outpath.with_suffix("")
            if out.exists():
                continue
            zipfile.ZipFile(path).extractall(out)  # noqa: S202
        elif path.endswith(".tar.gz"):
            out = outpath.with_suffix("").with_suffix("")
            if out.exists():
                continue
            with tarfile.open(path) as tarball:
                tarball.extractall(out)  # noqa: S202
        elif pathlib.Path(path).is_dir():
            outpath.symlink_to(path)


def ctags(args: list[str], cwd: pathlib.Path = pathlib.Path()) -> None:
    """Call `ctags` with `args` for both emacs and vim."""
    os.chdir(cwd)
    excludes = [
        "--exclude=.mypy_cache",
        "--exclude=.git",
        "--exclude=.direnv",
        "--exclude=.ruff_cache",
    ]
    subprocess.check_call(["ctags", *excludes, *args])
    subprocess.check_call(["ctags", "-e", *excludes, *args])