Skip to content

Commit f94bde7

Browse files
refactor(slugify): default= kwarg, caller parity, review feedback (#30)
1 parent c5db2b5 commit f94bde7

4 files changed

Lines changed: 50 additions & 9 deletions

File tree

api/export_api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ def bulk_export():
9191

9292
stats = compute_stats(session)
9393
md = session_to_markdown(session, stats)
94-
title_slug = slugify(session["title"]) or "session"
94+
title_slug = slugify(session["title"], default="session")
9595
short_id = sid[:8]
96-
proj_slug = slugify(project["name"]) or "project"
96+
proj_slug = slugify(project["name"], default="project")
9797
ts = session["metadata"].get("first_timestamp", "")
9898
ts_file = ts[:19].replace(":", "-") if ts else "0000-00-00T00-00-00"
9999
rel_path = f"{proj_slug}/{ts_file}__{title_slug}__{short_id}.md"
@@ -149,7 +149,7 @@ def export_session(project_name, session_id):
149149
if is_session_excluded(rules, session, project_name):
150150
return jsonify({"error": "Session not found"}), 404
151151
stats = compute_stats(session)
152-
title_slug = slugify(session["title"]) or "session"
152+
title_slug = slugify(session["title"], default="session")
153153

154154
if fmt == "json":
155155
content = session_to_json(session, stats)

scripts/export.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,9 @@ def cmd_export(args):
367367
meta["first_timestamp"] = ts
368368
date_str = ts[:10]
369369
ts_file = ts[:19].replace(":", "-") # 2026-02-10T01-46-15
370+
title_slug = slugify(session["title"], default="session")
370371
short_id = sid[:8]
371-
title_slug = slugify(session["title"]) or "session"
372-
project_slug = slugify(project["name"]) or "project"
372+
project_slug = slugify(project["name"], default="project")
373373

374374
if fmt in ("md", "both"):
375375
md = session_to_markdown(session, stats)
@@ -445,8 +445,8 @@ def cmd_export(args):
445445

446446
def _export_single(session: dict, stats: dict, fmt: str, out_dir: str):
447447
"""Write one session to disk as md, json, or both."""
448+
title_slug = slugify(session["title"], default="session")
448449
short_id = session["session_id"][:8]
449-
title_slug = slugify(session["title"]) or "session"
450450
ts = session["metadata"].get("first_timestamp", "")
451451
ts_file = ts[:19].replace(":", "-") if ts else "0000-00-00T00-00-00"
452452

tests/test_slugify.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
implementation matches the API for portable zip / download filenames.
66
"""
77

8+
import os
9+
810
from utils.slugify import slugify
911

1012

@@ -28,3 +30,31 @@ def test_empty_after_strip():
2830

2931
def test_digits_preserved():
3032
assert slugify("Issue 42 Fix") == "issue-42-fix"
33+
34+
35+
def test_punctuation_examples_match_regex_behavior():
36+
assert slugify("AT&T") == "at-t"
37+
assert slugify("issue#42") == "issue-42"
38+
39+
40+
def test_default_used_when_slug_empty():
41+
assert slugify("!!!", default="session") == "session"
42+
assert slugify("!!!") == ""
43+
44+
45+
def test_export_leaf_path_parity_api_zip_vs_cli():
46+
"""Same session inputs → same ``proj_slug``, ``title_slug``, and file leaf as API vs CLI."""
47+
title = "Issue #42: AT&T"
48+
project = "Foo/Bar!"
49+
sid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
50+
ts_file = "2026-05-07T12-00-00"
51+
short_id = sid[:8]
52+
title_slug = slugify(title, default="session")
53+
proj_slug = slugify(project, default="project")
54+
leaf_md = f"{ts_file}__{title_slug}__{short_id}.md"
55+
api_zip_inner = f"{proj_slug}/{leaf_md}"
56+
date_str = ts_file[:10]
57+
cli_rel = os.path.join(date_str, proj_slug, leaf_md)
58+
assert api_zip_inner.endswith(leaf_md)
59+
assert os.path.basename(cli_rel) == leaf_md
60+
assert cli_rel.replace("\\", "/").endswith(f"{proj_slug}/{leaf_md}")

utils/slugify.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,19 @@
1111
import re
1212

1313

14-
def slugify(text: str) -> str:
15-
"""Lowercase *text* and replace each run of non-[a-z0-9] with a single hyphen."""
14+
def slugify(text: str, *, default: str = "") -> str:
15+
"""Lowercase *text* and replace each run of non-[a-z0-9] with one hyphen.
16+
17+
After stripping leading/trailing hyphens, returns that string; if it is
18+
empty, returns *default*. Export code passes ``default="session"`` or
19+
``default="project"``.
20+
Examples (handled by the ``[^a-z0-9]+`` substitution below):
21+
22+
- ``AT&T`` → ``at-t``
23+
- ``issue#42`` → ``issue-42``
24+
"""
1625
text = text.lower()
26+
# Non-ASCII-alphanumeric runs → '-'; e.g. AT&T → at-t, issue#42 → issue-42.
1727
text = re.sub(r"[^a-z0-9]+", "-", text)
18-
return text.strip("-")
28+
stripped = text.strip("-")
29+
return stripped if stripped else default

0 commit comments

Comments
 (0)