From 41106b0d248f26c3cebbe282a81410187b633d0c Mon Sep 17 00:00:00 2001 From: CodeName393 Date: Sun, 26 Jul 2026 18:16:36 +0900 Subject: [PATCH] fix(core): strip trailing spaces and dots in template formatting to prevent WinError 3 - Clean up redundant spaces and separators when conditional tags (e.g. {year?}) are omitted. - Strip leading and trailing spaces and dots in sanitize_filename and TemplateFormatter. - Prevent FileNotFoundError [WinError 3] caused by trailing spaces in Windows directory paths. --- unshackle/core/utilities.py | 1 + unshackle/core/utils/template_formatter.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/unshackle/core/utilities.py b/unshackle/core/utilities.py index b86171c0..50a64755 100644 --- a/unshackle/core/utilities.py +++ b/unshackle/core/utilities.py @@ -142,6 +142,7 @@ def sanitize_filename(filename: str, spacer: str = ".") -> str: filename = re.sub(r"[:; ]", spacer, filename) # structural chars to (spacer) filename = re.sub(r"[\\*!?¿,'\"" "<>|$#~]", "", filename) # not filename safe chars filename = re.sub(rf"[{spacer}]{{2,}}", spacer, filename) # remove extra neighbouring (spacer)s + filename = filename.strip(" .") # strip leading and trailing spaces and dots for OS path safety return filename diff --git a/unshackle/core/utils/template_formatter.py b/unshackle/core/utils/template_formatter.py index 9d0e237e..080f8192 100644 --- a/unshackle/core/utils/template_formatter.py +++ b/unshackle/core/utils/template_formatter.py @@ -96,11 +96,13 @@ class TemplateFormatter: # Clean up multiple consecutive dots/separators and other artifacts result = re.sub(r"\.{2,}", ".", result) # Multiple dots -> single dot result = re.sub(r"\s{2,}", " ", result) # Multiple spaces -> single space - result = re.sub(r"-{2,}", "-", result) # Multiple dashes -> single dash - result = re.sub(r"^[\.\s\-]+|[\.\s\-]+$", "", result) # Remove leading/trailing dots, spaces, dashes + result = re.sub(r"(?:-\s*)+-", "-", result) # Multiple dashes (with optional spaces) -> single dash result = re.sub(r"\.-", "-", result) # Remove dots before dashes (for dot-based templates) result = re.sub(r"[\.\s]+\)", ")", result) # Remove dots/spaces before closing parentheses result = re.sub(r"\(\s*\)", "", result) # Remove empty parentheses (empty conditional) + result = re.sub(r"\s{2,}", " ", result) # Multiple spaces after parenthesis removal -> single space + result = re.sub(r"(?:-\s*)+-", "-", result) # Multiple dashes (with optional spaces) -> single dash + result = re.sub(r"^[\.\s\-]+|[\.\s\-]+$", "", result) # Remove leading/trailing dots, spaces, dashes # Determine the appropriate separator based on template style # Count separator characters between variables (between } and {) @@ -114,6 +116,8 @@ class TemplateFormatter: else: result = sanitize_filename(result, spacer=".") + result = result.strip(" .") + if not result or result.isspace(): log.warning("Template formatting resulted in empty filename, using fallback") return "untitled"