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
9 changes: 9 additions & 0 deletions routes/main_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@
from utils.data_loader import find_project_by_id, get_project_stats
from utils.file_server import read_starter_code, resolve_starter_file, get_starter_code_dir

from utils.link_helper import parse_external_link

# Create the Blueprint that app.py will register
main = Blueprint("main", __name__)


@main.app_template_filter("parse_resource")
def parse_resource_filter(resource_str):
"""Jinja filter to parse a resource string into a standardized dict."""
return parse_external_link(resource_str)



@main.route("/")
def index():
"""Render the homepage with the skill input form and dynamic stats."""
Expand Down
11 changes: 5 additions & 6 deletions templates/project.html
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,14 @@ <h2>Learning Resources</h2>
<ul class="resource-list">
{% for resource in project.resources %}
<li class="resource-item">
<!-- Parse the "Label: URL" format stored in the data -->
{% set parts = resource.split(": http") %}
{% if parts|length > 1 %}
<a href="http{{ parts[1] }}" target="_blank" rel="noopener noreferrer" class="resource-link">
<span>{{ parts[0] }}</span>
{% set link_info = resource | parse_resource %}
{% if link_info.is_link %}
<a href="{{ link_info.url }}" target="_blank" rel="noopener noreferrer" class="resource-link">
<span>{{ link_info.label }}</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
</a>
{% else %}
<span class="resource-plain">{{ resource }}</span>
<span class="resource-plain">{{ link_info.label }}</span>
{% endif %}
</li>
{% endfor %}
Expand Down
30 changes: 30 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
parse_skills,
score_single_project,
)
from utils.link_helper import parse_external_link
from app import app, internal_server_error


Expand Down Expand Up @@ -73,6 +74,35 @@ def test_find_project_by_id_missing():
# Recommender utility tests
# ============================================================

def test_parse_external_link():
"""parse_external_link should correctly identify and split links and labels."""
assert parse_external_link("Python docs: https://docs.python.org") == {
"label": "Python docs",
"url": "https://docs.python.org",
"is_link": True
}
assert parse_external_link("Real Python - https://realpython.com") == {
"label": "Real Python",
"url": "https://realpython.com",
"is_link": True
}
assert parse_external_link("https://docs.python.org") == {
"label": "https://docs.python.org",
"url": "https://docs.python.org",
"is_link": True
}
assert parse_external_link("Some non-link text description") == {
"label": "Some non-link text description",
"url": None,
"is_link": False
}
assert parse_external_link("") == {
"label": "",
"url": None,
"is_link": False
}


def test_parse_skills_basic():
"""parse_skills should split on commas and lowercase each entry."""
result = parse_skills("Python, HTML, CSS")
Expand Down
47 changes: 47 additions & 0 deletions utils/link_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# utils/link_helper.py
# Reusable helper to parse and standardize external link handling.

import re

def parse_external_link(resource):
"""
Safely parses a resource string which might contain an external URL.

Supports:
- "Label: https://url"
- "Label - https://url"
- "https://url" (plain URL)
- Plain text (no URL)

Returns a dict:
{
"label": str,
"url": str or None,
"is_link": bool
}
"""
if not resource:
return {"label": "", "url": None, "is_link": False}

resource_str = str(resource).strip()

# Regex to find a standard http/https URL
url_match = re.search(r'https?://[^\s]+', resource_str)
if not url_match:
return {"label": resource_str, "url": None, "is_link": False}

url = url_match.group(0)

# The label is whatever is before the URL, stripped of colons, dashes, and spaces
label_part = resource_str[:url_match.start()].strip()
if label_part:
# Remove trailing colons, dashes, or spaces
label = re.sub(r'[:\-–—\s]+$', '', label_part).strip()
else:
label = url

return {
"label": label,
"url": url,
"is_link": True
}
Loading