update run.py: show and find command
This commit is contained in:
209
run.py
209
run.py
@@ -125,6 +125,11 @@ class Language(Enum):
|
||||
return Language.UNDEFINED
|
||||
|
||||
|
||||
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):
|
||||
try:
|
||||
if range_str.startswith(".."):
|
||||
@@ -452,13 +457,18 @@ def state():
|
||||
click.secho("state.space has no member", fg="yellow", bold=True)
|
||||
state["space"] = {}
|
||||
|
||||
active = sum(1 for v in state["space"].values() if v)
|
||||
total = len(state["space"])
|
||||
click.secho(f"Space: {active}/{total} active", fg="cyan", bold=True)
|
||||
click.echo()
|
||||
|
||||
for k, v in state["space"].items():
|
||||
click.secho(f" {k}/", fg="yellow")
|
||||
if not v:
|
||||
print(f"{k}: _")
|
||||
click.echo(f" {click.style('—', fg='bright_black')} empty")
|
||||
else:
|
||||
print(
|
||||
f"{k}: {v['file']}.{k} @ {v['location']}{'/completed' if v['is_completed'] else ''}"
|
||||
)
|
||||
status = click.style("✓", fg="green") if v['is_completed'] else click.style("○", fg="white")
|
||||
click.echo(f" {status} {v['file']}.{k} @ {v['location']}")
|
||||
|
||||
|
||||
@click.command(name="init")
|
||||
@@ -500,11 +510,202 @@ def init(count: int):
|
||||
click.echo("2-2. config.yml exists")
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
\b
|
||||
FILTER format (optional):
|
||||
location e.g. zeta
|
||||
location/lang e.g. zeta/rs
|
||||
/lang e.g. /py
|
||||
"""
|
||||
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]
|
||||
|
||||
storage = pathlib.Path(STORAGE_DIR)
|
||||
if not storage.is_dir():
|
||||
raise click.ClickException(f"Storage directory '{STORAGE_DIR}' not found")
|
||||
|
||||
# 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]
|
||||
|
||||
if not entries:
|
||||
click.echo("No problems found.")
|
||||
return
|
||||
|
||||
# Display
|
||||
total = len(entries)
|
||||
completed_count = sum(1 for e in entries if e[3])
|
||||
uncompleted_count = total - completed_count
|
||||
|
||||
click.secho(f"Total: {total} (completed: {completed_count}, uncompleted: {uncompleted_count})", fg="cyan", bold=True)
|
||||
click.echo()
|
||||
|
||||
# Group by location
|
||||
current_loc = None
|
||||
current_lang = None
|
||||
for loc_name, lang_name, file_name, is_completed in entries:
|
||||
if loc_name != current_loc:
|
||||
current_loc = loc_name
|
||||
current_lang = None
|
||||
click.secho(f"[{loc_name}]", fg="green", bold=True)
|
||||
|
||||
if lang_name != current_lang:
|
||||
current_lang = lang_name
|
||||
click.secho(f" {lang_name}/", fg="yellow")
|
||||
|
||||
status = click.style("✓", fg="green") if is_completed else click.style("○", fg="white")
|
||||
click.echo(f" {status} {file_name}.{lang_name}")
|
||||
|
||||
|
||||
@click.command(name="find")
|
||||
@click.argument("keyword", 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):
|
||||
"""
|
||||
Find problems by keyword in storage.
|
||||
|
||||
KEYWORD format:
|
||||
keyword e.g. 2447
|
||||
keyword.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/.ext e.g. zeta/.py
|
||||
"""
|
||||
location = None
|
||||
lang = None
|
||||
if "/" in keyword:
|
||||
parts = keyword.split("/", 1)
|
||||
location = parts[0] or None
|
||||
keyword = parts[1]
|
||||
|
||||
# Extract language from extension
|
||||
if "." in keyword:
|
||||
keyword_base, ext = keyword.rsplit(".", 1)
|
||||
lang_candidate = Language.convert_ext(ext)
|
||||
if lang_candidate is not Language.UNDEFINED:
|
||||
lang = lang_candidate.value
|
||||
else:
|
||||
keyword_base = keyword
|
||||
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]
|
||||
|
||||
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)
|
||||
completed_count = sum(1 for e in entries if e[3])
|
||||
uncompleted_count = total - completed_count
|
||||
|
||||
click.secho(f"Found: {total} (completed: {completed_count}, uncompleted: {uncompleted_count})", fg="cyan", bold=True)
|
||||
click.echo()
|
||||
|
||||
current_loc = None
|
||||
current_lang = None
|
||||
for loc_name, lang_name, file_name, is_completed in entries:
|
||||
if loc_name != current_loc:
|
||||
current_loc = loc_name
|
||||
current_lang = None
|
||||
click.secho(f"[{loc_name}]", fg="green", bold=True)
|
||||
|
||||
if lang_name != current_lang:
|
||||
current_lang = lang_name
|
||||
click.secho(f" {lang_name}/", fg="yellow")
|
||||
|
||||
status = click.style("✓", fg="green") if is_completed else click.style("○", fg="white")
|
||||
click.echo(f" {status} {file_name}.{lang_name}")
|
||||
|
||||
|
||||
cli.add_command(run)
|
||||
cli.add_command(load)
|
||||
cli.add_command(init)
|
||||
cli.add_command(export)
|
||||
cli.add_command(state)
|
||||
cli.add_command(show)
|
||||
cli.add_command(find)
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
||||
Reference in New Issue
Block a user