diff --git a/.github/workflows/Arch.yaml b/.github/workflows/Arch.yaml index ec947f1b..f3777c8c 100644 --- a/.github/workflows/Arch.yaml +++ b/.github/workflows/Arch.yaml @@ -20,7 +20,7 @@ jobs: - name: Prepare system run: | sysctl kernel.perf_event_paranoid=-1 - pacman -Syu --noconfirm diffutils time gcc dpkg + pacman -Syu --noconfirm diffutils time gcc dpkg ghostscript texlive-latexextra - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/Ubuntu.yaml b/.github/workflows/Ubuntu.yaml index c754d1db..b63baad1 100644 --- a/.github/workflows/Ubuntu.yaml +++ b/.github/workflows/Ubuntu.yaml @@ -14,6 +14,8 @@ jobs: - /home/actions/oiejq:/github/home/.local/bin env: DEB_PYTHON_INSTALL_LAYOUT: deb + DEBIAN_FRONTEND: noninteractive + TZ: Europe/Warsaw options: --privileged steps: @@ -22,7 +24,7 @@ jobs: - name: Prepare system run: | apt update - apt install -y sqlite3 sqlite3-doc build-essential dpkg + apt install -y sqlite3 sqlite3-doc build-essential dpkg texlive-latex-extra ghostscript sysctl kernel.perf_event_paranoid=-1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 diff --git a/.github/workflows/macOS.yaml b/.github/workflows/macOS.yaml index 35591120..dbe1eda0 100644 --- a/.github/workflows/macOS.yaml +++ b/.github/workflows/macOS.yaml @@ -25,7 +25,7 @@ jobs: run: | rm -f /usr/local/bin/2to3* /usr/local/bin/python3* /usr/local/bin/idle3* \ /usr/local/bin/pydoc3* # Homebrew will fail if these exist - brew install gnu-time coreutils diffutils dpkg + brew install gnu-time coreutils diffutils dpkg ghostscript texlive - name: Install Python dependencies run: | python3 -m pip install .[tests] diff --git a/README.md b/README.md index 26325033..18a83c4f 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ You can also specify your ingen source file which will be used. Run `sinol-make - `sinol-make inwer` -- Verifies whether input files are correct using your "inwer.cpp" program. You can specify what inwer program to use, what tests to check and how many CPUs to use. Run `sinol-make inwer --help` to see available flags. - `sinol-make export` -- Creates archive ready to upload to sio2 or szkopul. Run `sinol-make export --help` to see all available flags. +- `sinol-make doc` -- Compiles all LaTeX files in doc/ directory to PDF. Run `sinol-make doc --help` to see all available flags. ### Reporting bugs and contributing code diff --git a/src/sinol_make/commands/doc/__init__.py b/src/sinol_make/commands/doc/__init__.py new file mode 100644 index 00000000..d6fd78fc --- /dev/null +++ b/src/sinol_make/commands/doc/__init__.py @@ -0,0 +1,71 @@ +import os +import glob +import argparse +import subprocess + +from sinol_make import util +from sinol_make.interfaces.BaseCommand import BaseCommand + + +class Command(BaseCommand): + """ + Class for `doc` command. + """ + + def get_name(self): + return "doc" + + def compile_file(self, file_path): + print(util.info(f'Compiling {os.path.basename(file_path)}...')) + os.chdir(os.path.dirname(file_path)) + subprocess.run(['latex', file_path]) + dvi_file = os.path.splitext(file_path)[0] + '.dvi' + dvi_file_path = os.path.join(os.path.dirname(file_path), dvi_file) + if not os.path.exists(dvi_file_path): + print(util.error('Compilation failed.')) + return False + + process = subprocess.run(['dvipdf', dvi_file_path]) + if process.returncode != 0: + print(util.error('Compilation failed.')) + return False + print(util.info(f'Compilation successful for file {os.path.basename(file_path)}.')) + return True + + def configure_subparser(self, subparser: argparse.ArgumentParser): + parser = subparser.add_parser( + self.get_name(), + help='Compile latex files to pdf', + description='Compiles latex files to pdf. By default compiles all files in the `doc` directory.\n' + 'You can also specify files to compile.') + parser.add_argument('files', type=str, nargs='*', help='files to compile') + + def run(self, args: argparse.Namespace): + util.exit_if_not_package() + + if args.files == []: + self.files = glob.glob(os.path.join(os.getcwd(), 'doc', '*.tex')) + else: + self.files = [] + for file in args.files: + if not os.path.exists(file): + print(util.warning(f'File {file} does not exist.')) + else: + self.files.append(os.path.abspath(file)) + if self.files == []: + print(util.warning('No files to compile.')) + return + + original_cwd = os.getcwd() + failed = [] + for file in self.files: + if not self.compile_file(file): + failed.append(file) + os.chdir(original_cwd) + + if failed: + for failed_file in failed: + print(util.error(f'Failed to compile {failed_file}')) + util.exit_with_error('Compilation failed.') + else: + print(util.info('Compilation was successful for all files.')) diff --git a/src/sinol_make/commands/export/__init__.py b/src/sinol_make/commands/export/__init__.py index 127e358c..7976d20d 100644 --- a/src/sinol_make/commands/export/__init__.py +++ b/src/sinol_make/commands/export/__init__.py @@ -83,6 +83,16 @@ def copy_package_required_files(self, target_dir: str): print(util.warning(f'Coping {os.path.basename(test)}...')) shutil.copy(test, os.path.join(target_dir, os.path.splitext(os.path.basename(test))[1])) + def clear_files(self, target_dir: str): + """ + Clears unnecessary files from target directory. + :param target_dir: Directory to clear files from. + """ + files_to_remove = ['doc/*~', 'doc/*.aux', 'doc/*.log', 'doc/*.dvi', 'doc/*.err', 'doc/*.inf'] + for pattern in files_to_remove: + for f in glob.glob(os.path.join(target_dir, pattern)): + os.remove(f) + def create_makefile_in(self, target_dir: str, config: dict): """ Creates required `makefile.in` file. @@ -143,6 +153,7 @@ def run(self, args: argparse.Namespace): util.change_stack_size_to_unlimited() self.copy_package_required_files(export_package_path) + self.clear_files(export_package_path) self.create_makefile_in(export_package_path, config) archive = self.compress(export_package_path) diff --git a/tests/commands/doc/test_integration.py b/tests/commands/doc/test_integration.py new file mode 100644 index 00000000..5ef28e16 --- /dev/null +++ b/tests/commands/doc/test_integration.py @@ -0,0 +1,32 @@ +import pytest + +from sinol_make import configure_parsers +from sinol_make.commands.doc import Command +from tests.fixtures import create_package +from tests import util + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_simple(capsys, create_package): + """ + Test `doc` command with no parameters. + """ + parser = configure_parsers() + args = parser.parse_args(["doc"]) + command = Command() + command.run(args) + out = capsys.readouterr().out + assert "Compilation was successful for all files." in out + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_argument(capsys, create_package): + """ + Test `doc` command with specified file. + """ + parser = configure_parsers() + args = parser.parse_args(["doc", "doc/doczad.tex"]) + command = Command() + command.run(args) + out = capsys.readouterr().out + assert "Compilation was successful for all files." in out diff --git a/tests/commands/doc/test_unit.py b/tests/commands/doc/test_unit.py new file mode 100644 index 00000000..d4f4545c --- /dev/null +++ b/tests/commands/doc/test_unit.py @@ -0,0 +1,12 @@ +import os +import pytest + +from sinol_make.commands.doc import Command +from tests.fixtures import create_package +from tests import util + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_compile_file(create_package): + command = Command() + assert command.compile_file(os.path.abspath(os.path.join(os.getcwd(), "doc/doczad.tex"))) is True diff --git a/tests/commands/export/test_integration.py b/tests/commands/export/test_integration.py index 851d52c6..f7fe1dea 100644 --- a/tests/commands/export/test_integration.py +++ b/tests/commands/export/test_integration.py @@ -5,6 +5,7 @@ import tempfile from sinol_make import configure_parsers +from sinol_make.commands.doc import Command as DocCommand from tests import util from tests.fixtures import create_package from .util import * @@ -56,3 +57,26 @@ def test_simple(create_package, capsys): task_id = package_util.get_task_id() out = capsys.readouterr().out _test_archive(package_path, out, f'{task_id}.tgz') + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_doc_cleared(create_package): + """ + Test if files in `doc` directory are cleared. + """ + parser = configure_parsers() + args = parser.parse_args(["doc"]) + command = DocCommand() + command.run(args) + args = parser.parse_args(["export"]) + command = Command() + command.run(args) + + with tempfile.TemporaryDirectory() as tmpdir: + with tarfile.open(f'{package_util.get_task_id()}.tgz', "r") as tar: + tar.extractall(tmpdir) + + extracted = os.path.join(tmpdir, package_util.get_task_id()) + assert os.path.exists(extracted) + for pattern in ['doc/*~', 'doc/*.aux', 'doc/*.log', 'doc/*.dvi', 'doc/*.err', 'doc/*.inf']: + assert glob.glob(os.path.join(extracted, pattern)) == [] diff --git a/tests/packages/doc/config.yml b/tests/packages/doc/config.yml new file mode 100644 index 00000000..0de939ee --- /dev/null +++ b/tests/packages/doc/config.yml @@ -0,0 +1,3 @@ +title: Package for testing `doc` command +time_limit: 1000 +memory_limit: 1024 diff --git a/tests/packages/doc/doc/doczad.tex b/tests/packages/doc/doc/doczad.tex new file mode 100644 index 00000000..4372318a --- /dev/null +++ b/tests/packages/doc/doc/doczad.tex @@ -0,0 +1,4 @@ +\documentclass{article} +\begin{document} + Hello World! +\end{document} diff --git a/tests/packages/doc/in/.gitkeep b/tests/packages/doc/in/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/packages/doc/out/.gitkeep b/tests/packages/doc/out/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/util.py b/tests/util.py index e9d86bdd..c65fa34e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -88,6 +88,13 @@ def get_override_limits_package_path(): return os.path.join(os.path.dirname(__file__), "packages", "ovl") +def get_doc_package_path(): + """ + Get path to package for testing `doc` command (/test/packages/doc) + """ + return os.path.join(os.path.dirname(__file__), "packages", "doc") + + def get_long_name_package_path(): """ Get path to package with long name (/test/packages/long_package_name)