refactoring stage 7
- implement StorageManager list_entries - fix show - fix find - fix some minor
This commit is contained in:
255
run.py
255
run.py
@@ -439,6 +439,65 @@ class StorageManager:
|
|||||||
/ f"{filename}.{lang.value}"
|
/ 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
|
# 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(list(testcase))
|
||||||
tcs: list[int] = _parse_range_string_list(testcase)
|
|
||||||
|
|
||||||
# build
|
# build
|
||||||
try:
|
try:
|
||||||
@@ -657,7 +715,7 @@ def load(target: str, force: bool):
|
|||||||
|
|
||||||
|
|
||||||
@click.command(name="export")
|
@click.command(name="export")
|
||||||
@click.argument("from", "from_", type=str, nargs=-1)
|
@click.argument("from_", type=str, nargs=-1)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--all", "all_", default=False, is_flag=True, help="Export all files in the space"
|
"--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.command(name="show")
|
||||||
@click.argument("filter", type=str, default=None, required=False)
|
@click.argument("filter_", type=str, default=None, required=False)
|
||||||
@click.option(
|
@click.option("--completed", "completed_mode", flag_value="completed", default=True)
|
||||||
"--completed/--no-completed",
|
@click.option("--uncompleted", "completed_mode", flag_value="uncompleted")
|
||||||
"-c/-nc",
|
@click.option("--both-completed", "completed_mode", flag_value="both")
|
||||||
default=False,
|
def show(filter_: str | None, completed_mode: str):
|
||||||
help="Filter by completed status (default: uncompleted only)",
|
"""Show all stored problems in storage directory.
|
||||||
)
|
|
||||||
@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.
|
|
||||||
|
|
||||||
FILTER format (optional):
|
FILTER format (optional):
|
||||||
location e.g. zeta
|
location e.g. zeta
|
||||||
location/lang e.g. zeta/rs
|
location/lang e.g. zeta/rs
|
||||||
/lang e.g. /py
|
/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
|
completed = {"completed": True, "uncompleted": False, "both": None}[completed_mode]
|
||||||
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]
|
|
||||||
|
|
||||||
storage = pathlib.Path(STORAGE_DIR)
|
location = lang = None
|
||||||
if not storage.is_dir():
|
if filter_:
|
||||||
raise click.ClickException(f"Storage directory '{STORAGE_DIR}' not found")
|
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 = StorageManager().list_entries(
|
||||||
entries = [] # (location, language, filename, is_completed)
|
location=location,
|
||||||
|
lang=Language.convert_name(lang) if lang else None,
|
||||||
for loc_dir in sorted(storage.iterdir()):
|
completed=completed,
|
||||||
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]
|
|
||||||
|
|
||||||
if not entries:
|
if not entries:
|
||||||
click.echo("No problems found.")
|
click.echo(" - ")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Display
|
|
||||||
total = len(entries)
|
total = len(entries)
|
||||||
completed_count = sum(1 for e in entries if e[3])
|
completed_count = sum(1 for e in entries if e[3])
|
||||||
uncompleted_count = total - completed_count
|
uncompleted_count = total - completed_count
|
||||||
@@ -906,7 +914,6 @@ def show(filter: str | None, completed: bool, show_all: bool):
|
|||||||
)
|
)
|
||||||
click.echo()
|
click.echo()
|
||||||
|
|
||||||
# Group by location
|
|
||||||
current_loc = None
|
current_loc = None
|
||||||
current_lang = None
|
current_lang = None
|
||||||
for loc_name, lang_name, file_name, is_completed in entries:
|
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.command(name="find")
|
||||||
@click.argument("keyword", type=str)
|
@click.argument("filter_", type=str)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--completed/--no-completed",
|
"--completed/--no-completed",
|
||||||
"-c/-nc",
|
"-c/-nc",
|
||||||
default=None,
|
default=None,
|
||||||
help="Filter by completed status (default: all)",
|
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:
|
FILTER format:
|
||||||
keyword e.g. 2447
|
filter e.g. 2447
|
||||||
keyword.ext e.g. 2447.py (filters by language)
|
filter.ext e.g. 2447.py (filters by language)
|
||||||
.ext e.g. .py (all files of a language)
|
.ext e.g. .py (all files of a language)
|
||||||
location/keyword e.g. zeta/2447
|
location/filter e.g. zeta/2447
|
||||||
location/keyword.ext e.g. zeta/2447.py
|
location/filter.ext e.g. zeta/2447.py
|
||||||
location/.ext e.g. zeta/.py
|
location/.ext e.g. zeta/.py
|
||||||
"""
|
"""
|
||||||
location = None
|
location: str | None = None
|
||||||
lang = None
|
filter_base: str | None = None
|
||||||
if "/" in keyword:
|
lang: Language | None = None
|
||||||
parts = keyword.split("/", 1)
|
|
||||||
|
if "/" in filter_:
|
||||||
|
parts = filter_.split("/", 1)
|
||||||
location = parts[0] or None
|
location = parts[0] or None
|
||||||
keyword = parts[1]
|
filter_ = parts[1]
|
||||||
|
else:
|
||||||
|
location = None
|
||||||
|
|
||||||
# Extract language from extension
|
if "." in filter_:
|
||||||
if "." in keyword:
|
filter_base, ext = filter_.rsplit(".", 1)
|
||||||
keyword_base, ext = keyword.rsplit(".", 1)
|
|
||||||
lang_candidate = Language.convert_ext(ext)
|
lang_candidate = Language.convert_ext(ext)
|
||||||
if lang_candidate is not Language.UNDEFINED:
|
if lang_candidate is not Language.UNDEFINED:
|
||||||
lang = lang_candidate.value
|
lang = lang_candidate
|
||||||
|
filter_base = None if filter_base == "" else filter_base
|
||||||
else:
|
else:
|
||||||
keyword_base = keyword
|
filter_base = filter_
|
||||||
else:
|
else:
|
||||||
keyword_base = keyword
|
filter_base = filter_
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
|
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])))
|
entries.sort(key=lambda e: (e[0], e[1], _natural_sort_key(e[2])))
|
||||||
|
|
||||||
if not entries:
|
if not entries:
|
||||||
click.echo("No problems found.")
|
click.echo("No problems found.")
|
||||||
return
|
return
|
||||||
|
|
||||||
total = len(entries)
|
total_count = len(entries)
|
||||||
completed_count = sum(1 for e in entries if e[3])
|
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(
|
click.secho(
|
||||||
f"Found: {total} (completed: {completed_count}, uncompleted: {uncompleted_count})",
|
f"Found: {total_count} (completed: {completed_count}, uncompleted: {uncompleted_count})",
|
||||||
fg="cyan",
|
fg="cyan",
|
||||||
bold=True,
|
bold=True,
|
||||||
)
|
)
|
||||||
@@ -1039,7 +1020,7 @@ def find(keyword: str, completed: bool | None):
|
|||||||
|
|
||||||
@click.command(name="fetchprob")
|
@click.command(name="fetchprob")
|
||||||
@click.argument("target", type=str, nargs=1, required=True)
|
@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):
|
def fetchprob(target: str, force: bool):
|
||||||
"""(disabled) Fetch BOJ problem HTML - BOJ is closing, keeping for future multi-location support."""
|
"""(disabled) Fetch BOJ problem HTML - BOJ is closing, keeping for future multi-location support."""
|
||||||
del target, force
|
del target, force
|
||||||
|
|||||||
Reference in New Issue
Block a user