|
| 1 | +import json |
1 | 2 | from pathlib import Path |
2 | 3 |
|
3 | 4 | import typer |
4 | 5 |
|
5 | 6 | from leetcode_py.tools.generator import generate_problem |
6 | 7 |
|
7 | | -from ..utils.problem_finder import find_problem_by_number, find_problems_by_tag, get_problem_json_path |
| 8 | +from ..utils.problem_finder import ( |
| 9 | + find_problem_by_number, |
| 10 | + find_problems_by_tag, |
| 11 | + get_all_problems, |
| 12 | + get_problem_json_path, |
| 13 | +) |
8 | 14 | from ..utils.resources import get_template_path |
9 | 15 |
|
10 | | -ERROR_EXACTLY_ONE_OPTION = ( |
11 | | - "Error: Exactly one of --problem-num, --problem-slug, or --problem-tag is required" |
12 | | -) |
| 16 | + |
| 17 | +def _get_problem_difficulty(problem_name: str) -> str | None: |
| 18 | + json_path = get_problem_json_path(problem_name) |
| 19 | + if not json_path.exists(): |
| 20 | + return None |
| 21 | + |
| 22 | + try: |
| 23 | + with open(json_path) as f: |
| 24 | + data = json.load(f) |
| 25 | + return data.get("difficulty") |
| 26 | + except Exception: |
| 27 | + return None |
13 | 28 |
|
14 | 29 |
|
15 | 30 | def resolve_problems( |
16 | | - problem_num: int | None, problem_slug: str | None, problem_tag: str | None |
| 31 | + problem_nums: list[int], |
| 32 | + problem_slugs: list[str], |
| 33 | + problem_tag: str | None, |
| 34 | + difficulty: str | None, |
| 35 | + all_problems: bool, |
17 | 36 | ) -> list[str]: |
18 | | - if problem_num is not None: |
19 | | - problem_name = find_problem_by_number(problem_num) |
20 | | - if not problem_name: |
21 | | - typer.echo(f"Error: Problem number {problem_num} not found", err=True) |
22 | | - raise typer.Exit(1) |
23 | | - return [problem_name] |
24 | | - elif problem_slug is not None: |
25 | | - return [problem_slug] |
26 | | - elif problem_tag is not None: |
| 37 | + options_count = sum( |
| 38 | + [ |
| 39 | + len(problem_nums) > 0, |
| 40 | + len(problem_slugs) > 0, |
| 41 | + problem_tag is not None, |
| 42 | + all_problems, |
| 43 | + ] |
| 44 | + ) |
| 45 | + |
| 46 | + if options_count != 1: |
| 47 | + typer.echo( |
| 48 | + "Error: Exactly one of --problem-num, --problem-slug, --problem-tag, or --all is required", |
| 49 | + err=True, |
| 50 | + ) |
| 51 | + raise typer.Exit(1) |
| 52 | + |
| 53 | + problems = [] |
| 54 | + |
| 55 | + if problem_nums: |
| 56 | + for num in problem_nums: |
| 57 | + problem_name = find_problem_by_number(num) |
| 58 | + if not problem_name: |
| 59 | + typer.echo(f"Error: Problem number {num} not found", err=True) |
| 60 | + raise typer.Exit(1) |
| 61 | + problems.append(problem_name) |
| 62 | + elif problem_slugs: |
| 63 | + problems = problem_slugs |
| 64 | + elif problem_tag: |
27 | 65 | problems = find_problems_by_tag(problem_tag) |
28 | 66 | if not problems: |
29 | 67 | typer.echo(f"Error: No problems found with tag '{problem_tag}'", err=True) |
30 | 68 | raise typer.Exit(1) |
31 | 69 | typer.echo(f"Found {len(problems)} problems with tag '{problem_tag}'") |
32 | | - return problems |
| 70 | + elif all_problems: |
| 71 | + problems = get_all_problems() |
| 72 | + typer.echo(f"Found {len(problems)} problems") |
| 73 | + |
| 74 | + # Apply difficulty filter if specified |
| 75 | + if difficulty: |
| 76 | + filtered_problems = [] |
| 77 | + for problem_name in problems: |
| 78 | + problem_difficulty = _get_problem_difficulty(problem_name) |
| 79 | + if problem_difficulty and problem_difficulty.lower() == difficulty.lower(): |
| 80 | + filtered_problems.append(problem_name) |
| 81 | + problems = filtered_problems |
| 82 | + typer.echo(f"Filtered to {len(problems)} problems with difficulty '{difficulty}'") |
33 | 83 |
|
34 | | - typer.echo(ERROR_EXACTLY_ONE_OPTION, err=True) |
35 | | - raise typer.Exit(1) |
| 84 | + return problems |
36 | 85 |
|
37 | 86 |
|
38 | 87 | def generate( |
39 | | - problem_num: int | None = typer.Option(None, "-n", "--problem-num", help="Problem number"), |
40 | | - problem_slug: str | None = typer.Option(None, "-s", "--problem-slug", help="Problem slug"), |
| 88 | + problem_nums: list[int] = typer.Option( |
| 89 | + [], "-n", "--problem-num", help="Problem number(s) (use multiple -n flags)" |
| 90 | + ), |
| 91 | + problem_slugs: list[str] = typer.Option( |
| 92 | + [], "-s", "--problem-slug", help="Problem slug(s) (use multiple -s flags)" |
| 93 | + ), |
41 | 94 | problem_tag: str | None = typer.Option(None, "-t", "--problem-tag", help="Problem tag (bulk)"), |
42 | | - output: str = typer.Option("leetcode", "-o", "--output", help="Output directory"), |
| 95 | + difficulty: str | None = typer.Option( |
| 96 | + None, "-d", "--difficulty", help="Filter by difficulty (Easy/Medium/Hard)" |
| 97 | + ), |
| 98 | + all_problems: bool = typer.Option(False, "--all", help="Generate all problems"), |
| 99 | + output: str = typer.Option(".", "-o", "--output", help="Output directory"), |
43 | 100 | force: bool = typer.Option(False, "--force", help="Force overwrite existing files"), |
44 | 101 | ): |
45 | | - options_provided = sum(x is not None for x in [problem_num, problem_slug, problem_tag]) |
46 | | - if options_provided != 1: |
47 | | - typer.echo(ERROR_EXACTLY_ONE_OPTION, err=True) |
48 | | - raise typer.Exit(1) |
49 | | - |
50 | 102 | template_dir = get_template_path() |
51 | 103 | output_dir = Path(output) |
52 | 104 |
|
53 | 105 | # Determine which problems to generate |
54 | | - problems = resolve_problems(problem_num, problem_slug, problem_tag) |
| 106 | + problems = resolve_problems(problem_nums, problem_slugs, problem_tag, difficulty, all_problems) |
55 | 107 |
|
56 | 108 | # Generate each problem |
| 109 | + success_count = 0 |
| 110 | + failed_count = 0 |
| 111 | + |
57 | 112 | for problem_name in problems: |
58 | 113 | json_path = get_problem_json_path(problem_name) |
59 | 114 | if not json_path.exists(): |
60 | 115 | typer.echo(f"Warning: JSON file not found for problem '{problem_name}', skipping", err=True) |
| 116 | + failed_count += 1 |
61 | 117 | continue |
62 | 118 |
|
63 | 119 | try: |
64 | 120 | generate_problem(json_path, template_dir, output_dir, force) |
| 121 | + success_count += 1 |
| 122 | + except typer.Exit: |
| 123 | + # typer.Exit was already handled with proper error message |
| 124 | + failed_count += 1 |
65 | 125 | except Exception as e: |
66 | 126 | typer.echo(f"Error generating problem '{problem_name}': {e}", err=True) |
67 | | - if len(problems) == 1: |
68 | | - raise typer.Exit(1) |
| 127 | + failed_count += 1 |
| 128 | + |
| 129 | + typer.echo(f"Completed: {success_count} successful, {failed_count} failed") |
| 130 | + |
| 131 | + if failed_count > 0: |
| 132 | + raise typer.Exit(1) |
0 commit comments