diff --git a/pyproject.toml b/pyproject.toml index 02c1fc0..4b3565d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ build-backend = "uv_build" [dependency-groups] dev = [ + "black>=26.1.0", "pytest>=9.0.2", "pytest-httpx>=0.35.0", "syrupy>=5.0.0", diff --git a/src/claude_code_transcripts/__init__.py b/src/claude_code_transcripts/__init__.py index e4854a3..bce36eb 100644 --- a/src/claude_code_transcripts/__init__.py +++ b/src/claude_code_transcripts/__init__.py @@ -1,5 +1,6 @@ """Convert Claude Code session JSON to a clean mobile-friendly HTML page with pagination.""" +import getpass import json import html import os @@ -207,30 +208,36 @@ def get_project_display_name(folder_name): name = name[len(prefix) :] break + # Strip username prefix if present (handles multi-part usernames like first.last) + username_stripped = False + try: + username = getpass.getuser().replace(".", "-") + "-" + if name.startswith(username): + name = name[len(username) :] + username_stripped = True + except Exception: + pass + # Split on dashes and find meaningful parts parts = name.split("-") # Common intermediate directories to skip skip_dirs = {"projects", "code", "repos", "src", "dev", "work", "documents"} - # Find the first meaningful part (after skipping username and common dirs) + # Find meaningful parts (skipping common directories) meaningful_parts = [] - found_project = False - for i, part in enumerate(parts): if not part: continue - # Skip the first part if it looks like a username (before common dirs) - if i == 0 and not found_project: - # Check if next parts contain common dirs - remaining = [p.lower() for p in parts[i + 1 :]] + # Fallback: if username wasn't stripped and this is the first part, + # skip it if there's a skip_dir later in the path + if not username_stripped and i == 0: + remaining = [p.lower() for p in parts[1:]] if any(d in remaining for d in skip_dirs): continue if part.lower() in skip_dirs: - found_project = True continue meaningful_parts.append(part) - found_project = True if meaningful_parts: return "-".join(meaningful_parts) diff --git a/tests/test_all.py b/tests/test_all.py index 8215acd..c12c4ef 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -71,22 +71,42 @@ def output_dir(): class TestGetProjectDisplayName: """Tests for get_project_display_name function.""" - def test_extracts_project_name_from_path(self): + def test_extracts_project_name_from_path(self, monkeypatch): """Test extracting readable project name from encoded path.""" + monkeypatch.setattr("getpass.getuser", lambda: "user") assert get_project_display_name("-home-user-projects-myproject") == "myproject" - def test_handles_nested_paths(self): + def test_handles_nested_paths(self, monkeypatch): """Test handling nested project paths.""" + monkeypatch.setattr("getpass.getuser", lambda: "user") assert get_project_display_name("-home-user-code-apps-webapp") == "apps-webapp" - def test_handles_windows_style_paths(self): + def test_handles_windows_style_paths(self, monkeypatch): """Test handling Windows-style encoded paths.""" + monkeypatch.setattr("getpass.getuser", lambda: "name") assert get_project_display_name("-mnt-c-Users-name-Projects-app") == "app" def test_handles_simple_name(self): """Test handling already simple names.""" assert get_project_display_name("simple-project") == "simple-project" + def test_skips_multipart_username_prefix(self, monkeypatch): + """Test that multi-part usernames are stripped as a prefix.""" + monkeypatch.setattr("getpass.getuser", lambda: "first.last") + # first.last becomes first-last-, stripped after -Users- + assert ( + get_project_display_name("-Users-first-last-code-github-org-repo") + == "github-org-repo" + ) + + def test_skips_all_skip_dirs(self, monkeypatch): + """Test that all skip_dirs are skipped (original behavior).""" + monkeypatch.setattr("getpass.getuser", lambda: "name") + # Both 'code' and 'work' are skip_dirs, both should be skipped + assert ( + get_project_display_name("-Users-name-code-work-myproject") == "myproject" + ) + class TestFindAllSessions: """Tests for find_all_sessions function."""