diff --git a/README.md b/README.md index 17d14308..6c641f9e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# st-make +# ![Logo](https://avatars.githubusercontent.com/u/93839068?s=60&v=4) st-make `st-make` is a CLI tool for creating and verifying problem packages for [sio2](https://github.com/sio2project/oioioi) with features such as: + - measuring time and memory in the same deterministic way as sio2, - running the solutions in parallel, - keeping a git-friendly report of solutions' scores, @@ -11,12 +12,11 @@ with features such as: This tool is a fork of [sinol-make](https://github.com/sio2project/sinol-make), with features specific to [Talent](https://talent.edu.pl/) contests. -# Contents +## Contents - [Why?](#why) - [Installation](#installation) - [Usage](#usage) -- [Configuarion](#configuration) - [Reporting bugs and contributing code](#reporting-bugs-and-contributing-code) ### Why? @@ -36,9 +36,10 @@ solutions' runtime, called `oiejq`. It's possible to directly install [st-make](https://pypi.org/project/st-make/) through Python's package manager pip, which usually is installed alongside Python: -``` +```bash pip3 install st-make ``` + `pip` installs the `st-make` executable in `~/.local/bin/` directory, so make sure this directory is in your `PATH`. [Here's](https://gist.github.com/nex3/c395b2f8fd4b02068be37c961301caa7) how to add a directory to `PATH`. @@ -48,11 +49,12 @@ As `oiejq` works only on Linux-based operating systems, Nevertheless `st-make` supports those operating systems, though there are additional installation steps required to use other tools for measuring time (which are non-deterministic and produce reports different from sio2): + - Debian-based systems (Ubuntu, usually Windows WSL): `apt install time` - Arch-based systems: `pacman -S time` - macOS: `brew install gnu-time coreutils` -### Autocompletion (optional) +#### Autocompletion (optional) If you would like to have autocompletion for `st-make` commands, run the following command and refresh the shell (e.g. by opening a new terminal): @@ -81,8 +83,9 @@ Run `st-make ingen --help` to see available flags. - `st-make outgen` -- Generate output files using the model solutions. Run `st-make outgen --help` to see available flags. - `st-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 `st-make inwer --help` to see available flags. -- `st-make export` -- Creates archive ready to upload to sio2 or szkopul. Run `st-make export --help` to see all available flags. +- `st-make export` -- Creates archive ready to upload to Wyzwania, Talent-camp and other sio2 instances. Run `st-make export --help` to see all available flags. - `st-make doc` -- Compiles all LaTeX files in doc/ directory to PDF. Run `st-make doc --help` to see all available flags. +- `st-make init [id]` -- Creates package from template [on github](https://github.com/Stowarzyszenie-Talent/st-make/tree/main/example_package) and sets task id to provided `[id]`. Requires an internet connection to run. ### Reporting bugs and contributing code diff --git a/src/sinol_make/__init__.py b/src/sinol_make/__init__.py index 55079849..1174ff22 100644 --- a/src/sinol_make/__init__.py +++ b/src/sinol_make/__init__.py @@ -1,15 +1,13 @@ # PYTHON_ARGCOMPLETE_OK +import argparse +import traceback +from time import sleep import argcomplete -import traceback -import argparse -import sys -import os from sinol_make import util, oiejq - -__version__ = "0.0.1" +__version__ = "0.0.2" def configure_parsers(): @@ -34,6 +32,21 @@ def configure_parsers(): return parser +def check_oiejq(): + if util.is_linux() and not oiejq.check_oiejq(): + print(util.warning('`oiejq` in `~/.local/bin/` not found, installing now...')) + try: + if oiejq.install_oiejq(): + print(util.info('`oiejq` was successfully installed.')) + else: + util.exit_with_error('`oiejq` could not be installed.\n' + 'You can download it from https://oij.edu.pl/zawodnik/srodowisko/oiejq.tar.gz, ' + 'unpack it to `~/.local/bin/` and rename oiejq.sh to oiejq.\n' + 'You can also use --oiejq-path to specify path to your oiejq.') + except Exception as err: + util.exit_with_error('`oiejq` could not be installed.\n' + err) + + def main_exn(): parser = configure_parsers() args = parser.parse_args() @@ -41,26 +54,7 @@ def main_exn(): for command in commands: if command.get_name() == args.command: - new_version = util.check_for_updates(__version__) - if new_version is not None: - print(util.warning( - f'New version of st-make is available (your version: {__version__}, available version: {new_version}).\n' - f' You can update it by running `pip3 install st-make --upgrade`.')) - - if util.is_linux() and not oiejq.check_oiejq(): - print(util.warning('`oiejq` in `~/.local/bin/` not found, installing now...')) - - try: - if oiejq.install_oiejq(): - print(util.info('`oiejq` was successfully installed.')) - else: - util.exit_with_error('`oiejq` could not be installed.\n' - 'You can download it from https://oij.edu.pl/zawodnik/srodowisko/oiejq.tar.gz' - ', unpack it to `~/.local/bin/` and rename oiejq.sh to oiejq.\n' - 'You can also use --oiejq-path to specify path to your oiejq.') - except Exception as err: - util.exit_with_error('`oiejq` could not be installed.\n' + err) - + check_oiejq() command.run(args) exit(0) @@ -68,14 +62,21 @@ def main_exn(): def main(): + new_version = None try: + new_version = util.check_for_updates(__version__) main_exn() except argparse.ArgumentError as err: util.exit_with_error(err) except SystemExit as err: exit(err.code) - except: + except Exception: print(traceback.format_exc()) util.exit_with_error('An error occurred while running the command.\n' 'If that is a bug, please report it or submit a bugfix: ' 'https://github.com/Stowarzyszenie-Talent/st-make/#reporting-bugs-and-contributing-code') + finally: + if new_version is not None: + print(util.warning( + f'New version of sinol-make is available (your version: {__version__}, available version: ' + f'{new_version}).\nYou can update it by running `pip3 install sinol-make --upgrade`.')) diff --git a/src/sinol_make/commands/doc/__init__.py b/src/sinol_make/commands/doc/__init__.py index d495853d..3d624bed 100644 --- a/src/sinol_make/commands/doc/__init__.py +++ b/src/sinol_make/commands/doc/__init__.py @@ -17,8 +17,8 @@ class Command(BaseCommand): def get_name(self): return "doc" - def compile_file(self, file_path): - print(util.info(f'Compiling {os.path.basename(file_path)}...')) + def compile_file_latex_div(self, file_path): + print(util.info(f'Compiling {os.path.basename(file_path)} (latex to dvi)...')) os.chdir(os.path.dirname(file_path)) subprocess.run(['latex', file_path]) dvi_file = os.path.splitext(file_path)[0] + '.dvi' @@ -34,13 +34,27 @@ def compile_file(self, file_path): print(util.info(f'Compilation successful for file {os.path.basename(file_path)}.')) return True + def compile_pdf_latex(self, file_path): + print(util.info(f'Compiling {os.path.basename(file_path)} (pdflatex)...')) + os.chdir(os.path.dirname(file_path)) + subprocess.run(['pdflatex', file_path]) + pdf_file = os.path.splitext(file_path)[0] + '.pdf' + pdf_file_path = os.path.join(os.path.dirname(file_path), pdf_file) + if not os.path.exists(pdf_file_path): + print(util.error('Compilation failed.')) + return False + return True + def make_file(self, file_path): """ Compile the file two times to get the references right. """ - if not self.compile_file(file_path): - return False - return self.compile_file(file_path) + if self.compilation_method == 'pdflatex': + return self.compile_pdf_latex(file_path) + else: + if not self.compile_file_latex_div(file_path): + return False + return self.compile_file_latex_div(file_path) def move_logs(self): output_dir = paths.get_cache_path('doc_logs') @@ -56,11 +70,31 @@ def configure_subparser(self, subparser: argparse.ArgumentParser): 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('--latex-compiler', dest='latex_compiler', choices=['auto', 'pdflatex', 'latex_dvi'], + help='Compiler used to compile documents. Available options:\n' + ' auto - uses the compiler based on the image types (default option).\n' + ' pdflatex - uses pdflatex. Works with .png and .jpg images.\n' + ' latex_dvi - uses latex and dvipdf. Works with .ps and .eps images.', default='auto') parser.add_argument('files', type=str, nargs='*', help='files to compile') def run(self, args: argparse.Namespace): args = util.init_package_command(args) + if not hasattr(args, 'latex_compiler'): + args.latex_compiler = 'auto' + + if args.latex_compiler == 'pdflatex': + self.compilation_method = 'pdflatex' + elif args.latex_compiler == 'latex_dvi': + self.compilation_method = 'latex_dvi' + elif args.latex_compiler == 'auto': + self.compilation_method = 'pdflatex' + for extension in ['ps', 'eps']: + if glob.glob(os.path.join(os.getcwd(), 'doc', f'*.{extension}')) != []: + self.compilation_method = 'latex_dvi' + else: + util.exit_with_error("Unrecognized latex compiler") + if args.files == []: self.files = glob.glob(os.path.join(os.getcwd(), 'doc', '*.tex')) else: diff --git a/tests/commands/doc/test_integration.py b/tests/commands/doc/test_integration.py index e3e9d2e5..fcf057ea 100644 --- a/tests/commands/doc/test_integration.py +++ b/tests/commands/doc/test_integration.py @@ -36,6 +36,7 @@ def test_argument(capsys, create_package): command.run(args) out = capsys.readouterr().out assert "Compilation was successful for all files." in out + assert "pdflatex" in out # In auto mode this command will use pdflatex logs_exist = False logs_dir = paths.get_cache_path('doc_logs') @@ -43,3 +44,54 @@ def test_argument(capsys, create_package): assert glob.glob(os.path.join(os.getcwd(), 'doc', pattern)) == [] logs_exist = logs_exist | (glob.glob(os.path.join(logs_dir, pattern)) != []) assert logs_exist + +def run_doc(capsys, command_args, expected, not_expected): + """ + Run doc command + """ + parser = configure_parsers() + args = parser.parse_args(command_args) + command = Command() + command.run(args) + out = capsys.readouterr().out + assert "Compilation was successful for all files." in out + assert expected in out + assert not_expected not in out + +@pytest.mark.parametrize("create_package", [util.get_ps_doc_package_path()], indirect=True) +def test_ps_images(capsys, create_package): + """ + Test `doc` command with ps images. + """ + run_doc( + capsys=capsys, + command_args=["doc"], + expected="latex to dvi", # In auto mode this command should use latex and dvipdf + not_expected="pdflatex" # and shouldn't use pdflatex for any compilation + ) + + +@pytest.mark.parametrize("create_package", [util.get_ps_doc_package_path()], indirect=True) +def test_compilation_mode(capsys, create_package): + """ + Test `doc` with compilation mode directly specified. + """ + run_doc( + capsys=capsys, + command_args=["doc", "doc/doczad.tex", "--latex-compiler", "pdflatex"], + expected="pdflatex", + not_expected="latex to dvi" + ) + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_compilation_mode_2(capsys, create_package): + """ + Test `doc` with compilation mode directly specified. + """ + run_doc( + capsys=capsys, + command_args=["doc", "doc/doczad.tex", "--latex-compiler", "latex_dvi"], + expected="latex to dvi", + not_expected="pdflatex" + ) diff --git a/tests/commands/doc/test_unit.py b/tests/commands/doc/test_unit.py index d4f4545c..aae11708 100644 --- a/tests/commands/doc/test_unit.py +++ b/tests/commands/doc/test_unit.py @@ -7,6 +7,11 @@ @pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) -def test_compile_file(create_package): +def test_compile_pdf_latex(create_package): command = Command() - assert command.compile_file(os.path.abspath(os.path.join(os.getcwd(), "doc/doczad.tex"))) is True + assert command.compile_pdf_latex(os.path.abspath(os.path.join(os.getcwd(), "doc/doczad.tex"))) is True + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_compile_file_latex_div(create_package): + command = Command() + assert command.compile_file_latex_div(os.path.abspath(os.path.join(os.getcwd(), "doc/doczad.tex"))) is True diff --git a/tests/packages/doc/doc/doctest.tex b/tests/packages/doc/doc/doctest.tex new file mode 100644 index 00000000..393fd875 --- /dev/null +++ b/tests/packages/doc/doc/doctest.tex @@ -0,0 +1,5 @@ +\documentclass{article} +\usepackage{graphicx} +\begin{document} +\includegraphics{test_image.png} +\end{document} diff --git a/tests/packages/doc/doc/test_image.png b/tests/packages/doc/doc/test_image.png new file mode 100644 index 00000000..0e90117b Binary files /dev/null and b/tests/packages/doc/doc/test_image.png differ diff --git a/tests/packages/ps_doc/config.yml b/tests/packages/ps_doc/config.yml new file mode 100644 index 00000000..1007a72a --- /dev/null +++ b/tests/packages/ps_doc/config.yml @@ -0,0 +1,4 @@ +title: Package for testing `doc` command (version with ps images) +time_limit: 1000 +memory_limit: 1024 +sinol_task_id: doc2 diff --git a/tests/packages/ps_doc/doc/doctest.tex b/tests/packages/ps_doc/doc/doctest.tex new file mode 100644 index 00000000..dadc0b7e --- /dev/null +++ b/tests/packages/ps_doc/doc/doctest.tex @@ -0,0 +1,5 @@ +\documentclass{article} +\usepackage{graphicx} +\begin{document} +\includegraphics{test_image.ps} +\end{document} diff --git a/tests/packages/ps_doc/doc/doczad.tex b/tests/packages/ps_doc/doc/doczad.tex new file mode 100644 index 00000000..4372318a --- /dev/null +++ b/tests/packages/ps_doc/doc/doczad.tex @@ -0,0 +1,4 @@ +\documentclass{article} +\begin{document} + Hello World! +\end{document} diff --git a/tests/packages/ps_doc/doc/test_image.ps b/tests/packages/ps_doc/doc/test_image.ps new file mode 100644 index 00000000..230c5a36 Binary files /dev/null and b/tests/packages/ps_doc/doc/test_image.ps differ diff --git a/tests/packages/ps_doc/in/.gitkeep b/tests/packages/ps_doc/in/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/packages/ps_doc/out/.gitkeep b/tests/packages/ps_doc/out/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/util.py b/tests/util.py index 5164849a..6778635c 100644 --- a/tests/util.py +++ b/tests/util.py @@ -94,6 +94,13 @@ def get_doc_package_path(): return os.path.join(os.path.dirname(__file__), "packages", "doc") +def get_ps_doc_package_path(): + """ + Get path to package for testing `doc` command (version with ps images) (/test/packages/ps_doc) + """ + return os.path.join(os.path.dirname(__file__), "packages", "ps_doc") + + def get_long_name_package_path(): """ Get path to package with long name (/test/packages/long_package_name)