From eb5c39402cf26623a424c1f0ee0e9e18087d074f Mon Sep 17 00:00:00 2001 From: pepepeboom Date: Sat, 18 Jul 2026 20:40:12 +0800 Subject: [PATCH] Add hex_color and slug custom types Add two regex-based custom types to outlines.types: - hex_color matches CSS hexadecimal color notation in the three-digit (#rgb) and six-digit (#rrggbb) forms. The four- and eight-digit alpha forms are intentionally excluded to keep the canonical opaque syntax. - slug matches lowercase-hyphenated URL slugs: ASCII lowercase alphanumeric groups separated by single hyphens, rejecting uppercase, underscores, empty strings, and leading, trailing, or doubled hyphens. Add accept and reject cases for both types to the parametrized regex test, following the pattern established for semver and mac_address. Refs #1303 Co-Authored-By: Claude Fable 5 --- src/outlines/types/__init__.py | 14 ++++++++++++++ tests/types/test_custom_types.py | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/outlines/types/__init__.py b/src/outlines/types/__init__.py index a81143b2e..860ddfed5 100644 --- a/src/outlines/types/__init__.py +++ b/src/outlines/types/__init__.py @@ -86,6 +86,8 @@ def __getattr__(self, name): "ipv6", "semver", "mac_address", + "hex_color", + "slug", # Document-specific types "sentence", "paragraph", @@ -160,6 +162,18 @@ def __getattr__(self, name): r"(?:\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}" ) +# CSS hexadecimal color notation: https://www.w3.org/TR/css-color-4/#hex-notation +# This matches only the ubiquitous three-digit (#rgb) and six-digit (#rrggbb) +# forms. The four- and eight-digit forms that encode an alpha channel are +# intentionally excluded to keep the type to the canonical opaque-color syntax. +hex_color = Regex(r"#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})") + +# URL slugs in the lowercase-hyphenated form produced by common web framework +# slugify helpers: ASCII lowercase alphanumeric groups separated by single +# hyphens. Uppercase letters, underscores, and leading, trailing, or +# consecutive hyphens are excluded, as is the empty string. +slug = Regex(r"[a-z0-9]+(?:-[a-z0-9]+)*") + # Document-specific types sentence = Regex(r"[A-Z].*\s*[.!?]") paragraph = Regex(rf"{sentence.pattern}(?:\s+{sentence.pattern})*\n+") diff --git a/tests/types/test_custom_types.py b/tests/types/test_custom_types.py index ae2fa5aea..f3df5510e 100644 --- a/tests/types/test_custom_types.py +++ b/tests/types/test_custom_types.py @@ -120,6 +120,24 @@ (types.mac_address, "00:1A:2B:3C:4D:5E", True), (types.mac_address, "00-1A-2B-3C-4D-5E", False), (types.mac_address, "00:1A:2B:3C:4D", False), + (types.hex_color, "#1a2b3c", True), + (types.hex_color, "#abc", True), + (types.hex_color, "#ABC123", True), + (types.hex_color, "1a2b3c", False), + (types.hex_color, "#12345", False), + (types.hex_color, "#gggggg", False), + (types.hex_color, "#abcd", False), + (types.hex_color, "#aabbccdd", False), + (types.hex_color, " #abc", False), + (types.slug, "my-post-title", True), + (types.slug, "post", True), + (types.slug, "a1-b2", True), + (types.slug, "My-Post", False), + (types.slug, "-leading-hyphen", False), + (types.slug, "trailing-hyphen-", False), + (types.slug, "double--hyphen", False), + (types.slug, "under_score", False), + (types.slug, "", False), ], ) def test_type_regex(custom_type, test_string, should_match):