refactoring stage 7
- implement StorageManager list_entries - fix show - fix find - fix some minor
This commit is contained in:
253
run.py
253
run.py
@@ -439,6 +439,65 @@ class StorageManager:
|
||||
/ f"{filename}.{lang.value}"
|
||||
)
|
||||
|
||||
def list_entries(
|
||||
self,
|
||||
location: str | None = None,
|
||||
lang: Language | None = None,
|
||||
completed: bool | None = None,
|
||||
) -> list[tuple[str, str, str, bool]]:
|
||||
"""저장된 파일 목록을 반환합니다.
|
||||
|
||||
Args:
|
||||
location: location 필터 (None이면 전체)
|
||||
lang: language 필터 (None이면 전체)
|
||||
completed: completed 필터 (None이면 both, True이면 completed만, False이면 uncompleted만)
|
||||
|
||||
Returns:
|
||||
list[tuple[str, str, str, bool]]: (location, lang, filename, is_completed)
|
||||
"""
|
||||
result: list[tuple[str, str, str, bool]] = []
|
||||
|
||||
if not STORAGE_DIR.is_dir():
|
||||
return result
|
||||
|
||||
for loc_dir in sorted(STORAGE_DIR.iterdir()):
|
||||
if not loc_dir.is_dir():
|
||||
continue
|
||||
loc_name = loc_dir.name
|
||||
|
||||
if location and loc_name != location:
|
||||
continue
|
||||
|
||||
for lang_dir in sorted(loc_dir.iterdir()):
|
||||
if not lang_dir.is_dir():
|
||||
continue
|
||||
lang_name = lang_dir.name
|
||||
|
||||
if Language.convert_ext(lang_name) is Language.UNDEFINED:
|
||||
continue
|
||||
|
||||
if lang and lang_name != lang.value:
|
||||
continue
|
||||
|
||||
for f in sorted(
|
||||
lang_dir.iterdir(), key=lambda p: _natural_sort_key(p.stem)
|
||||
):
|
||||
if f.is_file():
|
||||
result.append((loc_name, lang_name, f.stem, False))
|
||||
|
||||
completed_dir = lang_dir / "completed"
|
||||
if completed_dir.is_dir():
|
||||
for f in sorted(
|
||||
completed_dir.iterdir(), key=lambda p: _natural_sort_key(p.stem)
|
||||
):
|
||||
if f.is_file():
|
||||
result.append((loc_name, lang_name, f.stem, True))
|
||||
|
||||
if completed is not None:
|
||||
result = [e for e in result if e[3] == completed]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# =======================
|
||||
# MAIN CLI LOGIC
|
||||
@@ -547,8 +606,7 @@ def run(lang: str, testcase: tuple[str, ...], verbose: bool):
|
||||
)
|
||||
|
||||
# 테케 분석
|
||||
testcase: list[str] = list(testcase)
|
||||
tcs: list[int] = _parse_range_string_list(testcase)
|
||||
tcs: list[int] = _parse_range_string_list(list(testcase))
|
||||
|
||||
# build
|
||||
try:
|
||||
@@ -657,7 +715,7 @@ def load(target: str, force: bool):
|
||||
|
||||
|
||||
@click.command(name="export")
|
||||
@click.argument("from", "from_", type=str, nargs=-1)
|
||||
@click.argument("from_", type=str, nargs=-1)
|
||||
@click.option(
|
||||
"--all", "all_", default=False, is_flag=True, help="Export all files in the space"
|
||||
)
|
||||
@@ -810,91 +868,41 @@ def init(count: int):
|
||||
|
||||
|
||||
@click.command(name="show")
|
||||
@click.argument("filter", type=str, default=None, required=False)
|
||||
@click.option(
|
||||
"--completed/--no-completed",
|
||||
"-c/-nc",
|
||||
default=False,
|
||||
help="Filter by completed status (default: uncompleted only)",
|
||||
)
|
||||
@click.option(
|
||||
"--all",
|
||||
"show_all",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Show all problems regardless of completed status",
|
||||
)
|
||||
def show(filter: str | None, completed: bool, show_all: bool):
|
||||
"""
|
||||
Show all stored problems in storage directory.
|
||||
@click.argument("filter_", type=str, default=None, required=False)
|
||||
@click.option("--completed", "completed_mode", flag_value="completed", default=True)
|
||||
@click.option("--uncompleted", "completed_mode", flag_value="uncompleted")
|
||||
@click.option("--both-completed", "completed_mode", flag_value="both")
|
||||
def show(filter_: str | None, completed_mode: str):
|
||||
"""Show all stored problems in storage directory.
|
||||
|
||||
FILTER format (optional):
|
||||
location e.g. zeta
|
||||
location/lang e.g. zeta/rs
|
||||
/lang e.g. /py
|
||||
|
||||
FILTER MODE (default: --completed):
|
||||
--completed show completed only
|
||||
--uncompleted show uncompleted only
|
||||
--both-completed show both completed and uncompleted
|
||||
"""
|
||||
location = None
|
||||
lang = None
|
||||
if filter:
|
||||
parts = filter.split("/", 1)
|
||||
if len(parts) == 2:
|
||||
location = parts[0] or None
|
||||
lang = parts[1] or None
|
||||
else:
|
||||
location = parts[0]
|
||||
completed = {"completed": True, "uncompleted": False, "both": None}[completed_mode]
|
||||
|
||||
storage = pathlib.Path(STORAGE_DIR)
|
||||
if not storage.is_dir():
|
||||
raise click.ClickException(f"Storage directory '{STORAGE_DIR}' not found")
|
||||
location = lang = None
|
||||
if filter_:
|
||||
parts = filter_.split("/", 1)
|
||||
location = parts[0] if parts[0].strip() else None
|
||||
lang = parts[1] if len(parts) == 2 else None
|
||||
|
||||
# Collect all files
|
||||
entries = [] # (location, language, filename, is_completed)
|
||||
|
||||
for loc_dir in sorted(storage.iterdir()):
|
||||
if not loc_dir.is_dir():
|
||||
continue
|
||||
loc_name = loc_dir.name
|
||||
|
||||
if location and loc_name != location:
|
||||
continue
|
||||
|
||||
for lang_dir in sorted(loc_dir.iterdir()):
|
||||
if not lang_dir.is_dir():
|
||||
continue
|
||||
lang_name = lang_dir.name
|
||||
|
||||
if (
|
||||
lang
|
||||
and lang_name != Language.convert_name(lang).value
|
||||
and lang_name != lang
|
||||
):
|
||||
continue
|
||||
|
||||
# uncompleted files
|
||||
for f in sorted(
|
||||
lang_dir.iterdir(), key=lambda p: _natural_sort_key(p.stem)
|
||||
):
|
||||
if f.is_file():
|
||||
entries.append((loc_name, lang_name, f.stem, False))
|
||||
|
||||
# completed files
|
||||
completed_dir = lang_dir / "completed"
|
||||
if completed_dir.is_dir():
|
||||
for f in sorted(
|
||||
completed_dir.iterdir(), key=lambda p: _natural_sort_key(p.stem)
|
||||
):
|
||||
if f.is_file():
|
||||
entries.append((loc_name, lang_name, f.stem, True))
|
||||
|
||||
# Filter by completed status
|
||||
if not show_all:
|
||||
entries = [e for e in entries if e[3] == completed]
|
||||
entries = StorageManager().list_entries(
|
||||
location=location,
|
||||
lang=Language.convert_name(lang) if lang else None,
|
||||
completed=completed,
|
||||
)
|
||||
|
||||
if not entries:
|
||||
click.echo("No problems found.")
|
||||
click.echo(" - ")
|
||||
return
|
||||
|
||||
# Display
|
||||
total = len(entries)
|
||||
completed_count = sum(1 for e in entries if e[3])
|
||||
uncompleted_count = total - completed_count
|
||||
@@ -906,7 +914,6 @@ def show(filter: str | None, completed: bool, show_all: bool):
|
||||
)
|
||||
click.echo()
|
||||
|
||||
# Group by location
|
||||
current_loc = None
|
||||
current_lang = None
|
||||
for loc_name, lang_name, file_name, is_completed in entries:
|
||||
@@ -928,90 +935,64 @@ def show(filter: str | None, completed: bool, show_all: bool):
|
||||
|
||||
|
||||
@click.command(name="find")
|
||||
@click.argument("keyword", type=str)
|
||||
@click.argument("filter_", type=str)
|
||||
@click.option(
|
||||
"--completed/--no-completed",
|
||||
"-c/-nc",
|
||||
default=None,
|
||||
help="Filter by completed status (default: all)",
|
||||
)
|
||||
def find(keyword: str, completed: bool | None):
|
||||
def find(filter_: str, completed: bool | None):
|
||||
"""
|
||||
Find problems by keyword in storage.
|
||||
Find problems by filter in storage.
|
||||
|
||||
KEYWORD format:
|
||||
keyword e.g. 2447
|
||||
keyword.ext e.g. 2447.py (filters by language)
|
||||
FILTER format:
|
||||
filter e.g. 2447
|
||||
filter.ext e.g. 2447.py (filters by language)
|
||||
.ext e.g. .py (all files of a language)
|
||||
location/keyword e.g. zeta/2447
|
||||
location/keyword.ext e.g. zeta/2447.py
|
||||
location/filter e.g. zeta/2447
|
||||
location/filter.ext e.g. zeta/2447.py
|
||||
location/.ext e.g. zeta/.py
|
||||
"""
|
||||
location = None
|
||||
lang = None
|
||||
if "/" in keyword:
|
||||
parts = keyword.split("/", 1)
|
||||
location = parts[0] or None
|
||||
keyword = parts[1]
|
||||
location: str | None = None
|
||||
filter_base: str | None = None
|
||||
lang: Language | None = None
|
||||
|
||||
# Extract language from extension
|
||||
if "." in keyword:
|
||||
keyword_base, ext = keyword.rsplit(".", 1)
|
||||
if "/" in filter_:
|
||||
parts = filter_.split("/", 1)
|
||||
location = parts[0] or None
|
||||
filter_ = parts[1]
|
||||
else:
|
||||
location = None
|
||||
|
||||
if "." in filter_:
|
||||
filter_base, ext = filter_.rsplit(".", 1)
|
||||
lang_candidate = Language.convert_ext(ext)
|
||||
if lang_candidate is not Language.UNDEFINED:
|
||||
lang = lang_candidate.value
|
||||
lang = lang_candidate
|
||||
filter_base = None if filter_base == "" else filter_base
|
||||
else:
|
||||
keyword_base = keyword
|
||||
filter_base = filter_
|
||||
else:
|
||||
keyword_base = keyword
|
||||
|
||||
storage = pathlib.Path(STORAGE_DIR)
|
||||
if not storage.is_dir():
|
||||
raise click.ClickException(f"Storage directory '{STORAGE_DIR}' not found")
|
||||
|
||||
entries = [] # (location, language, filename, is_completed)
|
||||
|
||||
for loc_dir in sorted(storage.iterdir()):
|
||||
if not loc_dir.is_dir():
|
||||
continue
|
||||
loc_name = loc_dir.name
|
||||
|
||||
if location and loc_name != location:
|
||||
continue
|
||||
|
||||
for lang_dir in sorted(loc_dir.iterdir()):
|
||||
if not lang_dir.is_dir():
|
||||
continue
|
||||
lang_name = lang_dir.name
|
||||
|
||||
if lang and lang_name != lang:
|
||||
continue
|
||||
|
||||
for f in lang_dir.iterdir():
|
||||
if f.is_file() and keyword_base.lower() in f.stem.lower():
|
||||
entries.append((loc_name, lang_name, f.stem, False))
|
||||
|
||||
completed_dir = lang_dir / "completed"
|
||||
if completed_dir.is_dir():
|
||||
for f in completed_dir.iterdir():
|
||||
if f.is_file() and keyword_base.lower() in f.stem.lower():
|
||||
entries.append((loc_name, lang_name, f.stem, True))
|
||||
|
||||
if completed is not None:
|
||||
entries = [e for e in entries if e[3] == completed]
|
||||
filter_base = filter_
|
||||
|
||||
entries = StorageManager().list_entries(
|
||||
location=location, lang=lang, completed=completed
|
||||
)
|
||||
if filter_base is not None:
|
||||
entries = [e for e in entries if filter_base.lower() in e[2].lower()]
|
||||
entries.sort(key=lambda e: (e[0], e[1], _natural_sort_key(e[2])))
|
||||
|
||||
if not entries:
|
||||
click.echo("No problems found.")
|
||||
return
|
||||
|
||||
total = len(entries)
|
||||
total_count = len(entries)
|
||||
completed_count = sum(1 for e in entries if e[3])
|
||||
uncompleted_count = total - completed_count
|
||||
uncompleted_count = sum(1 for e in entries if not e[3])
|
||||
|
||||
click.secho(
|
||||
f"Found: {total} (completed: {completed_count}, uncompleted: {uncompleted_count})",
|
||||
f"Found: {total_count} (completed: {completed_count}, uncompleted: {uncompleted_count})",
|
||||
fg="cyan",
|
||||
bold=True,
|
||||
)
|
||||
@@ -1039,7 +1020,7 @@ def find(keyword: str, completed: bool | None):
|
||||
|
||||
@click.command(name="fetchprob")
|
||||
@click.argument("target", type=str, nargs=1, required=True)
|
||||
@click.option("--force", "-f", is_flag=True, help="Overwrite existing HTML files")
|
||||
@click.option("--force", "-f", is_flag=True, help="Overwrite existing static files")
|
||||
def fetchprob(target: str, force: bool):
|
||||
"""(disabled) Fetch BOJ problem HTML - BOJ is closing, keeping for future multi-location support."""
|
||||
del target, force
|
||||
|
||||
Reference in New Issue
Block a user