Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ classifiers = [
"Topic :: Utilities",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]

requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = []

[project.urls]
Expand All @@ -52,3 +52,6 @@ dev = [
"pytest>=8.3.4",
"ruff>=0.9.7",
]

[tool.ruff.lint.pyupgrade]
keep-runtime-typing = true
47 changes: 24 additions & 23 deletions pytinytex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
import warnings
from collections import deque
from contextlib import suppress
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _metadata_version
from pathlib import Path
from importlib.metadata import version as _metadata_version, PackageNotFoundError

try:
__version__ = _metadata_version("PyTinyTeX")
except PackageNotFoundError:
__version__ = "unknown"

from .tinytex_download import download_tinytex, DEFAULT_TARGET_FOLDER # noqa
from .log_parser import LogEntry, ParsedLog, parse_log # noqa
from .compiler import CompileResult, compile # noqa
from .doctor import DoctorCheck, DoctorResult, doctor # noqa
from .log_parser import LogEntry, ParsedLog, parse_log
from .compiler import CompileResult, compile
from .doctor import DoctorCheck, DoctorResult, doctor
from .tlmgr import ( # noqa
install,
remove,
Expand Down Expand Up @@ -53,35 +54,35 @@
__tinytex_path = None

__all__ = [
"download_tinytex",
"DEFAULT_TARGET_FOLDER",
"ensure_tinytex_installed",
"get_tinytex_path",
"CompileResult",
"DoctorCheck",
"DoctorResult",
"LogEntry",
"ParsedLog",
"__version__",
"clear_path_cache",
"compile",
"doctor",
"download_tinytex",
"ensure_tinytex_installed",
"get_engine",
"get_pdflatex_engine",
"get_xelatex_engine",
"get_lualatex_engine",
"get_pdf_latex_engine",
"get_pdflatex_engine",
"get_tinytex_path",
"get_version",
"get_xelatex_engine",
"help",
"info",
"install",
"remove",
"list_installed",
"parse_log",
"remove",
"search",
"info",
"update",
"uninstall",
"help",
"shell",
"compile",
"CompileResult",
"LogEntry",
"ParsedLog",
"parse_log",
"doctor",
"DoctorResult",
"DoctorCheck",
"__version__",
"uninstall",
"update",
]


Expand Down
8 changes: 4 additions & 4 deletions pytinytex/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class _CliFormatter(logging.Formatter):
def format(self, record):
if record.levelno == logging.INFO:
return record.getMessage()
return "%s: %s" % (record.levelname.lower(), record.getMessage())
return f"{record.levelname.lower()}: {record.getMessage()}"


def _setup_logging():
Expand Down Expand Up @@ -160,7 +160,7 @@ def main(argv=None):
loc = " (" + loc + ")"
print(" ! " + entry.message + loc)
if result.warnings:
print("Warnings: %d" % len(result.warnings))
print(f"Warnings: {len(result.warnings)}")
if result.success:
print("Success: " + str(result.pdf_path))
else:
Expand Down Expand Up @@ -193,7 +193,7 @@ def main(argv=None):
result = pytinytex.doctor()
for check in result.checks:
status = "PASS" if check.passed else "FAIL"
print(" [%s] %s: %s" % (status, check.name, check.message))
print(f" [{status}] {check.name}: {check.message}")
if result.healthy:
print("\nAll checks passed.")
else:
Expand All @@ -214,7 +214,7 @@ def main(argv=None):
print("TinyTeX uninstalled.")

except RuntimeError as e:
print("Error: %s" % e, file=sys.stderr)
print(f"Error: {e}", file=sys.stderr)
return 1

return 0
13 changes: 7 additions & 6 deletions pytinytex/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import subprocess
import sys
from pathlib import Path
from typing import List, Optional
from typing import Optional

from .log_parser import LogEntry, ParsedLog, parse_log

Expand All @@ -24,9 +24,9 @@ class CompileResult:
log_path: Optional[str] = None
engine: str = "pdflatex"
runs: int = 0
errors: List[LogEntry] = dataclasses.field(default_factory=list)
warnings: List[LogEntry] = dataclasses.field(default_factory=list)
installed_packages: List[str] = dataclasses.field(default_factory=list)
errors: list[LogEntry] = dataclasses.field(default_factory=list)
warnings: list[LogEntry] = dataclasses.field(default_factory=list)
installed_packages: list[str] = dataclasses.field(default_factory=list)
output: str = ""


Expand Down Expand Up @@ -110,6 +110,7 @@ def compile(
try:
proc = subprocess.run(
cmd,
check=False,
capture_output=True,
creationflags=creation_flag,
)
Expand All @@ -119,7 +120,7 @@ def compile(
if stderr:
output_lines.append(stderr)
exit_code = proc.returncode
except Exception as e:
except OSError as e:
output_lines.append(str(e))
exit_code = 1
break
Expand All @@ -134,7 +135,7 @@ def compile(
with open(log_path, encoding="utf-8", errors="replace") as f:
log_content = f.read()
parsed = parse_log(log_content)
except Exception as e:
except (OSError, ValueError) as e:
logger.warning("Failed to parse log file: %s", e)

result.errors = parsed.errors
Expand Down
15 changes: 7 additions & 8 deletions pytinytex/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import dataclasses
import os
import platform
from typing import List


@dataclasses.dataclass
Expand All @@ -20,7 +19,7 @@ class DoctorResult:
"""Overall health check result."""

healthy: bool
checks: List[DoctorCheck] = dataclasses.field(default_factory=list)
checks: list[DoctorCheck] = dataclasses.field(default_factory=list)


def doctor():
Expand All @@ -29,15 +28,15 @@ def doctor():
Returns:
DoctorResult with individual check outcomes.
"""
from . import get_tinytex_path, _LATEX_ENGINES, _find_file, _is_on_path
from . import _LATEX_ENGINES, _find_file, _is_on_path, get_tinytex_path
from .tlmgr import get_version

checks = []

# 1. TinyTeX installed?
try:
path = get_tinytex_path()
checks.append(DoctorCheck("TinyTeX installed", True, "Found at %s" % path))
checks.append(DoctorCheck("TinyTeX installed", True, f"Found at {path}"))
except RuntimeError as e:
checks.append(DoctorCheck("TinyTeX installed", False, str(e)))
return DoctorResult(healthy=False, checks=checks)
Expand All @@ -52,7 +51,7 @@ def doctor():
DoctorCheck(
"PATH configured",
False,
"TinyTeX bin directory (%s) is not on PATH" % path,
f"TinyTeX bin directory ({path}) is not on PATH",
)
)

Expand All @@ -61,7 +60,7 @@ def doctor():
if tlmgr:
checks.append(DoctorCheck("tlmgr found", True, tlmgr))
else:
checks.append(DoctorCheck("tlmgr found", False, "tlmgr not found in %s" % path))
checks.append(DoctorCheck("tlmgr found", False, f"tlmgr not found in {path}"))

# 4. tlmgr functional?
if tlmgr:
Expand All @@ -76,11 +75,11 @@ def doctor():
for engine_name in _LATEX_ENGINES:
engine_path = os.path.join(path, engine_name + suffix)
if os.path.isfile(engine_path):
checks.append(DoctorCheck("Engine: %s" % engine_name, True, engine_path))
checks.append(DoctorCheck(f"Engine: {engine_name}", True, engine_path))
else:
checks.append(
DoctorCheck(
"Engine: %s" % engine_name,
f"Engine: {engine_name}",
False,
"Not found (requires TinyTeX variation >= 1)",
)
Expand Down
8 changes: 4 additions & 4 deletions pytinytex/log_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import dataclasses
import re
from typing import List, Optional
from typing import Optional


@dataclasses.dataclass
Expand All @@ -22,9 +22,9 @@ class LogEntry:
class ParsedLog:
"""Structured result of parsing a LaTeX log file."""

errors: List[LogEntry] = dataclasses.field(default_factory=list)
warnings: List[LogEntry] = dataclasses.field(default_factory=list)
missing_packages: List[str] = dataclasses.field(default_factory=list)
errors: list[LogEntry] = dataclasses.field(default_factory=list)
warnings: list[LogEntry] = dataclasses.field(default_factory=list)
missing_packages: list[str] = dataclasses.field(default_factory=list)


# Patterns for extracting information from LaTeX logs
Expand Down
30 changes: 17 additions & 13 deletions pytinytex/tinytex_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _default_progress(downloaded, total):
mb = downloaded / (1024 * 1024)
mb_total = total / (1024 * 1024)
sys.stdout.write(
"\rDownloading TinyTeX: %.1f/%.1f MB (%d%%)" % (mb, mb_total, pct)
f"\rDownloading TinyTeX: {mb:.1f}/{mb_total:.1f} MB ({pct}%)"
)
sys.stdout.flush()
if downloaded >= total:
Expand All @@ -50,9 +50,7 @@ def download_tinytex(
):
if variation not in [None, 0, 1, 2]:
raise RuntimeError(
"Invalid TinyTeX variation {}. Valid variations are None, 0, 1, 2.".format(
variation
)
f"Invalid TinyTeX variation {variation}. Valid variations are None, 0, 1, 2."
)
if version == "latest" and variation == 2:
logger.info("TinyTeX-2 is only published in the daily release, using it.")
Expand All @@ -65,10 +63,10 @@ def download_tinytex(
version = "v" + version
elif version not in ("latest", "daily"):
raise RuntimeError(
"Invalid TinyTeX version {}. TinyTeX version has to be in the format "
f"Invalid TinyTeX version {version}. TinyTeX version has to be in the format "
"'latest' for the latest available version, 'daily' for the daily "
"build, or year.month, for example: '2024.12', '2024.09' for a "
"specific version.".format(version)
"specific version."
)
if progress_callback is None and sys.stdout.isatty():
progress_callback = _default_progress
Expand Down Expand Up @@ -135,7 +133,7 @@ def download_tinytex(
with tarfile.open(filename, "r:*") as tf:
tf.extractall(tmpdirname)
else:
raise RuntimeError("File {0} not supported".format(filename))
raise RuntimeError(f"File {filename} not supported")
# archives contain a single "TinyTeX" (or ".TinyTeX" on Linux) folder
tinytex_extracted = next(
p for p in tmpdirname.iterdir() if p.name.lstrip(".") == "TinyTeX"
Expand All @@ -160,7 +158,7 @@ def _get_tinytex_urls(version, variation):
version_url_frags = response.url.split("/")
version = version_url_frags[-1]
except urllib.error.HTTPError:
raise RuntimeError("Can't find TinyTeX version %s" % version)
raise RuntimeError(f"Can't find TinyTeX version {version}")
response = urlopen(
"https://github.com/rstudio/tinytex-releases/releases/expanded_assets/"
+ version
Expand Down Expand Up @@ -206,11 +204,17 @@ def _select_tinytex_urls(asset_paths, variation, arm64, musl=False):
new_style["darwin"] = url
elif os_name == "windows":
new_style["win32"] = url
elif os_name == "linux-arm64" and arm64 and not musl:
new_style["linux"] = url
elif os_name == "linux-x86_64" and not arm64 and not musl:
new_style["linux"] = url
elif os_name == "linuxmusl-x86_64" and not arm64 and musl:
elif (
os_name == "linux-arm64"
and arm64
and not musl
or os_name == "linux-x86_64"
and not arm64
and not musl
or os_name == "linuxmusl-x86_64"
and not arm64
and musl
):
new_style["linux"] = url
elif os_name is None:
if ext == "zip":
Expand Down
Loading
Loading