diff --git a/confluence_markdown_exporter/utils/app_data_store.py b/confluence_markdown_exporter/utils/app_data_store.py index 27e9cff..a1409fb 100644 --- a/confluence_markdown_exporter/utils/app_data_store.py +++ b/confluence_markdown_exporter/utils/app_data_store.py @@ -214,7 +214,8 @@ class ExportConfig(BaseModel): description="Whether to include breadcrumb links at the top of the page.", ) filename_encoding: str = Field( - default='"<":"_",">":"_",":":"_","\\"":"_","/":"_","\\\\":"_","|":"_","?":"_","*":"_","\\u0000":"_","[":"_","]":"_"', + default='"<":"_",">":"_",":":"_","\\"":"_","/":"_","\\\\":"_","|":"_","?":"_","*":"_","\\u0000":"_","[":"_","]":"_","\'":"_","’":"_","´":"_","`":"_"', # noqa: RUF001 + title="Filename Encoding", description=( "List character-to-replacement pairs, separated by commas. " @@ -231,6 +232,14 @@ class ExportConfig(BaseModel): title="Filename Length", description="Maximum length of the filename.", ) + filename_lowercase: bool = Field( + default=False, + title="Enforce lowercase paths", + description=( + "Make all paths/files lowercase.\n" + "By default the original casing will be retained.\n" + ), + ) include_document_title: bool = Field( default=True, title="Include Document Title", diff --git a/confluence_markdown_exporter/utils/export.py b/confluence_markdown_exporter/utils/export.py index fde4b2b..41640e7 100644 --- a/confluence_markdown_exporter/utils/export.py +++ b/confluence_markdown_exporter/utils/export.py @@ -106,6 +106,9 @@ def map_char(m: re.Match[str]) -> str: if name in reserved: sanitized = f"{sanitized}_" + if export_options.filename_lowercase: + sanitized = sanitized.lower() + # Limit length to specificed number of characters return sanitized[: export_options.filename_length] diff --git a/tests/unit/utils/test_export.py b/tests/unit/utils/test_export.py index 9b507b5..00383aa 100644 --- a/tests/unit/utils/test_export.py +++ b/tests/unit/utils/test_export.py @@ -127,6 +127,7 @@ def test_no_encoding_specified(self, mock_export_options: MagicMock) -> None: """Test sanitizing filename with no encoding specified.""" mock_export_options.filename_encoding = "" mock_export_options.filename_length = 255 + mock_export_options.filename_lowercase = False result = sanitize_filename("Test File.txt") assert result == "Test File.txt" @@ -136,15 +137,27 @@ def test_with_encoding_mapping(self, mock_export_options: MagicMock) -> None: """Test sanitizing filename with encoding mapping.""" mock_export_options.filename_encoding = '" ":"_",":":"_"' mock_export_options.filename_length = 255 + mock_export_options.filename_lowercase = False result = sanitize_filename("Test File: Name.txt") assert result == "Test_File__Name.txt" + @patch("confluence_markdown_exporter.utils.export.export_options") + def test_with_encoding_mapping_lowercase(self, mock_export_options: MagicMock) -> None: + """Test sanitizing filename with encoding mapping.""" + mock_export_options.filename_encoding = '" ":"_",":":"_"' + mock_export_options.filename_length = 255 + mock_export_options.filename_lowercase = True + + result = sanitize_filename("Test File: Name.txt") + assert result == "test_file__name.txt" + @patch("confluence_markdown_exporter.utils.export.export_options") def test_trim_trailing_spaces_and_dots(self, mock_export_options: MagicMock) -> None: """Test that trailing spaces and dots are trimmed.""" mock_export_options.filename_encoding = "" mock_export_options.filename_length = 255 + mock_export_options.filename_lowercase = False result = sanitize_filename("filename . . ") assert result == "filename" @@ -154,6 +167,7 @@ def test_reserved_windows_names(self, mock_export_options: MagicMock) -> None: """Test that reserved Windows names are handled.""" mock_export_options.filename_encoding = "" mock_export_options.filename_length = 255 + mock_export_options.filename_lowercase = False reserved_names = ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"] for name in reserved_names: @@ -180,6 +194,7 @@ def test_complex_filename_sanitization(self, mock_export_options: MagicMock) -> """Test complex filename sanitization with multiple rules.""" mock_export_options.filename_encoding = '" ":"_","?":"_",":":"_"' mock_export_options.filename_length = 50 + mock_export_options.filename_lowercase = False filename = "My Document: What? How? . ." result = sanitize_filename(filename)