|
| 1 | +import sys |
| 2 | +from typing import Iterable, List, Optional, Union |
| 3 | + |
| 4 | +import click |
| 5 | +from halo import Halo |
| 6 | +from rich.console import Console |
| 7 | +from tabulate import tabulate |
| 8 | + |
| 9 | +from good_first_issues import utils |
| 10 | +from good_first_issues.graphql import services |
| 11 | +from good_first_issues.utils import ParsedDuration, parse_period |
| 12 | + |
| 13 | +console = Console(color_system="auto") |
| 14 | + |
| 15 | + |
| 16 | +period_help_msg = """ |
| 17 | +Specify a time range for filtering data. |
| 18 | +Converts the specified time range to UTC date time. |
| 19 | +
|
| 20 | +# Query all organization repos |
| 21 | +$ gfi search "rust-lang" --period "30 hours" |
| 22 | +
|
| 23 | +# Query a specific repo in an organization |
| 24 | +$ gfi search "rust-lang" --repo "rust" -p "30 mins" |
| 25 | +
|
| 26 | +# Query repos with the topic hacktoberfest |
| 27 | +$ gfi search -hf -p "100 days" |
| 28 | +
|
| 29 | +# Query all user repos |
| 30 | +$ gfi search "yankeexe" --user -p "600 hrs" |
| 31 | +
|
| 32 | +# Query a specific repo of a user |
| 33 | +$ gfi search "yankeexe" --user --repo "good-first-issues" -p "600 days" |
| 34 | +
|
| 35 | +--period 1 m,min,mins,minutes |
| 36 | +--period 2 h,hr,hour,hours,hrs |
| 37 | +--period 3 d,day,days |
| 38 | +""" |
| 39 | + |
| 40 | + |
| 41 | +@click.command() |
| 42 | +@click.option( |
| 43 | + "--repo", |
| 44 | + "-r", |
| 45 | + help="Search in a specific repo of user or organization", |
| 46 | + type=str, |
| 47 | +) |
| 48 | +@click.option( |
| 49 | + "--hacktoberfest", |
| 50 | + "-hf", |
| 51 | + help="Search repositories with topic hacktoberfest", |
| 52 | + is_flag=True, |
| 53 | +) |
| 54 | +@click.option( |
| 55 | + "--limit", |
| 56 | + "-l", |
| 57 | + help="Limit the number of issues to display. Defaults to 10", |
| 58 | + type=int, |
| 59 | + default=10, |
| 60 | +) |
| 61 | +@click.option( |
| 62 | + "--user", |
| 63 | + "-u", |
| 64 | + help="Specify if it's a user repository", |
| 65 | + is_flag=True, |
| 66 | +) |
| 67 | +@click.option( |
| 68 | + "--web", |
| 69 | + help="Display issues on browser", |
| 70 | + is_flag=True, |
| 71 | +) |
| 72 | +@click.option( |
| 73 | + "--all", |
| 74 | + "-a", |
| 75 | + help="View all the issues found without limits.", |
| 76 | + is_flag=True, |
| 77 | +) |
| 78 | +@click.option("--period", "-p", help=period_help_msg) |
| 79 | +@click.option( |
| 80 | + "--language", |
| 81 | + "-lang", |
| 82 | + help="Filter issues by programming language (e.g., Python, JavaScript)", |
| 83 | + type=str, |
| 84 | + default=None, |
| 85 | +) |
| 86 | +@click.option( |
| 87 | + "--keyword", |
| 88 | + "-k", |
| 89 | + help="Filter issues by keyword in title or description", |
| 90 | + type=str, |
| 91 | + default=None, |
| 92 | +) |
| 93 | +@click.argument("name", required=False) |
| 94 | +def search( |
| 95 | + name: str, |
| 96 | + repo: str, |
| 97 | + user: bool, |
| 98 | + web: bool, |
| 99 | + limit: int, |
| 100 | + all: bool, |
| 101 | + hacktoberfest: bool, |
| 102 | + period: str, |
| 103 | + language: str, |
| 104 | + keyword: str, |
| 105 | +): |
| 106 | + """Search for good first issues in organizations or user repositories. |
| 107 | +
|
| 108 | + Usage: |
| 109 | +
|
| 110 | + gfi search <repo-owner/org-name> |
| 111 | +
|
| 112 | + ➡️ repo owner |
| 113 | + gfi search "yankeexe" --user |
| 114 | +
|
| 115 | + ➡️ org name |
| 116 | + gfi search "ollama" |
| 117 | +
|
| 118 | + ➡️ search in a particular repo |
| 119 | + gfi search "yankeexe" --repo "good-first-issues" |
| 120 | + gfi search "ollama" --repo "ollama-python" |
| 121 | +
|
| 122 | + ➡️ filter by language |
| 123 | + gfi search "facebook" --language "Python" |
| 124 | +
|
| 125 | + ➡️ filter by keyword |
| 126 | + gfi search "rust-lang" --keyword "documentation" |
| 127 | +
|
| 128 | + ➡️ combine filters |
| 129 | + gfi search "microsoft" --language "JavaScript" --keyword "API" |
| 130 | + """ |
| 131 | + |
| 132 | + if name is None and hacktoberfest is False: |
| 133 | + utils.print_help_msg(search) |
| 134 | + sys.exit() |
| 135 | + |
| 136 | + issues: Optional[Iterable] = None |
| 137 | + rate_limit: int = 0 |
| 138 | + |
| 139 | + # Check for GitHub Token |
| 140 | + token: Union[str, bool] = utils.check_credential() |
| 141 | + |
| 142 | + if period: |
| 143 | + period: ParsedDuration = parse_period(period) |
| 144 | + period = period.utc_date_time.strftime("%Y-%m-%dT%H:%M:%SZ") |
| 145 | + |
| 146 | + # Identify the flags passed. |
| 147 | + query, variables, mode = services.identify_mode( |
| 148 | + name, repo, user, hacktoberfest, period, limit |
| 149 | + ) |
| 150 | + |
| 151 | + # Spinner |
| 152 | + spinner = Halo(text="Fetching repos...", spinner="dots") |
| 153 | + spinner.start() |
| 154 | + |
| 155 | + # API Call |
| 156 | + response = services.caller(token, query, variables) |
| 157 | + spinner.succeed("Repos fetched.") |
| 158 | + |
| 159 | + # Data Filtering |
| 160 | + if mode in ["org", "user"]: |
| 161 | + issues, rate_limit = services.org_user_pipeline(response, mode) |
| 162 | + |
| 163 | + if mode == "repo": |
| 164 | + issues, rate_limit = services.org_user_pipeline(response, mode) |
| 165 | + |
| 166 | + if mode == "search": |
| 167 | + issues, rate_limit = services.extract_search_results(response) |
| 168 | + issues = issues[:limit] # cannot set limit on the search_query directly |
| 169 | + |
| 170 | + # Apply keyword filter |
| 171 | + if keyword: |
| 172 | + filtered_issues = [] |
| 173 | + for issue in issues: |
| 174 | + title = issue[0].lower() |
| 175 | + if keyword.lower() in title: |
| 176 | + filtered_issues.append(issue) |
| 177 | + issues = filtered_issues |
| 178 | + |
| 179 | + # Apply language filter |
| 180 | + if language: |
| 181 | + filtered_issues = [] |
| 182 | + for issue in issues: |
| 183 | + title = issue[0].lower() |
| 184 | + if language.lower() in title: |
| 185 | + filtered_issues.append(issue) |
| 186 | + issues = filtered_issues |
| 187 | + |
| 188 | + table_headers: List = ["Title", "Issue URL"] |
| 189 | + |
| 190 | + # No good first issues found |
| 191 | + if not issues: |
| 192 | + console.print( |
| 193 | + f"Remaining requests:dash:: {rate_limit}", |
| 194 | + style="bold green", |
| 195 | + ) |
| 196 | + return console.print( |
| 197 | + "No good first issues found!:mask:", |
| 198 | + style="bold red", |
| 199 | + ) |
| 200 | + |
| 201 | + # Display issues in browser |
| 202 | + if web: |
| 203 | + html_data = tabulate(issues, table_headers, tablefmt="html") |
| 204 | + return utils.web_server(html_data) |
| 205 | + |
| 206 | + # Display issues in terminal |
| 207 | + row_ids = list(range(1, len(issues) + 1)) |
| 208 | + print( |
| 209 | + tabulate( |
| 210 | + issues, |
| 211 | + table_headers, |
| 212 | + tablefmt="fancy_grid", |
| 213 | + showindex=row_ids, |
| 214 | + ) |
| 215 | + ) |
| 216 | + |
| 217 | + console.print(f"Remaining requests:dash:: {rate_limit}", style="bold green") |
| 218 | + |
| 219 | + # Show applied filters |
| 220 | + if keyword or language: |
| 221 | + filter_info = [] |
| 222 | + if language: |
| 223 | + filter_info.append(f"language: {language}") |
| 224 | + if keyword: |
| 225 | + filter_info.append(f"keyword: {keyword}") |
| 226 | + console.print( |
| 227 | + f"Filters applied: {', '.join(filter_info)}", |
| 228 | + style="bold cyan", |
| 229 | + ) |
| 230 | + |
| 231 | + console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") |
0 commit comments