From c934073436f1b19289634ad1abda6d40d26afb14 Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 8 Dec 2021 01:05:35 +0100 Subject: import tex2nix bits From https://github.com/Mic92/tex2nix commit 9379d49 --- README.md | 68 +++++++++++++++++++++++++++++++++ tex2nix/__init__.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 README.md create mode 100644 tex2nix/__init__.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..1072dcf --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# tex2nix + +Generate Texlive environment containing all dependencies for your document +rather than downloading gigabytes of texlive packages. + +## Installation + +With stable nix you can do: + +``` console +nix-build && ./result/bin/tex2nix +``` + +If you use flakes put the following in your inputs + +```nix +{ + inputs.tex2nix.url = "github:Mic92/tex2nix"; + inputs.tex2nix.inputs.utils.follows = "nixpkgs"; +} +``` + +or just do: + +```console +$ nix run github:Mic92/tex2nix +``` + + +## USAGE + +```console +$ tex2nix main.tex +wrote tex-env.nix +$ cat tex-env.nix +# Generated with tex2nix 0.0.0 +{ texlive }: +(texlive.combine { + inherit (texlive) scheme-small; + "varwidth" = texlive."varwidth"; + "tabu" = texlive."tabu"; + +}) +``` + +tex2nix does not follow `\input` or `\include`. However you can specify multiple +texfiles as input + +```console +$ tex2nix *.tex +``` + +The resulting expression can be imported like this to in your document directory: + +```nix +# shell.nix +with import {}; +mkShell { + buildInputs = [ (pkgs.callPackage ./tex-env.nix {}) ]; +} +``` + +``` console +$ nix-shell shell.nix +nix-shell> pdflatex --version +pdfTeX 3.14159265-2.6-1.40.21 (TeX Live 2020/NixOS.org) +... +``` diff --git a/tex2nix/__init__.py b/tex2nix/__init__.py new file mode 100644 index 0000000..252895a --- /dev/null +++ b/tex2nix/__init__.py @@ -0,0 +1,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", + "", + "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", "", "-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() -- cgit v1.2.3