refactoring stage 4
- modify StateManager to accept Language - fix load command - modify create command
This commit is contained in:
276
run.py
276
run.py
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user