Skip to content

Commit

Permalink
Merge pull request #60 from thombashi/fix_coverage
Browse files Browse the repository at this point in the history
Fix dot-files validation
  • Loading branch information
thombashi authored Jan 3, 2025
2 parents d10313f + fa4e9bd commit 377948a
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 26 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
setup.py
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
setup.py
Expand All @@ -88,7 +88,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
setup.py
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on_push_default_branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
setup.py
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: |
setup.py
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ LAST_UPDATE_YEAR := $(shell git log -1 --format=%cd --date=format:%Y)


$(BIN_CHANGELOG_FROM_RELEASE):
mkdir -p $(BIN_DIR)
GOBIN=$(BIN_DIR) go install github.com/rhysd/changelog-from-release/v3@latest

.PHONY: build
Expand Down
1 change: 1 addition & 0 deletions docs/pages/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Reference
function
types
handler
tips
53 changes: 53 additions & 0 deletions docs/pages/reference/tips.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
Tips
------------

Sanitize dot-files or dot-directories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When you process filenames or filepaths containing ``.`` or ``..`` with the ``sanitize_filename`` function or the ``sanitize_filepath`` function, by default, ``sanitize_filename`` does nothing, and ``sanitize_filepath`` normalizes the filepaths:

.. code-block:: python
print(sanitize_filename("."))
print(sanitize_filepath("hoge/./foo"))
.. code-block:: console
.
hoge/foo
If you would like to replace ``.`` and ``..`` like other reserved words, you need to specify the arguments as follows:

.. code-block:: python
from pathvalidate import sanitize_filepath, sanitize_filename
from pathvalidate.error import ValidationError
def always_add_trailing_underscore(e: ValidationError) -> str:
if e.reusable_name:
return e.reserved_name
return f"{e.reserved_name}_"
print(
sanitize_filename(
".",
reserved_name_handler=always_add_trailing_underscore,
additional_reserved_names=[".", ".."],
)
)
print(
sanitize_filepath(
"hoge/./foo",
normalize=False,
reserved_name_handler=always_add_trailing_underscore,
additional_reserved_names=[".", ".."],
)
)
.. code-block:: console
._
hoge/._/foo
30 changes: 18 additions & 12 deletions pathvalidate/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def is_valid(self, value: PathType) -> bool:
return True

def _is_reserved_keyword(self, value: str) -> bool:
return value in self.reserved_keywords
return value.upper() in self.reserved_keywords


class AbstractSanitizer(BaseFile, metaclass=abc.ABCMeta):
Expand Down Expand Up @@ -183,6 +183,7 @@ def sanitize(self, value: PathType, replacement_text: str = "") -> PathType: #

class BaseValidator(AbstractValidator):
__RE_ROOT_NAME: Final = re.compile(r"([^\.]+)")
__RE_REPEAD_DOT: Final = re.compile(r"^\.{3,}")

@property
def min_len(self) -> int:
Expand Down Expand Up @@ -218,17 +219,16 @@ def _validate_reserved_keywords(self, name: str) -> None:
return

root_name = self.__extract_root_name(name)
base_name = os.path.basename(name).upper()

if self._is_reserved_keyword(root_name.upper()) or self._is_reserved_keyword(
base_name.upper()
):
raise ReservedNameError(
f"'{root_name}' is a reserved name",
reusable_name=False,
reserved_name=root_name,
platform=self.platform,
)
base_name = os.path.basename(name)

for name in (root_name, base_name):
if self._is_reserved_keyword(name):
raise ReservedNameError(
f"'{root_name}' is a reserved name",
reusable_name=False,
reserved_name=root_name,
platform=self.platform,
)

def _validate_max_len(self) -> None:
if self.max_len < 1:
Expand All @@ -239,6 +239,12 @@ def _validate_max_len(self) -> None:

@classmethod
def __extract_root_name(cls, path: str) -> str:
if path in (".", ".."):
return path

if cls.__RE_REPEAD_DOT.search(path):
return path

match = cls.__RE_ROOT_NAME.match(os.path.basename(path))
if match is None:
return ""
Expand Down
8 changes: 4 additions & 4 deletions pathvalidate/_filename.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ def validate_filename(
Defaults to ``255``.
fs_encoding:
Filesystem encoding that used to calculate the byte length of the filename.
If |None|, get the value from the execution environment.
Filesystem encoding that is used to calculate the byte length of the filename.
If |None|, get the encoding from the execution environment.
check_reserved:
If |True|, check the reserved names of the ``platform``.
additional_reserved_names:
Expand Down Expand Up @@ -414,8 +414,8 @@ def sanitize_filename(
Truncate the name length if the ``filename`` length exceeds this value.
Defaults to ``255``.
fs_encoding:
Filesystem encoding that used to calculate the byte length of the filename.
If |None|, get the value from the execution environment.
Filesystem encoding that is used to calculate the byte length of the filename.
If |None|, get the encoding from the execution environment.
check_reserved:
[Deprecated] Use 'reserved_name_handler' instead.
null_value_handler:
Expand Down
8 changes: 4 additions & 4 deletions pathvalidate/_filepath.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ def validate_filepath(
- ``Windows``: 260
- ``universal``: 260
fs_encoding (Optional[str], optional):
Filesystem encoding that used to calculate the byte length of the file path.
If |None|, get the value from the execution environment.
Filesystem encoding that is used to calculate the byte length of the file path.
If |None|, get the encoding from the execution environment.
check_reserved (bool, optional):
If |True|, check the reserved names of the ``platform``.
Defaults to |True|.
Expand Down Expand Up @@ -455,8 +455,8 @@ def sanitize_filepath(
- ``Windows``: 260
- ``universal``: 260
fs_encoding:
Filesystem encoding that used to calculate the byte length of the file path.
If |None|, get the value from the execution environment.
Filesystem encoding that is used to calculate the byte length of the file path.
If |None|, get the encoding from the execution environment.
check_reserved:
[Deprecated] Use 'reserved_name_handler' instead.
null_value_handler:
Expand Down
34 changes: 33 additions & 1 deletion test/test_filename.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ def test_reserved_name(self, value, platform, expected):
["Abc", ["abc"], False],
["ABC", ["abc"], False],
["abc.txt", ["abc.txt"], False],
[".", [".", ".."], False],
["..", [".", ".."], False],
],
)
def test_normal_additional_reserved_names(self, value, arn, expected):
Expand Down Expand Up @@ -654,6 +656,35 @@ def test_normal_reserved_name_handler(self, value, reserved_name_handler, expect
== expected
)

@pytest.mark.parametrize(
["value", "test_platform", "arn", "expected"],
[
[".", "windows", [".", ".."], "._"],
[".", "universal", [".", ".."], "._"],
["..", "windows", [".", ".."], ".._"],
["..", "universal", [".", ".."], ".._"],
["...", "linux", [".", ".."], "..."],
],
)
def test_normal_custom_reserved_name_handler_for_dot_files(
self, value, test_platform, arn, expected
):
def always_add_trailing_underscore(e: ValidationError) -> str:
if e.reusable_name:
return e.reserved_name

return f"{e.reserved_name}_"

assert (
sanitize_filename(
value,
platform=test_platform,
reserved_name_handler=always_add_trailing_underscore,
additional_reserved_names=arn,
)
== expected
)

def test_exception_reserved_name_handler(self):
for platform in ["windows", "universal"]:
with pytest.raises(ValidationError) as e:
Expand All @@ -676,7 +707,7 @@ def test_normal_additional_reserved_names(self, value, arn, expected):
additional_reserved_names=arn,
)
== expected
)
), platform

@pytest.mark.parametrize(
["value", "check_reserved", "expected"],
Expand Down Expand Up @@ -709,6 +740,7 @@ def test_normal_check_reserved(self, value, check_reserved, expected):
["linux", "period.", "period."],
["linux", "space ", "space "],
["linux", "space_and_period. ", "space_and_period. "],
["linux", "...", "..."],
["universal", "period.", "period"],
["universal", "space ", "space"],
["universal", "space_and_period .", "space_and_period"],
Expand Down
28 changes: 28 additions & 0 deletions test/test_filepath.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,9 @@ def test_normal_reserved_name(self, value, test_platform, expected):
["value", "reserved_name_handler", "expected"],
[
["CON", ReservedNameHandler.add_trailing_underscore, "CON_"],
["hoge/CON", ReservedNameHandler.add_trailing_underscore, "hoge\\CON_"],
["CON", ReservedNameHandler.add_leading_underscore, "_CON"],
["hoge/CON", ReservedNameHandler.add_leading_underscore, "hoge\\_CON"],
["CON", ReservedNameHandler.as_is, "CON"],
],
)
Expand All @@ -713,6 +715,32 @@ def test_normal_reserved_name_handler(self, value, reserved_name_handler, expect
== expected
)

@pytest.mark.parametrize(
["value", "expected"],
[
["hoge/.", "hoge\\._"],
["hoge/./foo", "hoge\\._\\foo"],
["hoge/..", "hoge\\.._"],
],
)
def test_normal_custom_reserved_name_handler_for_dot_files(self, value, expected):
def always_add_trailing_underscore(e: ValidationError) -> str:
if e.reusable_name:
return e.reserved_name

return f"{e.reserved_name}_"

assert (
sanitize_filepath(
value,
platform="windows",
reserved_name_handler=always_add_trailing_underscore,
additional_reserved_names=[".", ".."],
normalize=False,
)
== expected
)

def test_exception_reserved_name_handler(self):
for platform in ["windows", "universal"]:
with pytest.raises(ValidationError) as e:
Expand Down

0 comments on commit 377948a

Please sign in to comment.