-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Marcelo Trylesinski <[email protected]>
- Loading branch information
Showing
5 changed files
with
136 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
from bump_pydantic.main import app | ||
from bump_pydantic.main import entrypoint | ||
|
||
if __name__ == "__main__": | ||
app() | ||
entrypoint() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import fnmatch | ||
import re | ||
from pathlib import Path | ||
from typing import List | ||
|
||
MATCH_SEP = r"(?:/|\\)" | ||
MATCH_SEP_OR_END = r"(?:/|\\|\Z)" | ||
MATCH_NON_RECURSIVE = r"[^/\\]*" | ||
MATCH_RECURSIVE = r"(?:.*)" | ||
|
||
|
||
def glob_to_re(pattern: str) -> str: | ||
"""Translate a glob pattern to a regular expression for matching.""" | ||
fragments: List[str] = [] | ||
for segment in re.split(r"/|\\", pattern): | ||
if segment == "": | ||
continue | ||
if segment == "**": | ||
# Remove previous separator match, so the recursive match can match zero or more segments. | ||
if fragments and fragments[-1] == MATCH_SEP: | ||
fragments.pop() | ||
fragments.append(MATCH_RECURSIVE) | ||
elif "**" in segment: | ||
raise ValueError("invalid pattern: '**' can only be an entire path component") | ||
else: | ||
fragment = fnmatch.translate(segment) | ||
fragment = fragment.replace(r"(?s:", r"(?:") | ||
fragment = fragment.replace(r".*", MATCH_NON_RECURSIVE) | ||
fragment = fragment.replace(r"\Z", r"") | ||
fragments.append(fragment) | ||
fragments.append(MATCH_SEP) | ||
# Remove trailing MATCH_SEP, so it can be replaced with MATCH_SEP_OR_END. | ||
if fragments and fragments[-1] == MATCH_SEP: | ||
fragments.pop() | ||
fragments.append(MATCH_SEP_OR_END) | ||
return rf"(?s:{''.join(fragments)})" | ||
|
||
|
||
def match_glob(path: Path, pattern: str) -> bool: | ||
"""Check if a path matches a glob pattern. | ||
If the pattern ends with a directory separator, the path must be a directory. | ||
""" | ||
match = bool(re.fullmatch(glob_to_re(pattern), str(path))) | ||
if pattern.endswith("/") or pattern.endswith("\\"): | ||
return match and path.is_dir() | ||
return match |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from __future__ import annotations | ||
|
||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
from bump_pydantic.glob_helpers import glob_to_re, match_glob | ||
|
||
|
||
class TestGlobHelpers: | ||
match_glob_values: list[tuple[str, Path, bool]] = [ | ||
("foo", Path("foo"), True), | ||
("foo", Path("bar"), False), | ||
("foo", Path("foo/bar"), False), | ||
("*", Path("foo"), True), | ||
("*", Path("bar"), True), | ||
("*", Path("foo/bar"), False), | ||
("**", Path("foo"), True), | ||
("**", Path("foo/bar"), True), | ||
("**", Path("foo/bar/baz/qux"), True), | ||
("foo/bar", Path("foo/bar"), True), | ||
("foo/bar", Path("foo"), False), | ||
("foo/bar", Path("far"), False), | ||
("foo/bar", Path("foo/foo"), False), | ||
("foo/*", Path("foo/bar"), True), | ||
("foo/*", Path("foo/bar/baz"), False), | ||
("foo/*", Path("foo"), False), | ||
("foo/*", Path("bar"), False), | ||
("foo/**", Path("foo/bar"), True), | ||
("foo/**", Path("foo/bar/baz"), True), | ||
("foo/**", Path("foo/bar/baz/qux"), True), | ||
("foo/**", Path("foo"), True), | ||
("foo/**", Path("bar"), False), | ||
("foo/**/bar", Path("foo/bar"), True), | ||
("foo/**/bar", Path("foo/baz/bar"), True), | ||
("foo/**/bar", Path("foo/baz/qux/bar"), True), | ||
("foo/**/bar", Path("foo/baz/qux"), False), | ||
("foo/**/bar", Path("foo/bar/baz"), False), | ||
("foo/**/bar", Path("foo/bar/bar"), True), | ||
("foo/**/bar", Path("foo"), False), | ||
("foo/**/bar", Path("bar"), False), | ||
("foo/**/*/bar", Path("foo/bar"), False), | ||
("foo/**/*/bar", Path("foo/baz/bar"), True), | ||
("foo/**/*/bar", Path("foo/baz/qux/bar"), True), | ||
("foo/**/*/bar", Path("foo/baz/qux"), False), | ||
("foo/**/*/bar", Path("foo/bar/baz"), False), | ||
("foo/**/*/bar", Path("foo/bar/bar"), True), | ||
("foo/**/*/bar", Path("foo"), False), | ||
("foo/**/*/bar", Path("bar"), False), | ||
("foo/ba*", Path("foo/bar"), True), | ||
("foo/ba*", Path("foo/baz"), True), | ||
("foo/ba*", Path("foo/qux"), False), | ||
("foo/ba*", Path("foo/baz/qux"), False), | ||
("foo/ba*", Path("foo/bar/baz"), False), | ||
("foo/ba*", Path("foo"), False), | ||
("foo/ba*", Path("bar"), False), | ||
("foo/**/ba*/*/qux", Path("foo/a/b/c/bar/a/qux"), True), | ||
("foo/**/ba*/*/qux", Path("foo/a/b/c/baz/a/qux"), True), | ||
("foo/**/ba*/*/qux", Path("foo/a/bar/a/qux"), True), | ||
("foo/**/ba*/*/qux", Path("foo/baz/a/qux"), True), | ||
("foo/**/ba*/*/qux", Path("foo/baz/qux"), False), | ||
("foo/**/ba*/*/qux", Path("foo/a/b/c/qux/a/qux"), False), | ||
("foo/**/ba*/*/qux", Path("foo"), False), | ||
("foo/**/ba*/*/qux", Path("bar"), False), | ||
] | ||
|
||
@pytest.mark.parametrize(("pattern", "path", "expected"), match_glob_values) | ||
def test_match_glob(self, pattern: str, path: Path, expected: bool): | ||
expr = glob_to_re(pattern) | ||
assert match_glob(path, pattern) == expected, f"path: {path}, pattern: {pattern}, expr: {expr}" |