# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Ant-style globbing patterns.

Metacharacters * and ? have their usual meaning.

There's no [...] syntax.

A directory name of ** means match the rest at any directory depth.
This does not recurse into symlinks.

XXX There's no way to quote a metacharacter.

XXX We'll think about matching Windows pathname syntax later
(backslash separator, drive letter prefix, and network node prefix) - done

"""

import os
import re
import stat

class AntPattern(object):

    def __init__(self, pattern):
        self.pattern = pattern
        self.subpatterns = pattern.split(os.sep)

    def glob(self, xdev=False):
        if os.name == "nt":
            if self.subpatterns[0].endswith(":"):
                root = self.subpatterns[0] + os.sep
                subpatterns = self.subpatterns[1:]
            else:
                root = ""
                subpatterns = self.subpatterns
        else:
            if self.pattern.startswith("/"):
                root = "/"
                subpatterns = self.subpatterns[1:]
            else:
                root = ""
                subpatterns = self.subpatterns
        return self._glob([root], subpatterns, xdev)

    def _glob(self, matches, subpatterns, xdev):
        if not matches:
            return []
        if not subpatterns:
            return matches
        subp, rest = subpatterns[0], subpatterns[1:]
        if subp == "**":
            results = self._glob(matches, rest, xdev)
            subdirs = []
            for match in matches:
                path = match or os.curdir
                try:
                    st = os.lstat(path)
                except os.error:
                    continue
                if stat.S_ISDIR(st.st_mode):
                    try:
                        names = os.listdir(path)
                    except os.error:
                        continue
                    newsubdirs = [os.path.join(match, name) for name in names]
                    if not xdev:
                        subdirs += newsubdirs
                    else:
                        parent_st_dev = st.st_dev
                        for x in newsubdirs:
                            try:
                                st = os.lstat(x)
                            except os.error:
                                continue
                            if st.st_dev == parent_st_dev:
                                subdirs.append(x)
            results += self._glob(subdirs, subpatterns, xdev)
            return results
        elif "*" in subp or "?" in subp:
            if os.name == "nt":
                regex = self.translate_win(subp)
            else:
                regex = self.translate(subp)
            results = []
            for match in matches:
                if (match == "" or
                    os.path.isdir(match) and not os.path.islink(match)):
                    try:
                        names = os.listdir(match or os.curdir)
                    except os.error:
                        continue
                    for name in names:
                        if re.match(regex, name):
                            results.append(os.path.join(match, name))
            return self._glob(results, rest, xdev)
        else:
            results = []
            for match in matches:
                name = os.path.join(match, subp)
                if os.path.exists(name):
                    results.append(name)
            return self._glob(results, rest, xdev)
    
    def match(self, path):
        parts = path.split(os.sep)
        return self._match(self.subpatterns, parts)
    
    def _match(self, subpatterns, parts):
        if not subpatterns:
            return not parts
        subp = subpatterns[0]
        rest = subpatterns[1:]
        if subp == "**":
            while True:
                if self._match(rest, parts):
                    return True
                if not parts:
                    break
                parts = parts[1:]
            return False
        elif not parts:
            return False
        elif "*" in subp or "?" in subp:
            if os.name == "nt":
                regex = self.translate_win(subp)
            else:
                regex = self.translate(subp)
            if not re.match(regex, parts[0]):
                return False
            else:
                return self._match(rest, parts[1:])
        else:
            if subp != parts[0]:
                return False
            else:
                return self._match(rest, parts[1:])

    _cache = {}

    def translate(self, pattern=None):
        if pattern is None:
            pattern = self.pattern
        cached = self._cache.get(pattern)
        if cached is not None:
            return cached
        original = pattern
        specialstart = specialend = False
        while pattern.startswith("**/"):
            pattern = pattern[3:]
            specialstart = True
        while pattern.endswith("/**"):
            pattern = pattern[:-3]
            specialend = True
        if pattern == "**":
            result = ".*"
        else:
            regex = ["^"]
            if specialstart:
                regex.append("([^/]*/)*")
            subpatterns = pattern.split("/**/")
            first = True
            for subp in subpatterns:
                if first:
                    first = False
                else:
                    regex.append("/([^/]*/)*")
                for c in subp:
                    if c == "*":
                        regex.append("[^/]*")
                    elif c == "?":
                        regex.append("[^/]")
                    elif c == "/":
                        regex.append("/")
                    else:
                        # Put a backslash in front of all non-alphanumerics
                        regex.append(re.escape(c))
            if specialend:
                regex.append("(/[^/]*)*")
            regex.append("$")
            result = "".join(regex)
        self._cache[original] = result
        return result

    def translate_win(self, pattern=None):
        if pattern is None:
            pattern = self.pattern
        cached = self._cache.get(pattern)
        if cached is not None:
            return cached
        original = pattern
        specialstart = specialend = False
        while pattern.startswith("**" + os.sep):
            pattern = pattern[3:]
            specialstart = True
        while pattern.endswith(os.sep + "**"):
            pattern = pattern[:-3]
            specialend = True
        if pattern == "**":
            result = ".*"
        else:
            regex = [r"^"]
            if specialstart:
                regex.append(r"([^\\]*\\)*")
            subpatterns = pattern.split("\\**\\")
            first = True
            for subp in subpatterns:
                if first:
                    first = False
                else:
                    regex.append(r"\\([^\\]*\\)*")
                for c in subp:
                    if c == "*":
                        regex.append(r"[^\\]*")
                    elif c == "?":
                        regex.append(r"[^\\]")
                    elif c == "\\":
                        regex.append(r"\\")
                    else:
                        # Put a backslash in front of all non-alphanumerics
                        regex.append(re.escape(c))
            if specialend:
                regex.append(r"(\\[^\\]*)*")
            regex.append("$")
            result = "".join(regex)

        self._cache[original] = result
        return result

def _test():
    if os.name == "nt":
        c = AntPattern("c:\\scripts\\**\\*.vbs")
        hfiles = c.glob()
        d = {}
        for hf in hfiles:
            d[hf] = 1
        assert len(d) == len(hfiles)
        print(len(d))
    else:
        import glob
        a = AntPattern("/etc/**/*.db")
        samples = (glob.glob("/etc/aliases*") + glob.glob("/etc/postfix/*") +
                   glob.glob("/etc/mail/*"))
        for s in samples:
            print("%5s %s" % (a.match(s), s))
        c = AntPattern("/usr/include/**/*.h")
        hfiles = c.glob()
        d = {}
        for hf in hfiles:
            d[hf] = 1
        assert len(d) == len(hfiles)
        print(len(d))

if __name__ == "__main__":
    _test()
