Skip to content

Commit 377948a

Browse files
authored
Merge pull request #60 from thombashi/fix_coverage
Fix dot-files validation
2 parents d10313f + fa4e9bd commit 377948a

File tree

11 files changed

+147
-26
lines changed

11 files changed

+147
-26
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131

3232
- uses: actions/setup-python@v5
3333
with:
34-
python-version: "3.12"
34+
python-version: "3.13"
3535
cache: pip
3636
cache-dependency-path: |
3737
setup.py
@@ -62,7 +62,7 @@ jobs:
6262

6363
- uses: actions/setup-python@v5
6464
with:
65-
python-version: "3.12"
65+
python-version: "3.13"
6666
cache: pip
6767
cache-dependency-path: |
6868
setup.py
@@ -88,7 +88,7 @@ jobs:
8888

8989
- uses: actions/setup-python@v5
9090
with:
91-
python-version: "3.12"
91+
python-version: "3.13"
9292
cache: pip
9393
cache-dependency-path: |
9494
setup.py

.github/workflows/on_push_default_branch.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727

2828
- uses: actions/setup-python@v5
2929
with:
30-
python-version: "3.12"
30+
python-version: "3.13"
3131
cache: pip
3232
cache-dependency-path: |
3333
setup.py

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121

2222
- uses: actions/setup-python@v5
2323
with:
24-
python-version: "3.12"
24+
python-version: "3.13"
2525
cache: pip
2626
cache-dependency-path: |
2727
setup.py

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ LAST_UPDATE_YEAR := $(shell git log -1 --format=%cd --date=format:%Y)
99

1010

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

1415
.PHONY: build

docs/pages/reference/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Reference
88
function
99
types
1010
handler
11+
tips

docs/pages/reference/tips.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Tips
2+
------------
3+
4+
Sanitize dot-files or dot-directories
5+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6+
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:
7+
8+
.. code-block:: python
9+
10+
print(sanitize_filename("."))
11+
print(sanitize_filepath("hoge/./foo"))
12+
13+
.. code-block:: console
14+
15+
.
16+
hoge/foo
17+
18+
If you would like to replace ``.`` and ``..`` like other reserved words, you need to specify the arguments as follows:
19+
20+
.. code-block:: python
21+
22+
from pathvalidate import sanitize_filepath, sanitize_filename
23+
from pathvalidate.error import ValidationError
24+
25+
26+
def always_add_trailing_underscore(e: ValidationError) -> str:
27+
if e.reusable_name:
28+
return e.reserved_name
29+
30+
return f"{e.reserved_name}_"
31+
32+
33+
print(
34+
sanitize_filename(
35+
".",
36+
reserved_name_handler=always_add_trailing_underscore,
37+
additional_reserved_names=[".", ".."],
38+
)
39+
)
40+
41+
print(
42+
sanitize_filepath(
43+
"hoge/./foo",
44+
normalize=False,
45+
reserved_name_handler=always_add_trailing_underscore,
46+
additional_reserved_names=[".", ".."],
47+
)
48+
)
49+
50+
.. code-block:: console
51+
52+
._
53+
hoge/._/foo

pathvalidate/_base.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def is_valid(self, value: PathType) -> bool:
140140
return True
141141

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

145145

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

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

187188
@property
188189
def min_len(self) -> int:
@@ -218,17 +219,16 @@ def _validate_reserved_keywords(self, name: str) -> None:
218219
return
219220

220221
root_name = self.__extract_root_name(name)
221-
base_name = os.path.basename(name).upper()
222-
223-
if self._is_reserved_keyword(root_name.upper()) or self._is_reserved_keyword(
224-
base_name.upper()
225-
):
226-
raise ReservedNameError(
227-
f"'{root_name}' is a reserved name",
228-
reusable_name=False,
229-
reserved_name=root_name,
230-
platform=self.platform,
231-
)
222+
base_name = os.path.basename(name)
223+
224+
for name in (root_name, base_name):
225+
if self._is_reserved_keyword(name):
226+
raise ReservedNameError(
227+
f"'{root_name}' is a reserved name",
228+
reusable_name=False,
229+
reserved_name=root_name,
230+
platform=self.platform,
231+
)
232232

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

240240
@classmethod
241241
def __extract_root_name(cls, path: str) -> str:
242+
if path in (".", ".."):
243+
return path
244+
245+
if cls.__RE_REPEAD_DOT.search(path):
246+
return path
247+
242248
match = cls.__RE_ROOT_NAME.match(os.path.basename(path))
243249
if match is None:
244250
return ""

pathvalidate/_filename.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ def validate_filename(
302302
303303
Defaults to ``255``.
304304
fs_encoding:
305-
Filesystem encoding that used to calculate the byte length of the filename.
306-
If |None|, get the value from the execution environment.
305+
Filesystem encoding that is used to calculate the byte length of the filename.
306+
If |None|, get the encoding from the execution environment.
307307
check_reserved:
308308
If |True|, check the reserved names of the ``platform``.
309309
additional_reserved_names:
@@ -414,8 +414,8 @@ def sanitize_filename(
414414
Truncate the name length if the ``filename`` length exceeds this value.
415415
Defaults to ``255``.
416416
fs_encoding:
417-
Filesystem encoding that used to calculate the byte length of the filename.
418-
If |None|, get the value from the execution environment.
417+
Filesystem encoding that is used to calculate the byte length of the filename.
418+
If |None|, get the encoding from the execution environment.
419419
check_reserved:
420420
[Deprecated] Use 'reserved_name_handler' instead.
421421
null_value_handler:

pathvalidate/_filepath.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,8 @@ def validate_filepath(
337337
- ``Windows``: 260
338338
- ``universal``: 260
339339
fs_encoding (Optional[str], optional):
340-
Filesystem encoding that used to calculate the byte length of the file path.
341-
If |None|, get the value from the execution environment.
340+
Filesystem encoding that is used to calculate the byte length of the file path.
341+
If |None|, get the encoding from the execution environment.
342342
check_reserved (bool, optional):
343343
If |True|, check the reserved names of the ``platform``.
344344
Defaults to |True|.
@@ -455,8 +455,8 @@ def sanitize_filepath(
455455
- ``Windows``: 260
456456
- ``universal``: 260
457457
fs_encoding:
458-
Filesystem encoding that used to calculate the byte length of the file path.
459-
If |None|, get the value from the execution environment.
458+
Filesystem encoding that is used to calculate the byte length of the file path.
459+
If |None|, get the encoding from the execution environment.
460460
check_reserved:
461461
[Deprecated] Use 'reserved_name_handler' instead.
462462
null_value_handler:

test/test_filename.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ def test_reserved_name(self, value, platform, expected):
405405
["Abc", ["abc"], False],
406406
["ABC", ["abc"], False],
407407
["abc.txt", ["abc.txt"], False],
408+
[".", [".", ".."], False],
409+
["..", [".", ".."], False],
408410
],
409411
)
410412
def test_normal_additional_reserved_names(self, value, arn, expected):
@@ -654,6 +656,35 @@ def test_normal_reserved_name_handler(self, value, reserved_name_handler, expect
654656
== expected
655657
)
656658

659+
@pytest.mark.parametrize(
660+
["value", "test_platform", "arn", "expected"],
661+
[
662+
[".", "windows", [".", ".."], "._"],
663+
[".", "universal", [".", ".."], "._"],
664+
["..", "windows", [".", ".."], ".._"],
665+
["..", "universal", [".", ".."], ".._"],
666+
["...", "linux", [".", ".."], "..."],
667+
],
668+
)
669+
def test_normal_custom_reserved_name_handler_for_dot_files(
670+
self, value, test_platform, arn, expected
671+
):
672+
def always_add_trailing_underscore(e: ValidationError) -> str:
673+
if e.reusable_name:
674+
return e.reserved_name
675+
676+
return f"{e.reserved_name}_"
677+
678+
assert (
679+
sanitize_filename(
680+
value,
681+
platform=test_platform,
682+
reserved_name_handler=always_add_trailing_underscore,
683+
additional_reserved_names=arn,
684+
)
685+
== expected
686+
)
687+
657688
def test_exception_reserved_name_handler(self):
658689
for platform in ["windows", "universal"]:
659690
with pytest.raises(ValidationError) as e:
@@ -676,7 +707,7 @@ def test_normal_additional_reserved_names(self, value, arn, expected):
676707
additional_reserved_names=arn,
677708
)
678709
== expected
679-
)
710+
), platform
680711

681712
@pytest.mark.parametrize(
682713
["value", "check_reserved", "expected"],
@@ -709,6 +740,7 @@ def test_normal_check_reserved(self, value, check_reserved, expected):
709740
["linux", "period.", "period."],
710741
["linux", "space ", "space "],
711742
["linux", "space_and_period. ", "space_and_period. "],
743+
["linux", "...", "..."],
712744
["universal", "period.", "period"],
713745
["universal", "space ", "space"],
714746
["universal", "space_and_period .", "space_and_period"],

0 commit comments

Comments
 (0)