aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md56
-rw-r--r--tex2nix/__init__.py108
-rwxr-xr-xtexnix102
3 files changed, 118 insertions, 148 deletions
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 <nixpkgs> {};
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",
- "<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()
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 '<nixpkgs>' -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 <nixpkgs> {}).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 <<EOF
+# Generated with texnix $texnix_version
+{ texlive, extraTexPackages ? {} }:
+texlive.combine ({
+ inherit (texlive) scheme-small;
+ $(extract_dependencies "$@" | sed '
+ s/.*/"&" = texlive."&";/
+ 2,$s/^/ /
+ ')
+} // extraTexPackages)
+EOF