refactoring stage 4

- modify StateManager to accept Language
- fix load command
- modify create command
This commit is contained in:
2026-05-05 17:42:51 +09:00
parent 843f2ef321
commit 97f1636c57

276
run.py
View File

@@ -72,23 +72,6 @@ def _parse_range_string_list(str_list: list[str]) -> list[int]:
return list(result)
def _dispatch_target(target: str) -> tuple[str, str, Language]:
splited = target.split("/")
if len(splited) != 2:
raise ValueError("Invalid target format. Expected format: <loc>/<problem>.<lang>")
prob_parts = splited[1].rsplit(".", 1)
if len(prob_parts) != 2:
raise ValueError("Invalid target format. Expected format: <loc>/<problem>.<lang>")
prob_name, ext = prob_parts
lang = Language.convert_ext(ext)
if lang is Language.UNDEFINED:
raise ValueError(f"Unknown language: {ext}")
return splited[0], prob_name, lang
@unique
class Language(Enum):
"""
@@ -225,6 +208,27 @@ class Language(Enum):
return Language.UNDEFINED
def _dispatch_target(target: str) -> tuple[str, str, Language]:
splited = target.split("/")
if len(splited) != 2:
raise ValueError(
"Invalid target format. Expected format: <loc>/<problem>.<lang>"
)
prob_parts = splited[1].rsplit(".", 1)
if len(prob_parts) != 2:
raise ValueError(
"Invalid target format. Expected format: <loc>/<problem>.<lang>"
)
prob_name, ext = prob_parts
lang = Language.convert_ext(ext)
if lang is Language.UNDEFINED:
raise ValueError(f"Unknown language: {ext}")
return splited[0], prob_name, lang
class StateManager:
_instance: "StateManager | None" = None
_state: dict | None = None
@@ -252,21 +256,39 @@ class StateManager:
with open(STATE_PATH, "w", encoding="utf-8") as f:
yaml.safe_dump(self._state, f)
def get_space(self, lang: str) -> dict | None:
def check_space(self, lang: Language) -> bool:
state = self._load()
return state.get("space", {}).get(lang)
return lang.value in state.get("space", {}) and bool(state["space"][lang.value])
def set_space(self, lang: str, data: dict) -> None:
def get_space(self, lang: Language) -> dict:
state = self._load()
if not self.check_space(lang):
raise ValueError(f"No space found for language: {lang}")
return state["space"][lang.value]
def add_space(
self, lang: Language, file: str, location: str, is_completed: bool
) -> None:
state = self._load()
if "space" not in state:
state["space"] = {}
state["space"][lang] = data
state["space"][lang.value] = {
"file": file,
"location": location,
"is_completed": is_completed,
}
self._state = state
def clear_space(self, lang: str) -> None:
def remove_space(self, lang: Language) -> None:
state = self._load()
if "space" in state and lang in state["space"]:
del state["space"][lang]
if "space" in state and lang.value in state["space"]:
state["space"][lang.value] = {}
self._state = state
def clear_space(self, lang: Language) -> None:
state = self._load()
if "space" in state and lang.value in state["space"]:
del state["space"][lang.value]
self._state = state
def reload(self) -> None:
@@ -319,9 +341,9 @@ class StorageManager:
return None
def save_file(
self, location: str, lang: str, filename: str, content: bytes, completed: bool
self, location: str, lang: Language, filename: str, content: bytes, completed: bool
) -> None:
path = STORAGE_DIR / location / lang / ("completed" if completed else "")
path = STORAGE_DIR / location / lang.value / ("completed" if completed else "")
os.makedirs(path, exist_ok=True)
with open(f"{path}/{filename}", "wb") as f:
f.write(content)
@@ -401,23 +423,59 @@ def register(location: str):
@click.command(name="create")
@click.argument("target", type=str, nargs=1, required=True)
@click.option("--completed/--no-completed", "-c/-nc", default=False, help="Mark as completed")
def create(target: str, completed: bool):
@click.option(
"--completed/--no-completed", "-c/-nc", default=False, help="Mark as completed"
)
@click.option(
"--no-template",
"-nt",
is_flag=True,
help="Do not use template content (create empty file instead)",
)
@click.option(
"--force", "-f", is_flag=True, help="Overwrite existing file if it already exists"
)
def create(target: str, completed: bool, template: bool, force: bool):
"""
지정된 location을 storage에 생성하는 명령어
"""
loc, prob, lang = _dispatch_target(target)
storage_manager = StorageManager()
if not storage_manager.check_location(loc):
raise click.ClickException(f"Location '{loc}' is not registered. Please register it first.")
if not StorageManager().check_location(loc):
raise click.ClickException(
f"Location '{loc}' is not registered. Please register it first."
)
filename = prob
if storage_manager.check_location_lang(loc, lang):
existing_file = storage_manager.check_get_file(loc, lang, filename)
if existing_file:
click.echo(f"File '{filename}.{lang.value}' already exists in location '{loc}'.")
existing = StorageManager().check_get_file(loc, lang, filename)
if existing:
click.secho(
f"File '{filename}.{lang.value}' already exists in location '{loc}'",
fg="yellow",
bold=True,
)
if not force:
return
storage_manager.save_file(loc, lang.value, f"{filename}.{lang.value}", b"", completed) # create empty file
click.echo(f"Problem '{filename}.{lang.value}' created successfully in location '{loc}' with completed status: {completed}.")
if not template:
content = b""
else:
template_path = TEMPLATES_DIR / f"{lang.value}.txt"
if template_path.is_file():
content = template_path.read_bytes()
click.secho(f"Using template: {template_path}", fg="cyan")
else:
content = b""
click.secho(f"Template not found: {template_path}", fg="yellow")
StorageManager().save_file(
loc, lang, f"{filename}.{lang.value}", content, completed
)
click.secho(
f"Created '{filename}.{lang.value}' in location '{loc}'",
fg="cyan",
bold=True,
)
@click.command(name="run")
@@ -436,11 +494,9 @@ def run(lang: str, testcase: list[str], verbose: bool):
raise click.ClickException("Undefined Language Exception")
# load된 state가 있는지 확인
space = StateManager().get_space(target_language.value)
if space is None:
if not StateManager().check_space(target_language):
raise click.ClickException(
f"No loaded state for language '{target_language.value}'"
f"No loaded file for language '{target_language.value}'. Please load a file first."
)
# 테케 분석
@@ -471,8 +527,10 @@ def run(lang: str, testcase: list[str], verbose: bool):
f_in_path = f"{TC_DIR}/{tc}.in"
f_out_path = f"{TC_DIR}/{tc}.out"
with open(f_in_path, "r", encoding="utf-8") as f_in, \
open(f_out_path, "r", encoding="utf-8") as f_out:
with (
open(f_in_path, "r", encoding="utf-8") as f_in,
open(f_out_path, "r", encoding="utf-8") as f_out,
):
expected = f_out.read().strip()
proc = subprocess.Popen(
@@ -496,7 +554,9 @@ def run(lang: str, testcase: list[str], verbose: bool):
continue
flag = stdout.strip() == expected
symbol = click.style("", fg="green") if flag else click.style("", fg="red")
symbol = (
click.style("", fg="green") if flag else click.style("", fg="red")
)
click.echo(f"{tc:02d}: {symbol}")
if verbose:
@@ -510,127 +570,41 @@ def run(lang: str, testcase: list[str], verbose: bool):
@click.command(name="load")
@click.argument("file", type=str, nargs=1, required=True)
@click.option("--to", type=str, default="") # language value
@click.option("--from", "from_", type=str, required=True)
@click.argument("target", type=str, nargs=1, required=True)
@click.option("--force", "-f", is_flag=True)
def load(file: str, to: str, from_: str, force: bool):
def load(target: str, force: bool):
"""
Storage의 파일을 각 언어의 src-space로 이동시키는 명령.
- force는 src-space에 이미 파일이 존재할 때, 덮어쓸지 결정
"""
# 파일의 src-space가 어딘지 확인
to_language: Language
file_name: str
file_sep_ext = file.split(".")
if len(file_sep_ext) == 1:
file_name = file_sep_ext[0]
to_language = Language.convert_name(to) # if blank, undefined
elif len(file_sep_ext) == 2:
file_name = file_sep_ext[0]
to_language = Language.convert_name(file_sep_ext[1])
else:
file_name = ""
to_language = Language.UNDEFINED
if to_language is Language.UNDEFINED:
raise click.UsageError("LoadError: `to` or `file` argument is wrong")
try:
with open(STATE_PATH, "r", encoding="utf-8") as f_state:
state = yaml.safe_load(f_state)
except FileNotFoundError as e:
raise click.ClickException(e)
except yaml.YAMLError as e:
raise click.ClickException(e)
if "space" not in state:
raise click.ClickException("state.yml is wrong")
elif not state["space"]:
click.secho("state.space has no member", fg="yellow", bold=True)
state["space"] = {}
lang_space_state: dict | None
if to_language.value not in state["space"]:
lang_space_state = None
else:
lang_space_state = state["space"][to_language.value]
loc, prob, lang = _dispatch_target(target)
if StateManager().check_space(lang):
if not force:
if lang_space_state is None or not lang_space_state:
pass
elif lang_space_state["file"] is None:
pass
else:
raise click.ClickException(
f"File {lang_space_state['file']}.{to_language.value} already in source space {to_language.value}"
f"Space for language '{lang.value}' is already occupied."
)
# 파일이 존재하는지 확인
# uncompleted
file_is_exist: bool = False
file_is_completed: bool = False
file_loc: str = None
if os.path.isfile(
f"{STORAGE_DIR}/{from_}/{to_language.value}/{file_name}.{to_language.value}"
):
file_is_exist = True
file_is_completed = False
file_loc = (
f"{STORAGE_DIR}/{from_}/{to_language.value}/{file_name}.{to_language.value}"
path = StorageManager().check_get_file(loc, lang, prob)
if path is None:
raise click.ClickException(
f"File '{prob}.{lang.value}' not found in location '{loc}'. Use 'create' command to create it first."
)
elif os.path.isfile(
f"{STORAGE_DIR}/{from_}/{to_language.value}/completed/{file_name}.{to_language.value}"
):
file_is_exist = True
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 (
open(file_loc, "rb") as f_src,
open(
f"{SRC_SPACE_DIR}/src-{to_language.value}/src/main.{to_language.value}",
"wb",
) as f_dst,
):
dest_path = SRC_SPACE_DIR / f"src-{lang.value}" / "src" / f"main.{lang.value}"
with open(path, "rb") as f_src, open(dest_path, "wb") as f_dst:
f_dst.write(f_src.read())
os.remove(file_loc)
else:
if TEMPLATES_DIR and os.path.isfile(
f"{TEMPLATES_DIR}/template.{to_language.value}"
):
with (
open(f"{TEMPLATES_DIR}/template.{to_language.value}", "rb") as f_src,
open(
f"{SRC_SPACE_DIR}/src-{to_language.value}/src/main.{to_language.value}",
"wb",
) as f_dst,
):
f_dst.write(f_src.read())
else:
with open(
f"{SRC_SPACE_DIR}/src-{to_language.value}/src/main.{to_language.value}",
"wb",
) as f_dst:
pass
with open("state.yml", "w", encoding="utf-8") as f:
if lang_space_state is None:
state["space"][to_language.value] = {}
state["space"][to_language.value]["file"] = file_name
state["space"][to_language.value]["location"] = from_
state["space"][to_language.value]["is_completed"] = file_is_completed
yaml.safe_dump(state, f)
if file_is_exist:
click.echo(
f"File {file_name}.{to_language.value} from {from_}{'/completed' if file_is_completed else ''} is successfully loaded"
StateManager().remove_space(lang)
StateManager().add_space(lang, prob, loc, False)
click.secho(
f"Loaded '{prob}.{lang.value}' from location '{loc}' to src-space.",
fg="cyan",
bold=True,
)
else:
click.echo(f"File {file_name}.{to_language.value} is successfully created")
@click.command(name="export")