refactor run.py

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-27 08:58:55 +09:00
parent c869d88c28
commit c332fa900c

125
run.py
View File

@@ -26,6 +26,15 @@ BUILD_DIR = "./build"
@unique
class Language(Enum):
"""
실행 가능한 언어(Language)를 정의하는 클래스입니다. 각 언어는 고유 문자열 값을 가짐.
- Python: py
- C: c
- C++: cpp
- Rust: rs
- Kotlin: kt
- Lua: lua
"""
PYTHON = "py"
C = "c"
CPP = "cpp"
@@ -37,6 +46,9 @@ class Language(Enum):
@property
def build_command(self):
"""
각 언어에 대한 빌드 명령어.
"""
return {
Language.PYTHON: [
"python",
@@ -67,10 +79,16 @@ class Language(Enum):
@property
def working_dir(self):
"""
각 언어의 작업 디렉토리
"""
return f"{SRC_SPACE_DIR}/src-{self.value}"
@property
def build_executable(self):
"""
각 언어의 빌드 후 생성되는 실행 파일의 이름.
"""
return {
Language.PYTHON: "py.pyc",
Language.C: "c.out",
@@ -79,19 +97,27 @@ class Language(Enum):
Language.KOTLIN: "kt.jar",
Language.LUA: "lua.luac",
}[self]
def run_executable_command(self, executable_loc):
@property
def executable_command(self):
"""
각 언어에 대한 실행 명령어.
"""
return {
Language.PYTHON: ["python", f"{executable_loc}"],
Language.C: [f"{executable_loc}"],
Language.CPP: [f"{executable_loc}"],
Language.RUST: [f"{executable_loc}"],
Language.KOTLIN: ["java", "-jar", f"{executable_loc}"],
Language.LUA: ["lua", f"{executable_loc}"],
Language.PYTHON: ["python", f"{BUILD_DIR}/{self.build_executable}"],
Language.C: [f"{BUILD_DIR}/{self.build_executable}"],
Language.CPP: [f"{BUILD_DIR}/{self.build_executable}"],
Language.RUST: [f"{BUILD_DIR}/{self.build_executable}"],
Language.KOTLIN: ["java", "-jar", f"{BUILD_DIR}/{self.build_executable}"],
Language.LUA: ["lua", f"{BUILD_DIR}/{self.build_executable}"],
}[self]
@staticmethod
def convert_ext(ext: str):
"""
확장자를 언어 Enum으로 변환하는 메소드.
만약 확장자가 정의된 언어에 해당하지 않으면 UNDEFINED를 반환.
"""
match ext:
case "py":
return Language.PYTHON
@@ -109,8 +135,14 @@ class Language(Enum):
return Language.UNDEFINED
@staticmethod
def convert_name(name: str):
match name.lower():
def convert_name(format_name: str):
"""
다양한 언어 형식 이름을 언어 Enum으로 변환하는 메소드.
convert_ext의 Super Set임.
"""
if Language.convert_ext(format_name) is not Language.UNDEFINED:
return Language.convert_ext(format_name)
match format_name.lower():
case "py" | "py3" | "python" | "python3":
return Language.PYTHON
case "c":
@@ -131,36 +163,41 @@ def natural_sort_key(s: str):
"""Natural sort key: splits string into text/number chunks for proper ordering."""
return [int(c) if c.isdigit() else c.lower() for c in re.split(r"(\d+)", s)]
def parse_range_string(range_str: str) -> list[int]:
"""
범위 문자열을 정수 리스트로 변환.
* ..N: 1부터 N까지
* M..N: M부터 N까지
* N: 단일 숫자
"""
if range_str.startswith(".."):
end_str = range_str[2:]
if not end_str.isdigit():
raise ValueError("Invalid range format")
return list(range(1, int(end_str) + 1))
def parse_range_string(range_str):
try:
if range_str.startswith(".."):
# 형식: "..N" → 1부터 N까지
end = int(range_str[2:])
return list(range(1, end + 1))
else:
# 형식: "M..N" → M부터 N까지
parts = range_str.split("..")
if len(parts) == 1:
# 단일 숫자 (예: "1")
return [int(parts[0])]
elif len(parts) == 2:
# 범위 (예: "3..5")
start = int(parts[0])
end = int(parts[1])
return list(range(start, end + 1))
else:
raise ValueError("Invalid range format")
except ValueError as e:
print(f"Error: {e}")
return []
parts = range_str.split("..")
if len(parts) == 1:
if not parts[0].isdigit():
raise ValueError("Invalid range format")
return [int(parts[0])]
elif len(parts) == 2:
if not parts[0].isdigit() or not parts[1].isdigit():
raise ValueError("Invalid range format")
return list(range(int(parts[0]), int(parts[1]) + 1))
else:
raise ValueError("Invalid range format")
def parse_range_string_list(str_list):
result = []
def parse_range_string_list(str_list) -> list[int]:
"""
여러 범위 문자열을 정수 리스트로 변환하는 함수; 이때 중복은 제거함.
* e.g. ["1..3", "5", "7..9"] -> [1, 2, 3, 5, 7, 8, 9]
"""
result = set()
for s in str_list:
result.extend(parse_range_string(s))
return result
result.update(parse_range_string(s))
return list(result)
@click.group()
@@ -173,6 +210,9 @@ def cli():
@click.option("--target", "-t", default=["1"], multiple=True)
@click.option("--verbose/--no-verbose", "-v/-nv", default=True)
def run(from_: str, target: str, verbose: bool):
"""
지정된 언어로 빌드 및 실행하는 명령어
"""
# Language 확인
from_language: Language = Language.convert_name(from_)
@@ -223,8 +263,6 @@ def run(from_: str, target: str, verbose: bool):
click.echo(">>>>>> [Error while BUILD process] >>>>>>")
raise click.ClickException(e.stderr)
build_executable = from_language.build_executable
for tc in tcs:
if not (
os.path.isfile(f"{TC_DIR}/{tc}.in") and os.path.isfile(f"{TC_DIR}/{tc}.out")
@@ -236,9 +274,7 @@ def run(from_: str, target: str, verbose: bool):
with open(f_in_path, "r", encoding="utf-8") as f_in:
try:
stdout = subprocess.run(
from_language.run_executable_command(
f"{BUILD_DIR}/{build_executable}"
),
from_language.executable_command,
check=True,
capture_output=True,
text=True,
@@ -268,7 +304,9 @@ def run(from_: str, target: str, verbose: bool):
@click.option("--force", "-f", is_flag=True)
def load(file: str, to: str, from_: str, force: bool):
"""
load specific file into corresponding src-space
파일을 각 언어의 src-space로 이동시키는 명령어.
이 명령어는 다음 과정을 수행함.
"""
# 파일의 src-space가 어딘지 확인
@@ -338,7 +376,7 @@ def load(file: str, to: str, from_: str, force: bool):
file_is_completed = True
file_loc = f"{STORAGE_DIR}/{from_}/{to_language.value}/completed/{file_name}.{to_language.value}"
# 존재하면 move
# 존재하지 않으면 빈 파일을 생성하고
# 존재하지 않으면 빈 파일(또는 템플릿 파일)을 생성하고
# state에 기록함
if file_is_exist:
with (
@@ -753,6 +791,7 @@ def find(keyword: str, completed: bool | None):
click.echo(f" {status} {file_name}.{lang_name}")
cli.add_command(run)
cli.add_command(load)
cli.add_command(init)