From 1348e64f69d7fb220df2a8b70538701c5b9f67ed Mon Sep 17 00:00:00 2001 From: Kunal Tyagi <2657068+kunaltyagi@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:11:29 +0900 Subject: [PATCH] Add custom code to expand file pattern functionality (#76) --- nsiqcppstyle_exe.py | 56 +++++++++++++++++++++++++++++++++++---- tests/test_file_filter.py | 5 ---- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/nsiqcppstyle_exe.py b/nsiqcppstyle_exe.py index 0993e4f..a51e75f 100755 --- a/nsiqcppstyle_exe.py +++ b/nsiqcppstyle_exe.py @@ -471,6 +471,42 @@ def GetFilterFile(self, filterfile): # - Represent each Filter # - Check if the file is included or not ############################################################################## +def recursive_star_replace(string): + recursive_star = re.compile(rf"(.*)\*\*{os.sep}(.*)") # search for **/ + split = recursive_star.split(string) + if len(split) != 1: + _empty, remainder, post_star, _empty2 = split + top_level = remainder == "" + if not (top_level or remainder.endswith(os.sep)): + msg = f"Except the beginning, the pattern (**) must be sandwitched between directory separator ({os.sep})" + raise ValueError(msg) + post_star = post_star.removeprefix(os.sep) # ** = recursive, including no subdirectories + if not top_level: + pre_star = recursive_star_replace(remainder) + pre_star = pre_star.removesuffix(os.sep) + string = rf"{pre_star}({os.sep}.*{os.sep}|{os.sep}){post_star}" + else: + string = rf"(.*{os.sep}|){post_star}" + return string + + +def single_star_replace(string): + single_star = re.compile(r"([^*]+)\*([^*]+)") + split = single_star.split(string) + if len(split) != 1: + start, pre_star, post_star, remainder = split + post_star_full = single_star_replace(post_star + remainder) if remainder != "" else post_star + string = rf"{start}{pre_star}[^{os.sep}]*{post_star_full}" + return string + + +def generate_regex(string): + string = string.replace(".", "\\.") # escape extension + single_star_start = re.compile(r"^\*([^*].*)") + string = single_star_replace(string) + if match_item := single_star_start.match(string): + string = rf"[^{os.sep}]*".join([""] + list(match_item.groups())) + return recursive_star_replace(string) class FileFilter: @@ -478,13 +514,20 @@ def __init__(self, filter_string: str, include: bool = True): self.include = include self.filter_string = filter_string - if sys.version_info >= (3, 13) and "*" in self.filter_string: - from glob import translate # type: ignore[attr-defined] # sys version already checked - - regex = translate(self.filter_string, recursive=True, include_hidden=True) - else: + if "*" not in self.filter_string: # prefix search capability only regex = rf"^{re.escape(filter_string)}" + else: + console.Out.Info( + "Found '*', using new pattern matching method. Please use Python 3.13 if you have * in your filename", + ) + if sys.version_info >= (3, 13): + from glob import translate # type: ignore[attr-defined] # sys version already checked + + regex = translate(self.filter_string, recursive=True, include_hidden=True) + else: + # use custom technique, might have bugs + regex = generate_regex(self.filter_string) self.filter_regex: re.Pattern = re.compile(regex) def __hash__(self): @@ -526,6 +569,9 @@ def to_string(self): s = s + (f" {count+1}. {eachfilter}\n") return template % (self.filterName, s, self.GetLangString()) + def __repr__(self): + return self.to_string() + def NormalizePath(self, filter_string): replacedpath = filter_string.replace("/", os.path.sep) replacedpath = replacedpath.replace("\\\\", os.path.sep) diff --git a/tests/test_file_filter.py b/tests/test_file_filter.py index 1d65fec..8b61080 100644 --- a/tests/test_file_filter.py +++ b/tests/test_file_filter.py @@ -1,5 +1,3 @@ -import sys - import pytest from nsiqcppstyle_exe import Filter @@ -10,7 +8,6 @@ def simple_filter(): return Filter("simple_test", {}, {}) -@pytest.mark.skipif(sys.version_info < (3, 13), reason="Needs new glob features from 3.13") def test_non_recursive_exclude(simple_filter: Filter): simple_filter.AddExclude("*.py") assert not simple_filter.CheckFileInclusion("hello.py") @@ -22,7 +19,6 @@ def test_non_recursive_exclude(simple_filter: Filter): assert simple_filter.CheckFileInclusion("ParentDir/SomeDir/hello.py") -@pytest.mark.skipif(sys.version_info < (3, 13), reason="Needs new glob features from 3.13") def test_recursive_exclude(simple_filter: Filter): simple_filter.AddExclude("**/*.py") assert not simple_filter.CheckFileInclusion("hello.py") @@ -30,7 +26,6 @@ def test_recursive_exclude(simple_filter: Filter): assert not simple_filter.CheckFileInclusion("ParentDir/SomeDir/hello.py") -@pytest.mark.skipif(sys.version_info < (3, 13), reason="Needs new glob features from 3.13") def test_mix_exclude(simple_filter: Filter): simple_filter.AddExclude("test/**/*.py") assert simple_filter.CheckFileInclusion("hello.py")