From e0de8c369146d51444051618e8a25205acf03bba Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Mon, 10 Nov 2025 00:18:47 +0200 Subject: [PATCH 01/10] Proposal: Add --language and --keyword flags to filter issues by language and keywords --- feature_language_keyword.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 feature_language_keyword.md diff --git a/feature_language_keyword.md b/feature_language_keyword.md new file mode 100644 index 0000000..2c42d83 --- /dev/null +++ b/feature_language_keyword.md @@ -0,0 +1,10 @@ +# Proposal: Add --language and --keyword flags + +This change introduces two new CLI flags: +- `--language`: filters issues by programming language (e.g., Python, JavaScript) +- `--keyword`: searches for specific keywords in issue titles/descriptions + +Example usage: +```bash +good-first-issues --language Python --keyword documentation + From 9403e7ee629f24cb8b96f561e5789b43cc108d42 Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Thu, 13 Nov 2025 22:08:42 +0200 Subject: [PATCH 02/10] Implement --language and --keyword filtering for issues - Add --language flag to filter issues by programming language - Add --keyword flag to filter issues by keywords in title - Both filters use case-insensitive matching - Display helpful messages when no results match filters - Show active filters in output for user clarity Resolves #42 --- good_first_issues/commands/search.py | 93 +++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/good_first_issues/commands/search.py b/good_first_issues/commands/search.py index 78f23c6..52fef82 100644 --- a/good_first_issues/commands/search.py +++ b/good_first_issues/commands/search.py @@ -78,6 +78,20 @@ is_flag=True, ) @click.option("--period", "-p", help=period_help_msg) +@click.option( + "--language", + "-lang", + help="Filter issues by programming language (e.g., Python, JavaScript)", + type=str, + default=None, +) +@click.option( + "--keyword", + "-k", + help="Filter issues by keyword in title or description", + type=str, + default=None, +) @click.argument("name", required=False) def search( name: str, @@ -88,6 +102,8 @@ def search( all: bool, hacktoberfest: bool, period: str, + language: str, + keyword: str, ): """Search for good first issues in organizations or user repositories. @@ -95,20 +111,32 @@ def search( gfi search - ➡️ repo owner + repo owner gfi search "yankeexe" --user - ➡️ org name + org name gfi search "ollama" - ➡️ search in a particular repo + search in a particular repo gfi search "yankeexe" --repo "good-first-issues" gfi search "ollama" --repo "ollama-python" + filter by language + + gfi search "facebook" --language "Python" + + filter by keyword + + gfi search "rust-lang" --keyword "documentation" + + combine filters + + gfi search "microsoft" --language "JavaScript" --keyword "API" + """ if name is None and hacktoberfest is False: @@ -118,14 +146,14 @@ def search( issues: Optional[Iterable] = None rate_limit: int = 0 - # Check for GitHub Token. + # Check for GitHub Token token: Union[str, bool] = utils.check_credential() if period: period: ParsedDuration = parse_period(period) period = period.utc_date_time.strftime("%Y-%m-%dT%H:%M:%SZ") - # Identify the flags passed. + # Identify the flags passed query, variables, mode = services.identify_mode( name, repo, user, hacktoberfest, period, limit ) @@ -150,21 +178,57 @@ def search( issues, rate_limit = services.extract_search_results(response) issues = issues[:limit] # cannot set limit on the search_query directly + # Apply keyword filter + if keyword: + filtered_issues = [] + for issue in issues: + # issue is a tuple: (title, url) + title = issue[0].lower() + # Check if keyword is in the title + if keyword.lower() in title: + filtered_issues.append(issue) + issues = filtered_issues + + # Apply language filter + if language: + filtered_issues = [] + for issue in issues: + # issue is a tuple: (title, url) + title = issue[0].lower() + # Simple language detection by keyword matching in title + if language.lower() in title: + filtered_issues.append(issue) + issues = filtered_issues + table_headers: List = ["Title", "Issue URL"] - # No good first issues found. + # No good first issues found if not issues: console.print( f"Remaining requests:dash:: {rate_limit}", style="bold green", ) + # Provide helpful message if filters were used + if keyword or language: + filter_msg = [] + if language: + filter_msg.append(f"language '{language}'") + if keyword: + filter_msg.append(f"keyword '{keyword}'") + filter_text = " and ".join(filter_msg) + + return console.print( + f"No issues found matching {filter_text}. Try different filters!", + style="bold red", + ) + return console.print( "No good first issues found!:mask:", style="bold red", ) - # Handle displaying issues on browser. + # Handle displaying issues on browser if web: html_data = tabulate(issues, table_headers, tablefmt="html") return utils.web_server(html_data) @@ -180,4 +244,17 @@ def search( ) console.print(f"Remaining requests:dash:: {rate_limit}", style="bold green") - console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") + + # Show active filters + if keyword or language: + filter_info = [] + if language: + filter_info.append(f"language: {language}") + if keyword: + filter_info.append(f"keyword: {keyword}") + console.print( + f"Filters applied: {', '.join(filter_info)}", + style="bold cyan", + ) + + console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") \ No newline at end of file From 710eb1a2fa6f561ea72db5c9b4677765f35a9b16 Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Thu, 13 Nov 2025 23:28:23 +0200 Subject: [PATCH 03/10] Fix Bug --- good_first_issues/commands/search.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/good_first_issues/commands/search.py b/good_first_issues/commands/search.py index 52fef82..a0623b4 100644 --- a/good_first_issues/commands/search.py +++ b/good_first_issues/commands/search.py @@ -12,7 +12,6 @@ console = Console(color_system="auto") - period_help_msg = """ Specify a time range for filtering data. Converts the specified time range to UTC date time. @@ -39,7 +38,6 @@ --period 3 d,day,days """ - @click.command() @click.option( "--repo", @@ -217,7 +215,7 @@ def search( if keyword: filter_msg.append(f"keyword '{keyword}'") filter_text = " and ".join(filter_msg) - + return console.print( f"No issues found matching {filter_text}. Try different filters!", style="bold red", @@ -244,7 +242,7 @@ def search( ) console.print(f"Remaining requests:dash:: {rate_limit}", style="bold green") - + # Show active filters if keyword or language: filter_info = [] @@ -256,5 +254,5 @@ def search( f"Filters applied: {', '.join(filter_info)}", style="bold cyan", ) - - console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") \ No newline at end of file + + console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") From bc58b1ec5987a27da301d56ac66f1abfeb0e85ec Mon Sep 17 00:00:00 2001 From: kholoudibrahim <119106866+kholoud512@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:30:59 +0200 Subject: [PATCH 04/10] Update search.py From 4095bd72a70bf3a1d8cbd16c751cdfed0e90da9c Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Thu, 13 Nov 2025 23:54:19 +0200 Subject: [PATCH 05/10] Fix indentation and code quality issues - Fix indentation of filter block (was causing syntax issues) - Update README and feature documentation - Ensure proper code formatting --- README.md | 11 +++++++++++ feature_language_keyword.md | 1 - good_first_issues/commands/search.py | 25 +++++++++---------------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index fd4fd7d..34a0bc4 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,17 @@ $ gfi search -hf --limit 10 --period "48 hours" +### Filter by Language + +$ gfi search "facebook" --language "Python" + +### Filter by Keyword + +$ gfi search "rust-lang" --keyword "documentation" + +### Combine Filters + +$ gfi search "microsoft" --language "JavaScript" --keyword "API" ### 📏 Search for issues within a certain period diff --git a/feature_language_keyword.md b/feature_language_keyword.md index 2c42d83..6b76fef 100644 --- a/feature_language_keyword.md +++ b/feature_language_keyword.md @@ -7,4 +7,3 @@ This change introduces two new CLI flags: Example usage: ```bash good-first-issues --language Python --keyword documentation - diff --git a/good_first_issues/commands/search.py b/good_first_issues/commands/search.py index a0623b4..d1ba888 100644 --- a/good_first_issues/commands/search.py +++ b/good_first_issues/commands/search.py @@ -176,25 +176,18 @@ def search( issues, rate_limit = services.extract_search_results(response) issues = issues[:limit] # cannot set limit on the search_query directly - # Apply keyword filter - if keyword: + # Apply filters + if keyword or language: # ← ADD 4 SPACES HERE! filtered_issues = [] for issue in issues: - # issue is a tuple: (title, url) title = issue[0].lower() - # Check if keyword is in the title - if keyword.lower() in title: - filtered_issues.append(issue) - issues = filtered_issues - - # Apply language filter - if language: - filtered_issues = [] - for issue in issues: - # issue is a tuple: (title, url) - title = issue[0].lower() - # Simple language detection by keyword matching in title - if language.lower() in title: + # Check both filters + matches = True + if keyword and keyword.lower() not in title: + matches = False + if language and language.lower() not in title: + matches = False + if matches: filtered_issues.append(issue) issues = filtered_issues From b68e5bdfe87f1d8a2ff7eaad81447abc5e05848a Mon Sep 17 00:00:00 2001 From: kholoudibrahim <119106866+kholoud512@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:57:53 +0200 Subject: [PATCH 06/10] Delete good_first_issues/commands/search.py --- good_first_issues/commands/search.py | 251 --------------------------- 1 file changed, 251 deletions(-) delete mode 100644 good_first_issues/commands/search.py diff --git a/good_first_issues/commands/search.py b/good_first_issues/commands/search.py deleted file mode 100644 index d1ba888..0000000 --- a/good_first_issues/commands/search.py +++ /dev/null @@ -1,251 +0,0 @@ -import sys -from typing import Iterable, List, Optional, Union - -import click -from halo import Halo -from rich.console import Console -from tabulate import tabulate - -from good_first_issues import utils -from good_first_issues.graphql import services -from good_first_issues.utils import ParsedDuration, parse_period - -console = Console(color_system="auto") - -period_help_msg = """ -Specify a time range for filtering data. -Converts the specified time range to UTC date time. - -# Query all organization repos -$ gfi search "rust-lang" --period "30 hours" - -# Query a specific repo in an organization -$ gfi search "rust-lang" --repo "rust" -p "30 mins" - -# Query repos with the topic hacktoberfest -$ gfi search -hf -p "100 days" - -# Query all user repos -$ gfi search "yankeexe" --user -p "600 hrs" - -# Query a specific repo of a user -$ gfi search "yankeexe" --user --repo "good-first-issues" -p "600 days" - ---period 1 m,min,mins,minutes - ---period 2 h,hr,hour,hours,hrs - ---period 3 d,day,days -""" - -@click.command() -@click.option( - "--repo", - "-r", - help="Search in a specific repo of user or organization", - type=str, -) -@click.option( - "--hacktoberfest", - "-hf", - help="Search repositories with topic hacktoberfest", - is_flag=True, -) -@click.option( - "--limit", - "-l", - help="Limit the number of issues to display. Defaults to 10", - type=int, - default=10, -) -@click.option( - "--user", - "-u", - help="Specify if it's a user repository", - is_flag=True, -) -@click.option( - "--web", - help="Display issues on browser", - is_flag=True, -) -@click.option( - "--all", - "-a", - help="View all the issues found without limits.", - is_flag=True, -) -@click.option("--period", "-p", help=period_help_msg) -@click.option( - "--language", - "-lang", - help="Filter issues by programming language (e.g., Python, JavaScript)", - type=str, - default=None, -) -@click.option( - "--keyword", - "-k", - help="Filter issues by keyword in title or description", - type=str, - default=None, -) -@click.argument("name", required=False) -def search( - name: str, - repo: str, - user: bool, - web: bool, - limit: int, - all: bool, - hacktoberfest: bool, - period: str, - language: str, - keyword: str, -): - """Search for good first issues in organizations or user repositories. - - Usage: - - gfi search - - repo owner - - gfi search "yankeexe" --user - - org name - - gfi search "ollama" - - search in a particular repo - - gfi search "yankeexe" --repo "good-first-issues" - - gfi search "ollama" --repo "ollama-python" - - filter by language - - gfi search "facebook" --language "Python" - - filter by keyword - - gfi search "rust-lang" --keyword "documentation" - - combine filters - - gfi search "microsoft" --language "JavaScript" --keyword "API" - - """ - - if name is None and hacktoberfest is False: - utils.print_help_msg(search) - sys.exit() - - issues: Optional[Iterable] = None - rate_limit: int = 0 - - # Check for GitHub Token - token: Union[str, bool] = utils.check_credential() - - if period: - period: ParsedDuration = parse_period(period) - period = period.utc_date_time.strftime("%Y-%m-%dT%H:%M:%SZ") - - # Identify the flags passed - query, variables, mode = services.identify_mode( - name, repo, user, hacktoberfest, period, limit - ) - - # Spinner - spinner = Halo(text="Fetching repos...", spinner="dots") - spinner.start() - - # API Call - response = services.caller(token, query, variables) - - spinner.succeed("Repos fetched.") - - # Data Filtering - if mode == "org" or mode == "user": - issues, rate_limit = services.org_user_pipeline(response, mode) - - if mode == "repo": - issues, rate_limit = services.org_user_pipeline(response, mode) - - if mode == "search": - issues, rate_limit = services.extract_search_results(response) - issues = issues[:limit] # cannot set limit on the search_query directly - - # Apply filters - if keyword or language: # ← ADD 4 SPACES HERE! - filtered_issues = [] - for issue in issues: - title = issue[0].lower() - # Check both filters - matches = True - if keyword and keyword.lower() not in title: - matches = False - if language and language.lower() not in title: - matches = False - if matches: - filtered_issues.append(issue) - issues = filtered_issues - - table_headers: List = ["Title", "Issue URL"] - - # No good first issues found - if not issues: - console.print( - f"Remaining requests:dash:: {rate_limit}", - style="bold green", - ) - - # Provide helpful message if filters were used - if keyword or language: - filter_msg = [] - if language: - filter_msg.append(f"language '{language}'") - if keyword: - filter_msg.append(f"keyword '{keyword}'") - filter_text = " and ".join(filter_msg) - - return console.print( - f"No issues found matching {filter_text}. Try different filters!", - style="bold red", - ) - - return console.print( - "No good first issues found!:mask:", - style="bold red", - ) - - # Handle displaying issues on browser - if web: - html_data = tabulate(issues, table_headers, tablefmt="html") - return utils.web_server(html_data) - - row_ids = list(range(1, len(issues) + 1)) - print( - tabulate( - issues, - table_headers, - tablefmt="fancy_grid", - showindex=row_ids, - ) - ) - - console.print(f"Remaining requests:dash:: {rate_limit}", style="bold green") - - # Show active filters - if keyword or language: - filter_info = [] - if language: - filter_info.append(f"language: {language}") - if keyword: - filter_info.append(f"keyword: {keyword}") - console.print( - f"Filters applied: {', '.join(filter_info)}", - style="bold cyan", - ) - - console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") From 7d6716fea3aed4a85869d8cb70bc6842e8588410 Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Fri, 14 Nov 2025 00:23:10 +0200 Subject: [PATCH 07/10] Add language and keyword filters to search command --- good_first_issues/commands/search.py | 231 +++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 good_first_issues/commands/search.py diff --git a/good_first_issues/commands/search.py b/good_first_issues/commands/search.py new file mode 100644 index 0000000..71fb8b8 --- /dev/null +++ b/good_first_issues/commands/search.py @@ -0,0 +1,231 @@ +import sys +from typing import Iterable, List, Optional, Union + +import click +from halo import Halo +from rich.console import Console +from tabulate import tabulate + +from good_first_issues import utils +from good_first_issues.graphql import services +from good_first_issues.utils import ParsedDuration, parse_period + +console = Console(color_system="auto") + + +period_help_msg = """ +Specify a time range for filtering data. +Converts the specified time range to UTC date time. + +# Query all organization repos +$ gfi search "rust-lang" --period "30 hours" + +# Query a specific repo in an organization +$ gfi search "rust-lang" --repo "rust" -p "30 mins" + +# Query repos with the topic hacktoberfest +$ gfi search -hf -p "100 days" + +# Query all user repos +$ gfi search "yankeexe" --user -p "600 hrs" + +# Query a specific repo of a user +$ gfi search "yankeexe" --user --repo "good-first-issues" -p "600 days" + +--period 1 m,min,mins,minutes +--period 2 h,hr,hour,hours,hrs +--period 3 d,day,days +""" + + +@click.command() +@click.option( + "--repo", + "-r", + help="Search in a specific repo of user or organization", + type=str, +) +@click.option( + "--hacktoberfest", + "-hf", + help="Search repositories with topic hacktoberfest", + is_flag=True, +) +@click.option( + "--limit", + "-l", + help="Limit the number of issues to display. Defaults to 10", + type=int, + default=10, +) +@click.option( + "--user", + "-u", + help="Specify if it's a user repository", + is_flag=True, +) +@click.option( + "--web", + help="Display issues on browser", + is_flag=True, +) +@click.option( + "--all", + "-a", + help="View all the issues found without limits.", + is_flag=True, +) +@click.option("--period", "-p", help=period_help_msg) +@click.option( + "--language", + "-lang", + help="Filter issues by programming language (e.g., Python, JavaScript)", + type=str, + default=None, +) +@click.option( + "--keyword", + "-k", + help="Filter issues by keyword in title or description", + type=str, + default=None, +) +@click.argument("name", required=False) +def search( + name: str, + repo: str, + user: bool, + web: bool, + limit: int, + all: bool, + hacktoberfest: bool, + period: str, + language: str, + keyword: str, +): + """Search for good first issues in organizations or user repositories. + + Usage: + + gfi search + + ➡️ repo owner + gfi search "yankeexe" --user + + ➡️ org name + gfi search "ollama" + + ➡️ search in a particular repo + gfi search "yankeexe" --repo "good-first-issues" + gfi search "ollama" --repo "ollama-python" + + ➡️ filter by language + gfi search "facebook" --language "Python" + + ➡️ filter by keyword + gfi search "rust-lang" --keyword "documentation" + + ➡️ combine filters + gfi search "microsoft" --language "JavaScript" --keyword "API" + """ + + if name is None and hacktoberfest is False: + utils.print_help_msg(search) + sys.exit() + + issues: Optional[Iterable] = None + rate_limit: int = 0 + + # Check for GitHub Token + token: Union[str, bool] = utils.check_credential() + + if period: + period: ParsedDuration = parse_period(period) + period = period.utc_date_time.strftime("%Y-%m-%dT%H:%M:%SZ") + + # Identify the flags passed. + query, variables, mode = services.identify_mode( + name, repo, user, hacktoberfest, period, limit + ) + + # Spinner + spinner = Halo(text="Fetching repos...", spinner="dots") + spinner.start() + + # API Call + response = services.caller(token, query, variables) + spinner.succeed("Repos fetched.") + + # Data Filtering + if mode in ["org", "user"]: + issues, rate_limit = services.org_user_pipeline(response, mode) + + if mode == "repo": + issues, rate_limit = services.org_user_pipeline(response, mode) + + if mode == "search": + issues, rate_limit = services.extract_search_results(response) + issues = issues[:limit] # cannot set limit on the search_query directly + + # Apply keyword filter + if keyword: + filtered_issues = [] + for issue in issues: + title = issue[0].lower() + if keyword.lower() in title: + filtered_issues.append(issue) + issues = filtered_issues + + # Apply language filter + if language: + filtered_issues = [] + for issue in issues: + title = issue[0].lower() + if language.lower() in title: + filtered_issues.append(issue) + issues = filtered_issues + + table_headers: List = ["Title", "Issue URL"] + + # No good first issues found + if not issues: + console.print( + f"Remaining requests:dash:: {rate_limit}", + style="bold green", + ) + return console.print( + "No good first issues found!:mask:", + style="bold red", + ) + + # Display issues in browser + if web: + html_data = tabulate(issues, table_headers, tablefmt="html") + return utils.web_server(html_data) + + # Display issues in terminal + row_ids = list(range(1, len(issues) + 1)) + print( + tabulate( + issues, + table_headers, + tablefmt="fancy_grid", + showindex=row_ids, + ) + ) + + console.print(f"Remaining requests:dash:: {rate_limit}", style="bold green") + + # Show applied filters + if keyword or language: + filter_info = [] + if language: + filter_info.append(f"language: {language}") + if keyword: + filter_info.append(f"keyword: {keyword}") + console.print( + f"Filters applied: {', '.join(filter_info)}", + style="bold cyan", + ) + + console.print("Happy Hacking :tada::zap::rocket:", style="bold blue") From 049d4cc3064daee4fe4001ce4a3e6fc6c4952a3a Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Fri, 14 Nov 2025 01:20:40 +0200 Subject: [PATCH 08/10] change --- feature_language_keyword.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/feature_language_keyword.md b/feature_language_keyword.md index 6b76fef..8408125 100644 --- a/feature_language_keyword.md +++ b/feature_language_keyword.md @@ -1,9 +1,14 @@ + # Proposal: Add --language and --keyword flags This change introduces two new CLI flags: + - `--language`: filters issues by programming language (e.g., Python, JavaScript) + - `--keyword`: searches for specific keywords in issue titles/descriptions Example usage: + ```bash + good-first-issues --language Python --keyword documentation From 5377dd4e4f30c40e867bbea8eb084005befd0be5 Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Fri, 14 Nov 2025 01:28:17 +0200 Subject: [PATCH 09/10] ubdated --- feature_language_keyword.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/feature_language_keyword.md b/feature_language_keyword.md index 8408125..fc11bb0 100644 --- a/feature_language_keyword.md +++ b/feature_language_keyword.md @@ -12,3 +12,5 @@ Example usage: ```bash good-first-issues --language Python --keyword documentation + +``` \ No newline at end of file From d756404392f2a4666fe55cc7137cefcf2775c0d5 Mon Sep 17 00:00:00 2001 From: Kholoud Ibrahim Date: Fri, 14 Nov 2025 01:37:44 +0200 Subject: [PATCH 10/10] ubdated2 --- README.md | 13 +++++++++++++ feature_language_keyword.md | 16 ---------------- 2 files changed, 13 insertions(+), 16 deletions(-) delete mode 100644 feature_language_keyword.md diff --git a/README.md b/README.md index 34a0bc4..9a6a322 100644 --- a/README.md +++ b/README.md @@ -133,14 +133,27 @@ $ gfi search -hf --limit 10 --period "48 hours" + + ### Filter by Language +- `--language`: filters issues by programming language (e.g., Python, JavaScript) + +```bash + $ gfi search "facebook" --language "Python" +``` + ### Filter by Keyword +- `--keyword`: searches for specific keywords in issue titles/descriptions + +```bash + $ gfi search "rust-lang" --keyword "documentation" +``` ### Combine Filters $ gfi search "microsoft" --language "JavaScript" --keyword "API" diff --git a/feature_language_keyword.md b/feature_language_keyword.md deleted file mode 100644 index fc11bb0..0000000 --- a/feature_language_keyword.md +++ /dev/null @@ -1,16 +0,0 @@ - -# Proposal: Add --language and --keyword flags - -This change introduces two new CLI flags: - -- `--language`: filters issues by programming language (e.g., Python, JavaScript) - -- `--keyword`: searches for specific keywords in issue titles/descriptions - -Example usage: - -```bash - -good-first-issues --language Python --keyword documentation - -``` \ No newline at end of file