diff --git a/good_first_issues/commands/search.py b/good_first_issues/commands/search.py index 78f23c6..c430f69 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. @@ -33,9 +32,7 @@ $ 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 """ @@ -93,24 +90,18 @@ def search( Usage: - gfi search + 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" - """ - if name is None and hacktoberfest is False: utils.print_help_msg(search) sys.exit() @@ -136,7 +127,6 @@ def search( # API Call response = services.caller(token, query, variables) - spinner.succeed("Repos fetched.") # Data Filtering @@ -158,7 +148,6 @@ def search( f"Remaining requests:dash:: {rate_limit}", style="bold green", ) - return console.print( "No good first issues found!:mask:", style="bold red", @@ -169,10 +158,13 @@ def search( html_data = tabulate(issues, table_headers, tablefmt="html") return utils.web_server(html_data) - row_ids = list(range(1, len(issues) + 1)) + # Format issues with smart line breaking for better table display + formatted_issues = utils.format_issues_for_display(issues) + row_ids = list(range(1, len(formatted_issues) + 1)) + print( tabulate( - issues, + formatted_issues, table_headers, tablefmt="fancy_grid", showindex=row_ids, diff --git a/good_first_issues/utils/__init__.py b/good_first_issues/utils/__init__.py index 8d41be5..2bda153 100644 --- a/good_first_issues/utils/__init__.py +++ b/good_first_issues/utils/__init__.py @@ -1,5 +1,4 @@ """Utils for CLI""" - import datetime import http.server import os @@ -10,7 +9,7 @@ import webbrowser from collections import namedtuple from pathlib import Path -from typing import Dict, Union +from typing import Dict, List, Tuple, Union import click from halo import Halo @@ -21,23 +20,86 @@ console = Console(color_system="auto") - # Global variables home_dir: str = str(Path.home()) filename: str = "good-first-issues" credential_dir: str = f"{home_dir}/.gfi" credential_file: str = f"{credential_dir}/{filename}" - ParsedDuration = namedtuple("ParsedDuration", ["absolute_period", "utc_date_time"]) +def wrap_text_for_table(text: str, max_width: int = 50) -> str: + """Wrap text at word boundaries for better table display. + + Args: + text: Text to wrap + max_width: Maximum character width per line (default: 50) + + Returns: + Text with newlines inserted at appropriate word boundaries + """ + if len(text) <= max_width: + return text + + words = text.split() + lines = [] + current_line = "" + + for word in words: + # Check if adding this word would exceed the limit + test_line = current_line + " " + word if current_line else word + if len(test_line) <= max_width: + current_line = test_line + else: + # Start new line + if current_line: + lines.append(current_line) + + # Handle extremely long words + if len(word) > max_width: + # Truncate with ellipsis, preserving important parts + if word.startswith(("https://", "http://")): + # For URLs, keep the domain visible + word = word[:max_width-3] + "..." + else: + # For titles, truncate at end + word = word[:max_width-3] + "..." + current_line = word + + if current_line: + lines.append(current_line) + + return "\n".join(lines) + + +def format_issues_for_display(issues: List[Tuple[str, str]], + title_width: int = 50, + url_width: int = 60) -> List[Tuple[str, str]]: + """Format issue list with proper text wrapping for table display. + + Args: + issues: List of tuples containing (title, url) pairs + title_width: Maximum width for title column (default: 50) + url_width: Maximum width for URL column (default: 60) + + Returns: + List of formatted (title, url) tuples with appropriate line breaks + """ + formatted_issues = [] + for title, url in issues: + wrapped_title = wrap_text_for_table(title or "N/A", title_width) + wrapped_url = wrap_text_for_table(url or "N/A", url_width) + formatted_issues.append((wrapped_title, wrapped_url)) + return formatted_issues + + def parse_period(duration: str) -> ParsedDuration: """Parses a duration string into absolute period and UTC datetime. Args: duration: String representing a duration in minutes, hours or days. - Format: where unit can be: + Format: where unit can be: - m, min, mins, minutes for minutes - h, hr, hrs, hours for hours - d, day, days for days @@ -50,7 +112,7 @@ def parse_period(duration: str) -> ParsedDuration: Raises: SystemExit: If duration string format is invalid """ - pattern = r"^(?P\d+)\s?(?Pm|min|mins|minutes|h|hr|hrs|hours|d|day|days)?$" + pattern = r"^(?P\d+)\s?(?Pm|min|mins|minutes|h|hr|hrs|hours|d|day|days)?$" regex = re.compile(pattern) match = regex.match(duration) @@ -61,8 +123,8 @@ def parse_period(duration: str) -> ParsedDuration: ) sys.exit(1) - period_str = match.group("Period") - duration_type = match.group("Duration") + period_str = match.group("period") + duration_type = match.group("duration") # Convert period to integer and ensure it's non-negative period = int(period_str) @@ -117,7 +179,6 @@ def add_credential(credential: str): shutil.rmtree(credential_dir) os.mkdir(credential_dir) - with open(f"{credential_file}", "w+") as cred: cred.write(credential.strip()) @@ -138,7 +199,6 @@ def check_credential() -> Union[str, bool]: elif os.path.exists(credential_file): with open(credential_file) as cred: return cred.readline() - return False @@ -153,7 +213,6 @@ def gql_rate_limit() -> int: spinner.start() payload: Dict = services.caller(token, rate_limit_query, {}) - spinner.succeed("rate limit") return payload["data"].get("rateLimit").get("remaining") @@ -165,20 +224,17 @@ def web_server(html_data): """ Handler = http.server.SimpleHTTPRequestHandler filename = "index.html" - url_data = add_anchor_tag(html_data) try: with open(filename, "w+") as file: file.write(html_template.format(table=url_data)) socketserver.TCPServer.allow_reuse_address = True - with socketserver.TCPServer(("localhost", 0), Handler) as httpd: port = httpd.server_address[1] print("Serving at port", port) webbrowser.open(f"http://127.0.0.1:{port}/") httpd.serve_forever() - except KeyboardInterrupt: print("\nServer stopped") finally: @@ -190,24 +246,21 @@ def add_anchor_tag(html_data): Use regex to get URL elements inside tag and wrap them inside an anchor tag. """ - pattern = re.compile(r"(https.+)<\/td>") + pattern = re.compile(r"(https.+)<\/td>") matches = re.findall(pattern, html_data) - for item in matches: - url = f"{item}" + url = f"{item}" html_data = re.sub(pattern, url, html_data, 1) - return html_data # Template for displaying issues on web. -html_template = """ - - +html_template = """ + - - - + + @@ -215,20 +268,18 @@ def add_anchor_tag(html_data): href="https://fonts.googleapis.com/css2?family=Secular+One&display=swap" rel="stylesheet" /> - +