Add a max_length option to slugify

Done

/home/devdi/devswarm/evals/tasks/slugify_max_length/repo · 2026-11-09 16:23:14 UTC · took 55.5s

  1. Plan (done)
  2. Code (done)
  3. Test (done)
  4. Review (done)
  5. Finish (done)
Cost
$0.0071
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
12
Prompt read from cache
76%

Usage by agent

Cost per agent
planner: $0.0024 planner planner, Cost: $0.0024 $0.0024 coder: $0.0019 coder coder, Cost: $0.0019 $0.0019 tester: $0.0028 tester tester, Cost: $0.0028 $0.0028
Tokens per agent
  • Cache read
  • Cache write
  • Input
  • Output
planner: 15.0K planner planner, Cache read: 8,183 planner, Cache write: 2,935 planner, Output: 3,842 15.0K coder: 23.1K coder coder, Cache read: 16.1K coder, Cache write: 4,539 coder, Output: 2,391 23.1K tester: 32.5K tester tester, Cache read: 21.9K tester, Cache write: 7,217 tester, Output: 3,344 32.5K
View as table
AgentCostCache readCache writeInputOutput
planner$0.00248,1832,935123,842
coder$0.001916,1194,539102,391
tester$0.002821,8917,217103,344

Timeline

  1. planner iteration 1 Finished 4 calls · 15.0K tokens · $0.0024 · 19.3s

    Loading…

  2. coder iteration 1 Finished 4 calls · 23.1K tokens · $0.0019 · 13.9s

    Loading…

  3. tester iteration 1 Finished 4 calls · 32.5K tokens · $0.0028 · 21.1s

    Loading…

  4. verify iteration 1 Passed 0.6s
    .................                                                        [100%]
    17 passed in 0.04s
    

Final diff

diff --git a/slug.py b/slug.py
index c4d3700..82dd8d3 100644
--- a/slug.py
+++ b/slug.py
@@ -1,5 +1,20 @@
 import re
+from typing import Optional
 
 
-def slugify(text: str) -> str:
-    return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
+def slugify(text: str, max_length: Optional[int] = None) -> str:
+    slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
+    if max_length is None:
+        return slug
+    if max_length < 0:
+        raise ValueError("max_length must be non-negative")
+    if len(slug) <= max_length:
+        return slug
+    # The slug has no leading, trailing or doubled hyphens, so cutting at any
+    # hyphen at or before max_length yields a whole-word prefix with no
+    # trailing hyphen.
+    idx = slug.rfind("-", 0, max_length + 1)
+    if idx != -1:
+        return slug[:idx]
+    # The first word alone is longer than max_length: hard-cut it.
+    return slug[:max_length]
diff --git a/test_slug.py b/test_slug.py
index 73209ea..8097461 100644
--- a/test_slug.py
+++ b/test_slug.py
@@ -3,3 +3,125 @@ from slug import slugify
 
 def test_basic():
     assert slugify("Hello, World!") == "hello-world"
+
+
+def test_no_max_length_unchanged():
+    assert slugify("the quick brown fox") == "the-quick-brown-fox"
+
+
+def test_short_slug_unchanged():
+    assert slugify("ab cd", 5) == "ab-cd"
+    assert slugify("ab cd", 50) == "ab-cd"
+
+
+def test_cut_at_word_boundary():
+    assert slugify("the quick brown fox", 10) == "the-quick"
+
+
+def test_exact_fit_at_boundary():
+    assert slugify("ab cd", 5) == "ab-cd"
+
+
+def test_hyphen_exactly_at_max_length():
+    # "abcd" is 4 chars; the hyphen sits at index 4 == max_length.
+    assert slugify("abcd efgh", 4) == "abcd"
+
+
+def test_long_first_word_hard_cut():
+    assert slugify("abcdefghij", 4) == "abcd"
+
+
+def test_no_trailing_hyphen_after_cut():
+    result = slugify("ab cd ef", 3)
+    assert result == "ab"
+    assert not result.endswith("-")
+
+
+def test_max_length_zero():
+    assert slugify("hello world", 0) == ""
+    assert slugify("", 0) == ""
+
+
+def test_empty_with_max_length():
+    assert slugify("", 5) == ""
+
+
+def test_negative_max_length_raises():
+    import pytest
+
+    with pytest.raises(ValueError):
+        slugify("hello", -1)
+
+
+def test_length_never_exceeds_max_length():
+    text = "a bb ccc dddd eeeee ffffff"
+    for n in range(0, 30):
+        result = slugify(text, n)
+        assert len(result) <= n
+        assert not result.endswith("-")
+
+
+def _original_slugify(text):
+    import re
+
+    return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
+
+
+def test_default_matches_original_implementation():
+    samples = [
+        "Hello, World!",
+        "  --Leading and trailing--  ",
+        "Café déjà vu & naïve ünïcode",
+        "",
+        "123 456-789",
+        "!!!",
+        "a" * 50,
+    ]
+    for text in samples:
+        assert slugify(text) == _original_slugify(text)
+        assert slugify(text, None) == _original_slugify(text)
+
+
+def test_longest_whole_word_prefix_is_chosen():
+    # "the-quick-brown" is 15 chars, so within 14 the longest whole-word prefix is "the-quick".
+    assert slugify("the quick brown fox", 15) == "the-quick-brown"
+    assert slugify("the quick brown fox", 14) == "the-quick"
+
+
+def test_hard_cut_when_first_word_too_long_with_more_words():
+    assert slugify("abcdefghij kl", 4) == "abcd"
+    assert slugify("abcdefghij-kl-mn", 9) == "abcdefghi"
+
+
+def test_non_ascii_and_punctuation_with_max_length():
+    # "Café déjà vu" slugifies to "caf-d-j-vu"; within 6 the longest whole-word prefix is "caf-d".
+    assert slugify("Café déjà vu", 6) == "caf-d"
+    assert slugify("Café déjà vu", 3) == "caf"
+    assert slugify("Hello, World!", 5) == "hello"
+
+
+def test_property_prefix_of_full_slug_on_word_boundary():
+    texts = [
+        "the quick brown fox jumps over the lazy dog",
+        "a bb ccc dddd eeeee ffffff",
+        "x1 y22 z333 w4444",
+    ]
+    for text in texts:
+        full = slugify(text)
+        for n in range(0, len(full) + 3):
+            result = slugify(text, n)
+            assert len(result) <= n
+            assert not result.endswith("-")
+            if len(full) <= n:
+                assert result == full
+            else:
+                # Must be a whole-word prefix: next char in full is a hyphen.
+                assert full.startswith(result)
+                if len(full.split("-")[0]) > n:
+                    # First word alone exceeds n: hard cut, no boundary expected.
+                    assert result == full[:n]
+                    continue
+                assert full[len(result)] == "-"
+                # And it must be the longest such prefix.
+                nxt = full.find("-", len(result) + 1)
+                assert nxt == -1 or nxt > n