From 78bf9d7f77e666987254e5a0267834be6d492ca1 Mon Sep 17 00:00:00 2001 From: Vadim Liventsev Date: Fri, 13 Oct 2023 01:15:16 +0200 Subject: [PATCH] chdir fix --- programlib/language.py | 32 +++++++++++++++----------------- programlib/program.py | 31 ++++++++++++++++++++----------- pyproject.toml | 4 +++- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/programlib/language.py b/programlib/language.py index 147f3a8..6f17470 100644 --- a/programlib/language.py +++ b/programlib/language.py @@ -1,6 +1,7 @@ import pexpect import shutil import os +from pathlib import Path class Language: """ @@ -14,45 +15,42 @@ def __init__(self, build_cmd, run_cmd, source, artefacts, name='custom') -> None self.artefacts = artefacts self.name = name - def build(self, workdir, name): + def build(self, name): if self.build_cmd: - os.chdir(workdir) stdout, status = pexpect.run(self.build_cmd.format(name=name), - cwd=workdir, withexitstatus=True, - echo=False) + withexitstatus=True, echo=False) return stdout.decode(), status else: return '', '' - def run(self, workdir, name, input_lines=[]): - child = self.spawn(workdir, name) + def run(self, name, input_lines=[]): + child = self.spawn(name) for line in input_lines: child.sendline(line) output = child.read() child.close() return output.decode(), child.exitstatus - def spawn(self, workdir, name): - os.chdir(workdir) - child = pexpect.spawn(self.run_cmd.format(name=name), cwd=workdir) + def spawn(self, name): + child = pexpect.spawn(self.run_cmd.format(name=name)) child.setecho(False) return child - def write_source(self, workdir, name, source): - with open(workdir / self.source.format(name=name), 'w') as f: + def write_source(self, name, source): + with open(self.source.format(name=name), 'w') as f: f.write(source) - def read_source(self, workdir, name): - with open(workdir / self.source.format(name=name), 'r') as f: + def read_source(self, name): + with open(self.source.format(name=name), 'r') as f: return f.read() - def copy_source(self, workdir, name, dest): - shutil.copy(workdir / self.source.format(name=name), dest) + def copy_source(self, name, dest): + shutil.copy(self.source.format(name=name), dest) - def cleanup(self, workdir, name): + def cleanup(self, name): for path in [self.source] + self.artefacts: try: - (workdir / path.format(name=name)).unlink() + Path(path.format(name=name)).unlink() except FileNotFoundError: pass diff --git a/programlib/program.py b/programlib/program.py index 05b7314..8fb790c 100644 --- a/programlib/program.py +++ b/programlib/program.py @@ -4,6 +4,7 @@ from uuid import uuid4 from pathlib import Path from itertools import zip_longest +from contextlib_chdir import chdir from programlib import Agent, language_, Terminal @@ -51,12 +52,13 @@ def __init__(self, source=None, name=None, language='C++', self.workdir = Path(__file__).parent / 'programs' self.ephemeral = True - if source: - self.language.write_source(self.workdir, self.name, source) - else: - source = self.language.read_source(self.workdir, self.name) + with chdir(self.workdir): + if source: + self.language.write_source(self.name, source) + else: + source = self.language.read_source(self.name) - self.stdout, self.exitstatus = self.language.build(self.workdir, self.name) + self.stdout, self.exitstatus = self.language.build(self.name) # Please send your opinions on 'not not' vs 'bool()' to dont@vadim.me self.compile_error = not not self.exitstatus @@ -66,8 +68,9 @@ def __lt__(self, other): def read(self): """Obtain the source code of the program""" - - return self.language.read_source(self.workdir, self.name) + + with chdir(self.workdir): + return self.language.read_source(self.name) def run(self, input_lines=[], force=True): """ @@ -81,7 +84,8 @@ def run(self, input_lines=[], force=True): Raw results will always be stored in `program.stdout` and `program.exitstatus` attributes """ - self.stdout, self.exitstatus = self.language.run(self.workdir, self.name, input_lines) + with chdir(self.workdir): + self.stdout, self.exitstatus = self.language.run(self.name, input_lines) assert force or not self.exitstatus, f'Exit status {self.exitstatus}' return self.term.emulate(self.stdout) @@ -90,7 +94,8 @@ def spawn(self, delimiter='\n'): Launch the program and get a process object to communicate with it interactively """ - process = self.language.spawn(self.workdir, self.name) + with chdir(self.workdir): + process = self.language.spawn(self.name) return Agent(self, process, delimiter=delimiter) def test(self, test_cases, force=True): @@ -127,8 +132,12 @@ def test(self, test_cases, force=True): return test_runs def save(self, path): - self.language.copy_source(self.workdir, self.name, path) + path = Path(path).absolute() + + with chdir(self.workdir): + self.language.copy_source(self.name, path) def __del__(self): if self.ephemeral: - self.language.cleanup(self.workdir, self.name) \ No newline at end of file + with chdir(self.workdir): + self.language.cleanup(self.name) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 28188e3..a9d24cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "programlib" -version = "7.2.0" +version = "8.0.0" description = "Programs as Objects" authors = ["Vadim Liventsev "] license = "MIT" @@ -17,6 +17,8 @@ pexpect = "^4.8.0" gym = "^0.26.2" numpy = "^1.24.2" pyte = "^0.8.0" +contextlib-chdir = "^1.0.2" + [build-system] requires = ["poetry-core"]