Skip to content

Commit bf369da

Browse files
committed
normalize tag
1 parent ce39054 commit bf369da

5 files changed

Lines changed: 21 additions & 21 deletions

File tree

src/rockgarden/output/tags.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,11 @@
11
"""Tag index page generation."""
22

3-
import re
43
from pathlib import Path
54

65
from jinja2 import Environment
76

87
from rockgarden.content.models import Page
9-
from rockgarden.urls import get_tag_url, get_tags_root_url, get_url
10-
11-
12-
def normalize_tag(tag: str) -> str:
13-
"""Normalize a tag to a URL-safe slug.
14-
15-
Strips leading '#', lowercases, and replaces any character that is not
16-
alphanumeric, hyphen, or underscore with a hyphen. This prevents path
17-
traversal via tags containing '/' or '..'.
18-
19-
Tags 'Python', '#python', and 'python' all normalize to 'python'.
20-
Obsidian nested tags like 'character/pc' normalize to 'character-pc'.
21-
"""
22-
slug = tag.lstrip("#").lower()
23-
slug = re.sub(r"[^a-z0-9_-]", "-", slug)
24-
slug = re.sub(r"-+", "-", slug)
25-
return slug.strip("-")
8+
from rockgarden.urls import get_tag_url, get_tags_root_url, get_url, normalize_tag
269

2710

2811
def collect_tags(pages: list[Page]) -> dict[str, list[Page]]:

src/rockgarden/render/engine.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from rockgarden.config import Config
1111
from rockgarden.content.models import Page
1212
from rockgarden.nav.tree import NavNode
13-
from rockgarden.urls import get_tag_url, get_tags_root_url
13+
from rockgarden.urls import get_tag_url, get_tags_root_url, normalize_tag
1414

1515

1616
def _make_format_datetime(tz_name: str):
@@ -64,6 +64,7 @@ def create_engine(
6464
)
6565
env.filters["format_datetime"] = _make_format_datetime(config.dates.timezone)
6666
clean_urls = config.site.clean_urls
67+
env.globals["normalize_tag"] = normalize_tag
6768
env.globals["tag_url"] = lambda slug: get_tag_url(slug, clean_urls)
6869
env.globals["tags_root_url"] = get_tags_root_url(clean_urls)
6970
return env

src/rockgarden/templates/folder_index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
{% if child.tags %}
5050
<div class="flex flex-wrap gap-1">
5151
{% for tag in child.tags %}
52-
{% set tag_slug = tag.lstrip('#').lower() %}
52+
{% set tag_slug = normalize_tag(tag) %}
5353
{% if site.tag_index %}
5454
<a href="{{ tag_url(tag_slug) }}" class="badge badge-sm badge-ghost hover:badge-primary">{{ tag.lstrip('#') }}</a>
5555
{% else %}

src/rockgarden/templates/page.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
{% if tags %}
2727
<div class="flex flex-wrap gap-1">
2828
{% for tag in tags %}
29-
{% set tag_slug = tag.lstrip('#').lower() %}
29+
{% set tag_slug = normalize_tag(tag) %}
3030
{% if site.tag_index %}
3131
<a href="{{ tag_url(tag_slug) }}" class="badge badge-sm badge-ghost hover:badge-primary">{{ tag.lstrip('#') }}</a>
3232
{% else %}

src/rockgarden/urls.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33
import re
44

55

6+
def normalize_tag(tag: str) -> str:
7+
"""Normalize a tag to a URL-safe slug.
8+
9+
Strips leading '#', lowercases, and replaces any character that is not
10+
alphanumeric, hyphen, or underscore with a hyphen. Prevents path traversal
11+
via tags containing '/' or '..'.
12+
13+
Tags 'Python', '#python', and 'python' all normalize to 'python'.
14+
Obsidian nested tags like 'character/pc' normalize to 'character-pc'.
15+
"""
16+
slug = tag.lstrip("#").lower()
17+
slug = re.sub(r"[^a-z0-9_-]", "-", slug)
18+
slug = re.sub(r"-+", "-", slug)
19+
return slug.strip("-")
20+
21+
622
def generate_slug(relative_path: str) -> str:
723
"""Generate URL-safe slug from a relative file path.
824

0 commit comments

Comments
 (0)