From b13349f5d2785ca38141ee5a1717b826a7acfaf6 Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 8 Dec 2021 13:09:23 +0100 Subject: loosely translate Python to shell --- README.md | 56 ++++++++------------------- tex2nix/__init__.py | 108 ---------------------------------------------------- texnix | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 148 deletions(-) delete mode 100644 tex2nix/__init__.py create mode 100755 texnix diff --git a/README.md b/README.md index 1072dcf..57bde75 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,29 @@ -# tex2nix +# texnix + +_texnix is based on [tex2nix](https://github.com/Mic92/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 +## 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"; - -}) +$ echo '\usepackage{babel}' | texnix - +# Generated with texnix 1.0.0 +{ texlive, extraTexPackages ? {} }: +texlive.combine ({ + inherit (texlive) scheme-small; + "babel" = texlive."babel"; + "ctablestack" = texlive."ctablestack"; + "luatexbase" = texlive."luatexbase"; +} // extraTexPackages) ``` -tex2nix does not follow `\input` or `\include`. However you can specify multiple +texnix does not follow `\input` or `\include`. However you can specify multiple texfiles as input ```console -$ tex2nix *.tex +$ texnix *.tex > tex-env.nix ``` The resulting expression can be imported like this to in your document directory: @@ -56,7 +32,7 @@ The resulting expression can be imported like this to in your document directory # shell.nix with import {}; mkShell { - buildInputs = [ (pkgs.callPackage ./tex-env.nix {}) ]; + buildInputs = [ (callPackage ./tex-env.nix {}) ]; } ``` diff --git a/tex2nix/__init__.py b/tex2nix/__init__.py deleted file mode 100644 index 252895a..0000000 --- a/tex2nix/__init__.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/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() diff --git a/texnix b/texnix new file mode 100755 index 0000000..1f7cf7e --- /dev/null +++ b/texnix @@ -0,0 +1,102 @@ +#! /bin/sh +# usage: texnix TEXFILE... +# Print Nix expression for creating a TeX working environment for a set of +# TEXFILEs. + +set -efu + +texnix_version=1.0.0 + +TMPDIR=$(mktemp --tmpdir -d texnix.XXXXXXXX) +trap 'rm -R "$TMPDIR"' EXIT + +# Export TMPDIR for subsequent calls to mktemp. +export TMPDIR + +# usage: extract_dependencies TEXFILE... +# Print all (transitive) dependencies of TEXFILEs. +extract_dependencies() {( + all_packages=$TMPDIR/all_packages + dependencies=$TMPDIR/dependencies + working_set=$TMPDIR/working_set + + get_all_packages > "$all_packages" + truncate -s 0 "$dependencies" + get_packages "$@" | comm -12 - "$all_packages" > "$working_set" + + while test -s "$working_set"; do + package=$(pop "$working_set") + + echo "$package" | add "$dependencies" + + tmp_working_set=$(mktemp working_set.XXXXXXXX) + { cat "$working_set" + texlive_cat "$package" | get_packages - | comm -2 - "$dependencies" + } | sort -u | comm -12 - "$all_packages" > "$tmp_working_set" + mv "$tmp_working_set" "$working_set" + done + + cat "$dependencies" +)} + +# usage: texlive_cat PACKAGE +# Concatenate and print all files of a TeX live package. +texlive_cat() { + nix-build --no-out-link '' -A texlive."$1".pkgs | + while read -r path; do + find "$path" -mindepth 1 -maxdepth 1 -name tex -exec find {} -type f \; + done | + xargs cat +} + +# usage: get_all_packages +# Print all TeX live packages. +get_all_packages() { + nix-instantiate \ + --eval \ + --json \ + --strict \ + -E \ + 'builtins.attrNames (import {}).texlive' \ + | + jq -r .[] | + sort -u +} + +# usage: get_packages TEXFILE... +# Print all packages used in TEXFILEs. +get_packages() { + sed -rn ' + s:.*(usepackage|RequirePackage).*\{([^}]+)}:\2:;T + s:,:\n:g + p + ' "$@" | + sort -u +} + +# usage: add SETFILE +# Add lines read from stdin to SETFILE. +add() {( + t=$(mktemp add.XXXXXXXX) + sort -u - "$1" > "$t" + mv "$t" "$1" +)} + +# usage: pop FILE +# Remove first line of FILE and print it to stdout. +pop() { + head -1 "$1" + sed -i 1d "$1" +} + +cat <