refactoring stage 3

- run command
This commit is contained in:
2026-05-05 15:22:53 +09:00
parent 63ecdae57b
commit 843f2ef321

140
run.py
View File

@@ -1,16 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import base64
import os import os
import pathlib import pathlib
import re import re
import subprocess import subprocess
import sys from enum import Enum, unique
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from enum import Enum, auto, unique
from html import escape, unescape
import click import click
import yaml import yaml
@@ -68,7 +61,7 @@ def _parse_range_string(range_str: str) -> list[int]:
raise ValueError("Invalid range format") raise ValueError("Invalid range format")
def _parse_range_string_list(str_list) -> list[int]: def _parse_range_string_list(str_list: list[str]) -> list[int]:
""" """
여러 범위 문자열을 정수 리스트로 변환하는 함수; 이때 중복은 제거함. 여러 범위 문자열을 정수 리스트로 변환하는 함수; 이때 중복은 제거함.
* e.g. ["1..3", "5", "7..9"] -> [1, 2, 3, 5, 7, 8, 9] * e.g. ["1..3", "5", "7..9"] -> [1, 2, 3, 5, 7, 8, 9]
@@ -241,7 +234,7 @@ class StateManager:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
return cls._instance return cls._instance
def load(self) -> dict: def _load(self) -> dict:
if self._state is not None: if self._state is not None:
return self._state return self._state
try: try:
@@ -260,18 +253,18 @@ class StateManager:
yaml.safe_dump(self._state, f) yaml.safe_dump(self._state, f)
def get_space(self, lang: str) -> dict | None: def get_space(self, lang: str) -> dict | None:
state = self.load() state = self._load()
return state.get("space", {}).get(lang) return state.get("space", {}).get(lang)
def set_space(self, lang: str, data: dict) -> None: def set_space(self, lang: str, data: dict) -> None:
state = self.load() state = self._load()
if "space" not in state: if "space" not in state:
state["space"] = {} state["space"] = {}
state["space"][lang] = data state["space"][lang] = data
self._state = state self._state = state
def clear_space(self, lang: str) -> None: def clear_space(self, lang: str) -> None:
state = self.load() state = self._load()
if "space" in state and lang in state["space"]: if "space" in state and lang in state["space"]:
del state["space"][lang] del state["space"][lang]
self._state = state self._state = state
@@ -396,17 +389,22 @@ def register(location: str):
""" """
storage_manager = StorageManager() storage_manager = StorageManager()
if storage_manager.check_location(location): if storage_manager.check_location(location):
click.echo(f"Location '{location}' is already registered.") click.secho(
f"Location '{location}' is already registered.", fg="yellow", bold=True
)
else: else:
storage_manager.register_location(location) storage_manager.register_location(location)
click.echo(f"Location '{location}' registered successfully.") click.secho(
f"Location '{location}' registered successfully.", fg="cyan", bold=True
)
@click.command(name="create") @click.command(name="create")
@click.argument("target", type=str, nargs=1, required=True) @click.argument("target", type=str, nargs=1, required=True)
@click.option("--completed/--no-completed", "-c/-nc", default=False, help="Mark as completed") @click.option("--completed/--no-completed", "-c/-nc", default=False, help="Mark as completed")
def create(target: str, completed: bool): def create(target: str, completed: bool):
""" """
지정된 target을 storage에 생성하는 명령어 지정된 location을 storage에 생성하는 명령어
""" """
loc, prob, lang = _dispatch_target(target) loc, prob, lang = _dispatch_target(target)
storage_manager = StorageManager() storage_manager = StorageManager()
@@ -423,62 +421,47 @@ def create(target: str, completed: bool):
@click.command(name="run") @click.command(name="run")
@click.argument("target", type=str, nargs=1, required=True) @click.argument("lang", type=str, nargs=1, required=True)
@click.option("--testcase", "-t", default=["1"], multiple=True) @click.option("--testcase", "-t", default=["1"], multiple=True)
@click.option("--verbose/--no-verbose", "-v/-nv", default=True) @click.option("--verbose/--no-verbose", "-v/-nv", default=True)
def run(target: str, testcase: str, verbose: bool): def run(lang: str, testcase: list[str], verbose: bool):
""" """
지정된 언어로 빌드 및 실행하는 명령어 지정된 언어로 빌드 및 실행하는 명령어
""" """
loc, prob, from_language = _dispatch_target(target)
if from_language is Language.UNDEFINED: # Language 확인
target_language: Language = Language.convert_name(lang)
if target_language is Language.UNDEFINED:
raise click.ClickException("Undefined Language Exception") raise click.ClickException("Undefined Language Exception")
try:
with open("state.yml", "r", encoding="utf-8") as f:
state = yaml.safe_load(f)
except FileNotFoundError as e:
raise click.ClickException(e)
except yaml.YAMLError as e:
raise click.ClickException(e)
if "space" not in state: # load된 state가 있는지 확인
raise click.ClickException("State(state.yml) Exception") space = StateManager().get_space(target_language.value)
elif not state["space"]:
click.secho("state.space has no member", fg="yellow", bold=True)
state["space"] = {}
lang_space_state: dict | None if space is None:
if from_language.value not in state["space"]: raise click.ClickException(
lang_space_state = None f"No loaded state for language '{target_language.value}'"
else: )
lang_space_state = state["space"][from_language.value]
if lang_space_state is None or not lang_space_state:
raise click.ClickException("Run Exception: There are no pre-defined state")
# 테케 분석 # 테케 분석
try: tcs: list[int] = _parse_range_string_list(testcase)
tcs: list[int] = _parse_range_string_list(target)
except ValueError as e:
raise click.ClickException(e)
# build # build
try: try:
s = subprocess.run( s = subprocess.run(
from_language.build_command, target_language.build_command,
check=True, check=True,
capture_output=True, capture_output=True,
text=True, text=True,
cwd=from_language.working_dir, cwd=target_language.working_dir,
) )
print(s.stderr) print(s.stderr)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
click.echo(">>>>>> [Error while BUILD process] >>>>>>") click.secho(">>>>>> [BUILD TIME ERROR] >>>>>>", fg="red", bold=True)
raise click.ClickException(e.stderr) raise click.ClickException(e.stderr)
# execution for each test case
for tc in tcs: for tc in tcs:
if not ( if not (
os.path.isfile(f"{TC_DIR}/{tc}.in") and os.path.isfile(f"{TC_DIR}/{tc}.out") os.path.isfile(f"{TC_DIR}/{tc}.in") and os.path.isfile(f"{TC_DIR}/{tc}.out")
@@ -487,30 +470,43 @@ def run(target: str, testcase: str, verbose: bool):
else: else:
f_in_path = f"{TC_DIR}/{tc}.in" f_in_path = f"{TC_DIR}/{tc}.in"
f_out_path = f"{TC_DIR}/{tc}.out" f_out_path = f"{TC_DIR}/{tc}.out"
with open(f_in_path, "r", encoding="utf-8") as f_in:
try: with open(f_in_path, "r", encoding="utf-8") as f_in, \
stdout = subprocess.run( open(f_out_path, "r", encoding="utf-8") as f_out:
from_language.executable_command, expected = f_out.read().strip()
check=True,
capture_output=True, proc = subprocess.Popen(
text=True, target_language.executable_command,
stdin=f_in, stdin=f_in,
).stdout stdout=subprocess.PIPE,
except subprocess.CalledProcessError as e: stderr=subprocess.PIPE,
click.echo(">>>>>> [Error while RUNNING process] >>>>>>") text=True,
click.echo(f"{e.stderr}") )
click.echo(f"returncode: {e.returncode}")
stdout = "" if not verbose:
continue for line in proc.stdout: # type: ignore
flag: bool = False print(line, end="")
with open(f_out_path, "r", encoding="utf-8") as f_out:
flag = stdout.strip() == f_out.read().strip() stdout, stderr = proc.communicate()
click.echo(f"{tc:02d}: {flag}")
if proc.returncode != 0:
click.secho(">>>>>> [RUN TIME ERROR] >>>>>>", fg="red", bold=True)
click.secho(f"{stderr}", fg="red")
click.secho(f"returncode: {proc.returncode}", fg="red")
continue
flag = stdout.strip() == expected
symbol = click.style("", fg="green") if flag else click.style("", fg="red")
click.echo(f"{tc:02d}: {symbol}")
if verbose: if verbose:
click.echo("===" * 3) click.secho("===" * 3, fg="bright_black")
click.echo(stdout) click.secho(f"[INPUT]", fg="cyan", bold=True)
click.echo("===" * 3) with open(f_in_path, "r") as f:
click.echo(f.read())
click.secho(f"[ACTUAL]", fg="cyan", bold=True)
click.echo(stdout.strip() if stdout else "(empty)")
click.secho("===" * 3, fg="bright_black")
@click.command(name="load") @click.command(name="load")
@@ -687,9 +683,7 @@ def export(from_: str, completed: bool, copy: bool):
if completed: if completed:
s_is_completed = True s_is_completed = True
dest_path: str = ( dest_path: str = f"{STORAGE_DIR}/{s_loc}/{from_language.value}{'/completed' if s_is_completed else ''}/{s_file}.{from_language.value}"
f"{STORAGE_DIR}/{s_loc}/{from_language.value}{'/completed' if s_is_completed else ''}/{s_file}.{from_language.value}"
)
source_path: str = ( source_path: str = (
f"{SRC_SPACE_DIR}/src-{from_language.value}/src/main.{from_language.value}" f"{SRC_SPACE_DIR}/src-{from_language.value}/src/main.{from_language.value}"
) )