diff --git a/run.py b/run.py index c515d38..a1d8eb7 100755 --- a/run.py +++ b/run.py @@ -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)