summaryrefslogtreecommitdiff
path: root/support/spix
diff options
context:
space:
mode:
Diffstat (limited to 'support/spix')
-rw-r--r--support/spix/CHANGELOG.md21
-rw-r--r--support/spix/spix.148
-rw-r--r--support/spix/spix.pdfbin1521970 -> 1528250 bytes
-rwxr-xr-xsupport/spix/spix.py145
4 files changed, 211 insertions, 3 deletions
diff --git a/support/spix/CHANGELOG.md b/support/spix/CHANGELOG.md
index dea80d9162..59fbcaeb63 100644
--- a/support/spix/CHANGELOG.md
+++ b/support/spix/CHANGELOG.md
@@ -1,18 +1,33 @@
+* spix 1.1.0 (2020-07-23)
+
+ * Python 3.9 support.
+ * Replace `pathlib.PosixPath` with `pathlib.Path`.
+ * Merge `__init__.py` and `__main__.py` into a single `spix.py` file.
+ * Add a man page.
+
+ -- Louis Paternault <spalax@gresille.org>
+
+* spix 1.0.1 (2020-06-23)
+
+ * Do not crash when processing non-UTF8 files (closes #1).
+
+ -- Louis Paternault <spalax@gresille.org>
+
* spix 1.0.0 (2020-06-19)
* [doc] Minor documentation improvements.
- -- Louis Paternault <spalax+python@gresille.org>
+ -- Louis Paternault <spalax@gresille.org>
* spix 1.0.0-beta2 (2020-06-12)
* [command line] Use FILE instead of TEX in description of command line arguments.
* Code snippet can use environment variables `$basename`, `$texname`.
- -- Louis Paternault <spalax+python@gresille.org>
+ -- Louis Paternault <spalax@gresille.org>
* spix 1.0.0-beta (2020-06-11)
* First published version.
- -- Louis Paternault <spalax+python@gresille.org>
+ -- Louis Paternault <spalax@gresille.org>
diff --git a/support/spix/spix.1 b/support/spix/spix.1
new file mode 100644
index 0000000000..e2ade137ec
--- /dev/null
+++ b/support/spix/spix.1
@@ -0,0 +1,48 @@
+.TH SPIX 1
+.SH NAME
+spix \- Compile a .tex file, executing commands that are set inside the file itself.
+.SH SYNOPSIS
+.B spix
+[\fB\--version\fR]
+[\fB\-h\fR]
+[\fB\-n\fR]
+.IR file
+.SH DESCRIPTION
+.B spix
+parses a .tex file to find lines starting with \fI%$\fR (before the preambule).
+Those lines are shell commands that are executed by \fBspix\fR.
+.PP
+Commands are executed as-is, excepted that:
+.RS
+.PP
+- command are run from the directory of the file given in argument;
+.PP
+- shell variables \fI$texname\fR and \fI$basename\fR are set to the name of the tex file (respectively with and without the .tex extension).
+.RE
+.SH OPTIONS
+.TP
+.BR \-n ", " \-\-dry\-run
+Print the commands that would be executed, but do not execute them.
+.TP
+.BR \-h ", " \-\-help
+Print help, and exit.
+.TP
+.BR \-\-version
+Print version, and exit.
+.SH EXAMPLES
+Let \fIfoo.tex\fR be the following file.
+.PP
+.nf
+.RS
+% Compile this file twice with lualatex.
+%$ lualatex foo.tex
+%$ lualatex foo.tex
+
+\\documentclass{article}
+\\begin{document}
+Hello, world!
+\\end{document}
+.RE
+.fi
+.PP
+When running \fBspix\fR on this file, it is compiled twice using \fBlualatex(1)\fR (as written in lines 2 and 3 of the file).
diff --git a/support/spix/spix.pdf b/support/spix/spix.pdf
index 89ad286c90..699bacd9cc 100644
--- a/support/spix/spix.pdf
+++ b/support/spix/spix.pdf
Binary files differ
diff --git a/support/spix/spix.py b/support/spix/spix.py
new file mode 100755
index 0000000000..1369703a7a
--- /dev/null
+++ b/support/spix/spix.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+
+# Copyright 2020 Louis Paternault
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+"""Compile a `.tex` file, executing commands that are set inside the file itself."""
+
+import argparse
+import logging
+import os
+import pathlib
+import re
+import subprocess
+import sys
+
+NAME = "SpiX"
+VERSION = "1.1.0"
+
+RE_EMPTY = re.compile("^ *$")
+RE_COMMENT = re.compile("^ *%")
+RE_COMMAND = re.compile(r"^%\$ ?(.*)$")
+
+
+class SpixError(Exception):
+ """Exception that should be catched and nicely displayed to user."""
+
+
+def parse_lines(lines):
+ """Parse line to find code snippets.
+
+ :param iterable lines: Lines to parte (typically ``open("foo.tex").readlines()``.
+ :return: Iterator over snippets (as strings).
+ """
+ snippet = None
+ for line in lines:
+ line = line.rstrip("\n")
+ if RE_COMMAND.match(line):
+ match = RE_COMMAND.match(line)
+ if snippet is None:
+ snippet = ""
+ else:
+ snippet += "\n"
+ snippet += match.groups()[0]
+ elif RE_EMPTY.match(line) or RE_COMMENT.match(line):
+ if snippet is not None:
+ yield snippet
+ snippet = None
+ else:
+ break
+ if snippet is not None:
+ yield snippet
+
+
+def compiletex(filename, *, dryrun=False):
+ """Read commands from file, and execute them.
+
+ :param str filename: File to process.
+ :param bool dryrun: If ``True``, print commands to run, but do not execute them.
+ """
+ env = os.environ
+ filename = pathlib.Path(filename)
+ env["texname"] = filename.name
+ env["basename"] = filename.stem
+
+ try:
+ with open(filename, errors="ignore") as file:
+ for snippet in parse_lines(file.readlines()):
+ print(snippet)
+ if dryrun:
+ continue
+
+ subprocess.check_call(
+ ["sh", "-c", snippet, NAME, filename.name],
+ cwd=(pathlib.Path.cwd() / filename).parent,
+ env=env,
+ )
+ except subprocess.CalledProcessError:
+ raise SpixError()
+ except IsADirectoryError as error:
+ raise SpixError(str(error))
+
+
+def commandline_parser():
+ """Return a command line parser.
+
+ :rtype: argparse.ArgumentParser
+ """
+ parser = argparse.ArgumentParser(
+ prog="spix",
+ description=(
+ "Compile a `.tex` file, "
+ "executing commands that are set inside the file itself."
+ ),
+ )
+ parser.add_argument(
+ "-n",
+ "--dry-run",
+ action="store_true",
+ help="Print the commands that would be executed, but do not execute them.",
+ )
+ parser.add_argument(
+ "--version",
+ help="Show version and exit.",
+ action="version",
+ version=f"{NAME} {VERSION}",
+ )
+ parser.add_argument("FILE", nargs=1, help="File to process.")
+
+ return parser
+
+
+def main():
+ """Main function."""
+ arguments = commandline_parser().parse_args()
+
+ if os.path.exists(arguments.FILE[0]):
+ arguments.FILE = arguments.FILE[0]
+ elif os.path.exists(f"{arguments.FILE[0]}.tex"):
+ arguments.FILE = f"{arguments.FILE[0]}.tex"
+ else:
+ logging.error("""File not found: "%s".""", arguments.FILE[0])
+ sys.exit(1)
+
+ try:
+ compiletex(arguments.FILE, dryrun=arguments.dry_run)
+ except SpixError as error:
+ if str(error):
+ logging.error(error)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()