filenames return a normalized absolutepath copy of the given filename if not relroot or relroot is usecwd return os path abspathfilename if fixroot relroot os path abspathrelroot return fixfilenamefilename relroot def fixfilenamefilename relroot badprefixf os path sep orig filename first we normalize filename os path normpathfilename if filename startswithbadprefix raise valueerrorf bad filename orig r resolves beyond relative root now make sure it is absolute relative to relroot if not os path isabsfilename filename os path joinrelroot filename else relpath os path relpathfilename relroot if os path joinrelroot relpath filename raise valueerrorf expected relroot r as lroot got orig r return filename def fixfilenamesfilenames relrootusecwd if not relroot or relroot is usecwd filenames os path abspathv for v in filenames else relroot os path abspathrelroot filenames fixfilenamev relroot for v in filenames return filenames relroot def formatfilenamefilename relrootusecwd fixroottrue normalizetrue badprefixf os path sep otherwise leave it asis make it relative to cwd make it relative to relroot return true if one path ends the other if path1 path2 return true if os path isabspath1 if os path isabspath2 return false return matchtailpath1 path2 elif os path isabspath2 return matchtailpath2 path1 else return matchtailpath1 path2 or matchtailpath2 path1 def matchtailpath tail assert not os path isabstail reprtail return path endswithos path sep tail find files def matchglobfilename pattern if fnmatch fnmatchfilename pattern return true fnmatch doesn t handle quite right it will not match the following xspam py x py spam py py though it will match the following xyspam py x py xspam py py if not in pattern return false we only accommodate the single case return fnmatch fnmatchfilename pattern replace 1 def processfilenamesfilenames startnone includenone excludenone relrootusecwd if relroot and relroot is not usecwd relroot os path abspathrelroot if start start fixfilenamestart relroot fixrootfalse if include include setfixfilenamev relroot fixrootfalse for v in include if exclude exclude setfixfilenamev relroot fixrootfalse for v in exclude onempty exception no filenames provided for filename solo in itermanyfilenames onempty filename fixfilenamefilename relroot fixrootfalse relfile formatfilenamefilename relroot fixrootfalse normalizefalse check start getcheckfilename start include exclude yield filename relfile check solo def expandfilenamesfilenames for filename in filenames xxx do we need to use glob escape a la commit 9355868458 gh20994 if in filename yield from glob globfilename replace yield from glob globfilename def getcheckfilename start include exclude if start and filename start return lambda skipped start else def check if isexcludedfilename exclude include return excluded return none return check none def isexcludedfilename exclude include if include for included in include if matchglobfilename included return false return true elif exclude for excluded in exclude if matchglobfilename excluded return true return false else return false def walktreeroot walkos walk a wrapper around os walk that resolves the filenames for parent names in walkroot for name in names yield os path joinparent name def walktreeroot suffixnone walkwalktree if suffix and not isinstancesuffix str raise valueerror suffix must be a string for filename in walkroot if suffix and not filename endswithsuffix continue yield filename def globtreeroot suffixnone globglob iglob suffix suffix or if not isinstancesuffix str raise valueerror suffix must be a string for filename in globf rootsuffix yield filename for filename in globf rootsuffix yield filename def iterfilesroot suffixnone relparentnone getfilesos walk globglobtree walkwalktree if not isinstanceroot str roots root for root in roots yield from iterfilesroot suffix relparent getfilesgetfiles globglob walkwalk return use the right walk function if getfiles in glob glob glob iglob globtree getfiles glob else files walktree if getfiles in os walk walktree else getfiles getfiles lambda a k walka walkfiles k handle a single suffix if suffix and not isinstancesuffix str filenames getfilesroot suffix tuplesuffix else filenames getfilesroot suffixsuffix suffix none for filename in filenames if suffix and not isinstancesuffix str multiple suffixes if not filename endswithsuffix continue if relparent filename os path relpathfilename relparent yield filename def iterfilesbysuffixroot suffixes relparentnone walkwalktree iterfilesiterfiles if isinstancesuffixes str suffixes suffixes xxx ignore repeated suffixes for suffix in suffixes yield from iterfilesroot suffix relparent file info xxx posixonly sirany stat sirusr stat sirgrp stat siroth siwany stat siwusr stat siwgrp stat siwoth sixany stat sixusr stat sixgrp stat sixoth def isreadablefile usernone checkfalse filename st mode getfileinfofile if check try okay checkfilefilename sirany except notimplementederror okay notimplemented if okay is not notimplemented return okay fall back to checking the mode return checkmodest mode sirany user def iswritablefile usernone checkfalse filename st mode getfileinfofile if check try okay checkfilefilename siwany except notimplementederror okay notimplemented if okay is not notimplemented return okay fall back to checking the mode return checkmodest mode siwany user def isexecutablefile usernone checkfalse filename st mode getfileinfofile if check try okay checkfilefilename sixany except notimplementederror okay notimplemented if okay is not notimplemented return okay fall back to checking the mode return checkmodest mode sixany user def getfileinfofile filename st mode none if isinstancefile int mode file elif isinstancefile os statresult st file else if isinstancefile str filename file elif hasattrfile name and os path existsfile name filename file name else raise notimplementederrorfile st os statfilename return filename st mode or st stmode def checkfilefilename check if not isinstancefilename str raise exceptionf filename required to check file got filename if check sirany flags os ordonly elif check siwany flags os owronly elif check sixany we can worry about sixany later return notimplemented else raise notimplementederrorcheck try fd os openfilename flags except permissionerror return false we do not ignore other exceptions else os closefd return true def getuserinfouser import pwd username uid gid groups none if user is none uid os geteuid username os getlogin username pwd getpwuiduid0 gid os getgid groups os getgroups else if isinstanceuser int uid user entry pwd getpwuiduid username entry pwname elif isinstanceuser str username user entry pwd getpwnamusername uid entry pwuid else raise notimplementederroruser gid entry pwgid os getgrouplistusername gid return username uid gid groups def checkmodest mode check user orig check uid gid groups getuserinfouser if check sirany check sirany matched false if mode stat sirusr if st stuid uid matched true if mode stat sirgrp if st stuid gid or st stuid in groups matched true if mode stat siroth matched true if not matched return false if check siwany check siwany matched false if mode stat siwusr if st stuid uid matched true if mode stat siwgrp if st stuid gid or st stuid in groups matched true if mode stat siwoth matched true if not matched return false if check sixany check sixany matched false if mode stat sixusr if st stuid uid matched true if mode stat sixgrp if st stuid gid or st stuid in groups matched true if mode stat sixoth matched true if not matched return false if check raise notimplementederrororig check return true re raise filenames return a normalized absolute path copy of the given filename first we normalize now make sure it is absolute relative to relroot return a consistent relative path representation of the filename otherwise leave it as is make it relative to cwd make it relative to relroot return true if one path ends the other find files fnmatch doesn t handle quite right it will not match the following x spam py x py spam py py though it will match the following x y spam py x py x spam py py we only accommodate the single case xxx do we need to use glob escape a la commit 9355868458 gh 20994 a wrapper around os walk that resolves the filenames yield each file in the tree under the given directory name if suffix is provided then only files with that suffix will be included yield each file in the tree under the given directory name if suffix is provided then only files with that suffix will be included yield each file in the tree under the given directory name if root is a non string iterable then do the same for each of those trees if suffix is provided then only files with that suffix will be included if relparent is provided then it is used to resolve each filename as a relative path use the right walk function handle a single suffix multiple suffixes yield each file in the tree that has the given suffixes unlike iter_files the results are in the original suffix order xxx ignore repeated suffixes file info xxx posix only fall back to checking the mode fall back to checking the mode fall back to checking the mode we can worry about s_ixany later we do not ignore other exceptions username os getlogin
import fnmatch
import glob
import os
import os.path
import shutil
import stat

from .iterutil import iter_many


USE_CWD = object()


C_SOURCE_SUFFIXES = ('.c', '.h')


def create_backup(old, backup=None):
    if isinstance(old, str):
        filename = old
    else:
        filename = getattr(old, 'name', None)
    if not filename:
        return None
    if not backup or backup is True:
        backup = f'{filename}.bak'
    try:
        shutil.copyfile(filename, backup)
    except FileNotFoundError as exc:
        if exc.filename != filename:
            raise   
        backup = None
    return backup





def fix_filename(filename, relroot=USE_CWD, *,
                 fixroot=True,
                 _badprefix=f'..{os.path.sep}',
                 ):
    
    if not relroot or relroot is USE_CWD:
        return os.path.abspath(filename)
    if fixroot:
        relroot = os.path.abspath(relroot)
    return _fix_filename(filename, relroot)


def _fix_filename(filename, relroot, *,
                  _badprefix=f'..{os.path.sep}',
                  ):
    orig = filename

    
    filename = os.path.normpath(filename)
    if filename.startswith(_badprefix):
        raise ValueError(f'bad filename {orig!r} (resolves beyond relative root')

    
    if not os.path.isabs(filename):
        filename = os.path.join(relroot, filename)
    else:
        relpath = os.path.relpath(filename, relroot)
        if os.path.join(relroot, relpath) != filename:
            raise ValueError(f'expected {relroot!r} as lroot, got {orig!r}')

    return filename


def fix_filenames(filenames, relroot=USE_CWD):
    if not relroot or relroot is USE_CWD:
        filenames = (os.path.abspath(v) for v in filenames)
    else:
        relroot = os.path.abspath(relroot)
        filenames = (_fix_filename(v, relroot) for v in filenames)
    return filenames, relroot


def format_filename(filename, relroot=USE_CWD, *,
                    fixroot=True,
                    normalize=True,
                    _badprefix=f'..{os.path.sep}',
                    ):
    
    orig = filename
    if normalize:
        filename = os.path.normpath(filename)
    if relroot is None:
        
        return filename
    elif relroot is USE_CWD:
        
        filename = os.path.relpath(filename)
    else:
        
        if fixroot:
            relroot = os.path.abspath(relroot)
        elif not relroot:
            raise ValueError('missing relroot')
        filename = os.path.relpath(filename, relroot)
    if filename.startswith(_badprefix):
        raise ValueError(f'bad filename {orig!r} (resolves beyond relative root')
    return filename


def match_path_tail(path1, path2):
    
    if path1 == path2:
        return True
    if os.path.isabs(path1):
        if os.path.isabs(path2):
            return False
        return _match_tail(path1, path2)
    elif os.path.isabs(path2):
        return _match_tail(path2, path1)
    else:
        return _match_tail(path1, path2) or _match_tail(path2, path1)


def _match_tail(path, tail):
    assert not os.path.isabs(tail), repr(tail)
    return path.endswith(os.path.sep + tail)





def match_glob(filename, pattern):
    if fnmatch.fnmatch(filename, pattern):
        return True

    
    
    
    
    
    
    
    
    
    

    if '**/' not in pattern:
        return False

    
    return fnmatch.fnmatch(filename, pattern.replace('**/', '', 1))


def process_filenames(filenames, *,
                      start=None,
                      include=None,
                      exclude=None,
                      relroot=USE_CWD,
                      ):
    if relroot and relroot is not USE_CWD:
        relroot = os.path.abspath(relroot)
    if start:
        start = fix_filename(start, relroot, fixroot=False)
    if include:
        include = set(fix_filename(v, relroot, fixroot=False)
                      for v in include)
    if exclude:
        exclude = set(fix_filename(v, relroot, fixroot=False)
                      for v in exclude)

    onempty = Exception('no filenames provided')
    for filename, solo in iter_many(filenames, onempty):
        filename = fix_filename(filename, relroot, fixroot=False)
        relfile = format_filename(filename, relroot, fixroot=False, normalize=False)
        check, start = _get_check(filename, start, include, exclude)
        yield filename, relfile, check, solo


def expand_filenames(filenames):
    for filename in filenames:
        
        if '**/' in filename:
            yield from glob.glob(filename.replace('**/', ''))
        yield from glob.glob(filename)


def _get_check(filename, start, include, exclude):
    if start and filename != start:
        return (lambda: '<skipped>'), start
    else:
        def check():
            if _is_excluded(filename, exclude, include):
                return '<excluded>'
            return None
        return check, None


def _is_excluded(filename, exclude, include):
    if include:
        for included in include:
            if match_glob(filename, included):
                return False
        return True
    elif exclude:
        for excluded in exclude:
            if match_glob(filename, excluded):
                return True
        return False
    else:
        return False


def _walk_tree(root, *,
               _walk=os.walk,
               ):
    
    for parent, _, names in _walk(root):
        for name in names:
            yield os.path.join(parent, name)


def walk_tree(root, *,
              suffix=None,
              walk=_walk_tree,
              ):
    
    if suffix and not isinstance(suffix, str):
        raise ValueError('suffix must be a string')

    for filename in walk(root):
        if suffix and not filename.endswith(suffix):
            continue
        yield filename


def glob_tree(root, *,
              suffix=None,
              _glob=glob.iglob,
              ):
    
    suffix = suffix or ''
    if not isinstance(suffix, str):
        raise ValueError('suffix must be a string')

    for filename in _glob(f'{root}/*{suffix}'):
        yield filename
    for filename in _glob(f'{root}/**/*{suffix}'):
        yield filename


def iter_files(root, suffix=None, relparent=None, *,
               get_files=os.walk,
               _glob=glob_tree,
               _walk=walk_tree,
               ):
    
    if not isinstance(root, str):
        roots = root
        for root in roots:
            yield from iter_files(root, suffix, relparent,
                                  get_files=get_files,
                                  _glob=_glob, _walk=_walk)
        return

    
    if get_files in (glob.glob, glob.iglob, glob_tree):
        get_files = _glob
    else:
        _files = _walk_tree if get_files in (os.walk, walk_tree) else get_files
        get_files = (lambda *a, **k: _walk(*a, walk=_files, **k))

    
    if suffix and not isinstance(suffix, str):
        filenames = get_files(root)
        suffix = tuple(suffix)
    else:
        filenames = get_files(root, suffix=suffix)
        suffix = None

    for filename in filenames:
        if suffix and not isinstance(suffix, str):  
            if not filename.endswith(suffix):
                continue
        if relparent:
            filename = os.path.relpath(filename, relparent)
        yield filename


def iter_files_by_suffix(root, suffixes, relparent=None, *,
                         walk=walk_tree,
                         _iter_files=iter_files,
                         ):
    
    if isinstance(suffixes, str):
        suffixes = [suffixes]
    
    for suffix in suffixes:
        yield from _iter_files(root, suffix, relparent)







S_IRANY = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
S_IWANY = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH
S_IXANY = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH


def is_readable(file, *, user=None, check=False):
    filename, st, mode = _get_file_info(file)
    if check:
        try:
            okay = _check_file(filename, S_IRANY)
        except NotImplementedError:
            okay = NotImplemented
        if okay is not NotImplemented:
            return okay
        
    return _check_mode(st, mode, S_IRANY, user)


def is_writable(file, *, user=None, check=False):
    filename, st, mode = _get_file_info(file)
    if check:
        try:
            okay = _check_file(filename, S_IWANY)
        except NotImplementedError:
            okay = NotImplemented
        if okay is not NotImplemented:
            return okay
        
    return _check_mode(st, mode, S_IWANY, user)


def is_executable(file, *, user=None, check=False):
    filename, st, mode = _get_file_info(file)
    if check:
        try:
            okay = _check_file(filename, S_IXANY)
        except NotImplementedError:
            okay = NotImplemented
        if okay is not NotImplemented:
            return okay
        
    return _check_mode(st, mode, S_IXANY, user)


def _get_file_info(file):
    filename = st = mode = None
    if isinstance(file, int):
        mode = file
    elif isinstance(file, os.stat_result):
        st = file
    else:
        if isinstance(file, str):
            filename = file
        elif hasattr(file, 'name') and os.path.exists(file.name):
            filename = file.name
        else:
            raise NotImplementedError(file)
        st = os.stat(filename)
    return filename, st, mode or st.st_mode


def _check_file(filename, check):
    if not isinstance(filename, str):
        raise Exception(f'filename required to check file, got {filename}')
    if check & S_IRANY:
        flags = os.O_RDONLY
    elif check & S_IWANY:
        flags = os.O_WRONLY
    elif check & S_IXANY:
        
        return NotImplemented
    else:
        raise NotImplementedError(check)

    try:
        fd = os.open(filename, flags)
    except PermissionError:
        return False
    
    else:
        os.close(fd)
        return True


def _get_user_info(user):
    import pwd
    username = uid = gid = groups = None
    if user is None:
        uid = os.geteuid()
        
        username = pwd.getpwuid(uid)[0]
        gid = os.getgid()
        groups = os.getgroups()
    else:
        if isinstance(user, int):
            uid = user
            entry = pwd.getpwuid(uid)
            username = entry.pw_name
        elif isinstance(user, str):
            username = user
            entry = pwd.getpwnam(username)
            uid = entry.pw_uid
        else:
            raise NotImplementedError(user)
        gid = entry.pw_gid
        os.getgrouplist(username, gid)
    return username, uid, gid, groups


def _check_mode(st, mode, check, user):
    orig = check
    _, uid, gid, groups = _get_user_info(user)
    if check & S_IRANY:
        check -= S_IRANY
        matched = False
        if mode & stat.S_IRUSR:
            if st.st_uid == uid:
                matched = True
        if mode & stat.S_IRGRP:
            if st.st_uid == gid or st.st_uid in groups:
                matched = True
        if mode & stat.S_IROTH:
            matched = True
        if not matched:
            return False
    if check & S_IWANY:
        check -= S_IWANY
        matched = False
        if mode & stat.S_IWUSR:
            if st.st_uid == uid:
                matched = True
        if mode & stat.S_IWGRP:
            if st.st_uid == gid or st.st_uid in groups:
                matched = True
        if mode & stat.S_IWOTH:
            matched = True
        if not matched:
            return False
    if check & S_IXANY:
        check -= S_IXANY
        matched = False
        if mode & stat.S_IXUSR:
            if st.st_uid == uid:
                matched = True
        if mode & stat.S_IXGRP:
            if st.st_uid == gid or st.st_uid in groups:
                matched = True
        if mode & stat.S_IXOTH:
            matched = True
        if not matched:
            return False
    if check:
        raise NotImplementedError((orig, check))
    return True