Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions good_first_issues/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
"""

Expand Down Expand Up @@ -93,24 +90,18 @@ def search(

Usage:

gfi search <repo-owner/org-name>
gfi search <repo-owner>

➡️ 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()
Expand All @@ -136,7 +127,6 @@ def search(

# API Call
response = services.caller(token, query, variables)

spinner.succeed("Repos fetched.")

# Data Filtering
Expand All @@ -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",
Expand All @@ -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,
Expand Down
113 changes: 78 additions & 35 deletions good_first_issues/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Utils for CLI"""

import datetime
import http.server
import os
Expand All @@ -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
Expand All @@ -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: <number><unit> where unit can be:
Format: <number> <unit> where unit can be:
- m, min, mins, minutes for minutes
- h, hr, hrs, hours for hours
- d, day, days for days
Expand All @@ -50,7 +112,7 @@ def parse_period(duration: str) -> ParsedDuration:
Raises:
SystemExit: If duration string format is invalid
"""
pattern = r"^(?P<Period>\d+)\s?(?P<Duration>m|min|mins|minutes|h|hr|hrs|hours|d|day|days)?$"
pattern = r"^(?P<period>\d+)\s?(?P<duration>m|min|mins|minutes|h|hr|hrs|hours|d|day|days)?$"
regex = re.compile(pattern)
match = regex.match(duration)

Expand All @@ -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)
Expand Down Expand Up @@ -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())

Expand All @@ -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


Expand All @@ -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")
Expand All @@ -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:
Expand All @@ -190,45 +246,40 @@ def add_anchor_tag(html_data):
Use regex to get URL elements inside <td> tag and
wrap them inside an anchor tag.
"""
pattern = re.compile(r"<td>(https.+)<\/td>")
pattern = re.compile(r"</td><td>(https.+)&lt;\/td&gt;")
matches = re.findall(pattern, html_data)

for item in matches:
url = f"<td><a target='_blank' href='{item}'>{item}</a></td>"
url = f"</td><a href="{item}" target="_blank">{item}</a>"
html_data = re.sub(pattern, url, html_data, 1)

return html_data


# Template for displaying issues on web.
html_template = """
<!DOCTYPE html>
<html lang="en">
html_template = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Secular+One&display=swap"
rel="stylesheet"
/>
<link rel="icon" href="data:,">
<link href="data:," rel="icon"/>
<style>
body {{
background-color: #afd0a9;
font-family: "Roboto", sans-serif;
}}

h1 {{
text-align: center;
font-size: 42px;
font-family: "Secular One", sans-serif;
color: white;
}}

table {{
border-spacing: 0px;
width: 80%;
Expand All @@ -237,36 +288,29 @@ def add_anchor_tag(html_data):
overflow: hidden;
box-shadow: 5px 5px 10px gray;
}}

th,
td {{
text-align: left;
padding: 12px;
}}

tr:nth-child(even) {{
background-color: #f2f2f2;
}}

tr:nth-child(odd) {{
background-color: white;
}}

tr:hover {{
background-color: #c0c0c0;
}}

th {{
background-color: #006e58;
color: white;
font-size: 18px;
font-weight: 900;
}}

a {{
color: #006e58;
}}

a:hover {{
color: black;
}}
Expand All @@ -278,5 +322,4 @@ def add_anchor_tag(html_data):
{table}
</body>
</html>

"""
Loading