@@ -76,10 +76,14 @@ def generate_permalink(file_path: Union[Path, str, PathLike], split_extension: b
7676
7777 Args:
7878 file_path: Original file path (str, Path, or PathLike)
79+ split_extension: Whether to split off and discard file extensions.
80+ When True, uses mimetypes to detect real extensions.
81+ When False, preserves all content including periods.
7982
8083 Returns:
8184 Normalized permalink that matches validation rules. Converts spaces and underscores
8285 to hyphens for consistency. Preserves non-ASCII characters like Chinese.
86+ Preserves periods in version numbers (e.g., "2.0.0") when they're not real file extensions.
8387
8488 Examples:
8589 >>> generate_permalink("docs/My Feature.md")
@@ -90,12 +94,25 @@ def generate_permalink(file_path: Union[Path, str, PathLike], split_extension: b
9094 'design/unified-model-refactor'
9195 >>> generate_permalink("中文/测试文档.md")
9296 '中文/测试文档'
97+ >>> generate_permalink("Version 2.0.0")
98+ 'version-2.0.0'
9399 """
94100 # Convert Path to string if needed
95101 path_str = Path (str (file_path )).as_posix ()
96102
97- # Remove extension (for now, possibly)
98- (base , extension ) = os .path .splitext (path_str )
103+ # Only split extension if there's a real file extension
104+ # Use mimetypes to detect real extensions, avoiding misinterpreting periods in version numbers
105+ import mimetypes
106+ mime_type , _ = mimetypes .guess_type (path_str )
107+ has_real_extension = mime_type is not None
108+
109+ if has_real_extension and split_extension :
110+ # Real file extension detected - split it off
111+ (base , extension ) = os .path .splitext (path_str )
112+ else :
113+ # No real extension or split_extension=False - process the whole string
114+ base = path_str
115+ extension = ""
99116
100117 # Check if we have CJK characters that should be preserved
101118 # CJK ranges: \u4e00-\u9fff (CJK Unified Ideographs), \u3000-\u303f (CJK symbols),
@@ -147,9 +164,9 @@ def generate_permalink(file_path: Union[Path, str, PathLike], split_extension: b
147164 # Remove apostrophes entirely (don't replace with hyphens)
148165 text_no_apostrophes = text_with_hyphens .replace ("'" , "" )
149166
150- # Replace unsafe chars with hyphens, but preserve CJK characters
167+ # Replace unsafe chars with hyphens, but preserve CJK characters and periods
151168 clean_text = re .sub (
152- r"[^a-z0-9\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf/\-]" , "-" , text_no_apostrophes
169+ r"[^a-z0-9\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf/\-\. ]" , "-" , text_no_apostrophes
153170 )
154171 else :
155172 # Original ASCII-only processing for backward compatibility
@@ -168,8 +185,8 @@ def generate_permalink(file_path: Union[Path, str, PathLike], split_extension: b
168185 # Remove apostrophes entirely (don't replace with hyphens)
169186 text_no_apostrophes = text_with_hyphens .replace ("'" , "" )
170187
171- # Replace remaining invalid chars with hyphens
172- clean_text = re .sub (r"[^a-z0-9/\-]" , "-" , text_no_apostrophes )
188+ # Replace remaining invalid chars with hyphens, preserving periods
189+ clean_text = re .sub (r"[^a-z0-9/\-\. ]" , "-" , text_no_apostrophes )
173190
174191 # Collapse multiple hyphens
175192 clean_text = re .sub (r"-+" , "-" , clean_text )
0 commit comments