aboutsummaryrefslogtreecommitdiffstats
path: root/tex2nix/__init__.py
blob: 252895ab94fabd6739ca4899cffc4a0457c6126a (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
102
103
104
105
106
107
108
#!/usr/bin/env python3

import re
import fileinput
import subprocess
import json
import os

from typing import Set, Iterable


def get_nix_packages() -> Set[str]:
    res = subprocess.run(
        [
            "nix",
            "--experimental-features",
            "nix-command",
            "eval",
            "--json",
            "-f",
            "<nixpkgs>",
            "texlive",
            "--apply",
            "builtins.attrNames",
        ],
        check=True,
        text=True,
        stdout=subprocess.PIPE,
    )
    return set(json.loads(res.stdout))


def get_packages(line: str) -> Set[str]:
    match = re.match(r"\\(?:usepackage|RequirePackage).*{([^}]+)}", line)
    if not match:
        return set()
    args = match.group(1)
    packages = set()
    for arg in args.split(","):
        packages.add(arg.strip())
    return packages


def _collect_deps(
    working_set: Set[str], done: Set[str], all_packages: Set[str]
) -> None:
    package = working_set.pop()
    done.add(package)
    cmd = ["nix-build", "--no-out-link", "<nixpkgs>", "-A", f"texlive.{package}.pkgs"]
    paths = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE)
    for dir in paths.stdout.split("\n"):
        path = os.path.join(dir, "tex")
        for root, _, files in os.walk(path):
            for fname in files:
                path = os.path.join(root, fname)
                with open(path, "rb") as f:
                    for line in f:
                        packages = get_packages(line.decode("utf-8", errors="ignore"))
                        working_set |= all_packages & (packages - done)


def collect_deps(packages: Set[str], all_packages: Set[str]) -> Set[str]:
    working_set: Set[str] = set(packages)
    done: Set[str] = set()
    while working_set:
        _collect_deps(working_set, done, all_packages)

    return done


def write_tex_env(dir: str, packages: Set[str]) -> str:
    name = os.path.join(dir, "tex-env.nix")
    with open(name, "w") as f:
        f.write(
            """# Generated with tex2nix 0.0.0
{ texlive, extraTexPackages ? {} }:
(texlive.combine ({
    inherit (texlive) scheme-small;
"""
        )
        for package in packages:
            f.write(f'    "{package}" = texlive."{package}";\n')
        f.write(
            """
} // extraTexPackages))
"""
        )
    print("wrote tex-env.nix...")
    return name


def extract_dependencies(lines: Iterable[str]) -> Set[str]:
    packages = set()
    for line in lines:
        packages |= get_packages(line)

    all_packages = get_nix_packages()
    nix_packages = packages & all_packages
    return collect_deps(nix_packages, all_packages)


def main() -> None:
    packages = extract_dependencies(fileinput.input())
    write_tex_env(os.getcwd(), packages)


if __name__ == "__main__":
    main()