#!/usr/bin/env python3 """ Traversing directories and archives with AVFS/fuse """ import os import os.path import subprocess import stat avfs_sys_mount = os.environ['HOME'] + '/.avfs' avfs_started = False def sys_name(fpath): global avfs_started if not avfs_started: subprocess.check_call(['mountavfs']) avfs_started = True return avfs_sys_mount + os.path.abspath(fpath) def exists(fpath): return os.path.exists(sys_name(fpath)) def isdir(fpath): return os.path.isdir(sys_name(fpath)) orig_open = open def open(fpath, *pargs, **kwargs): return orig_open(sys_name(fpath), *pargs, **kwargs) # AVFS has its own automatic view selection using file extensions, but it # includes plugins (like #patch) that will lead us into an infinite loop # if we try to do a directory traversal. Also, there are a few # extensions we want to add. avfscmds = { ('.gz', '#ugz'), ('.tgz', '#ugz#utar'), ('.tar.bz2', '#ubz2#utar'), ('.bz2', '#ubz2'), ('.bz', '#ubz2'), ('.tbz2', '#ubz2#utar'), ('.tbz', '#ubz2#utar'), ('.Z', '#uz'), ('.tpz', '#uz#utar'), ('.tz', '#uz#utar'), ('.taz', '#uz#utar'), ('.a', '#uar'), ('.deb', '#uar'), ('.tar', '#utar'), ('.gem', '#utar'), # Add upstream ('.rar', '#urar'), ('.sfx', '#urar'), ('.zip', '#uzip'), ('.jar', '#uzip'), ('.ear', '#uzip'), ('.war', '#uzip'), ('.nupkg', '#uzip'), # Add upstream ('.whl', '#uzip'), # Add upstream ('.7z', '#u7z'), ('.zoo', '#uzoo'), ('.lha', '#ulha'), ('.lhz', '#ulha'), ('.arj', '#uarj'), ('.cpio', '#ucpio'), ('.rpm', '#rpm'), ('.tar.xz', '#uxze#utar'), ('.txz', '#uxze#utar'), ('.xz', '#uxze'), ('.lzma', '#uxze'), } def guesscmd(filename): for ext, cmd in avfscmds: if filename.endswith(ext): return cmd + guesscmd(filename[:-len(ext)]) return '' def get_lstat_mode(filename): """ Get the st_mode stat value of the given file, or None if the stat fails (doesn't exist, I/O error) """ try: return os.lstat(filename).st_mode except: return None def find(path, excludes): """ Recursively list all files under path, including files in archives supported by AVFS. """ sys_path = sys_name(path) name = os.path.basename(path) mode = get_lstat_mode(sys_path) if name in excludes: return # If AVFS provides a cooked view of the file, substitute the view # for the original file. if stat.S_ISREG(mode): cmd = guesscmd(name) if cmd: cmd_mode = get_lstat_mode(sys_path + cmd) if cmd_mode is not None: path = path + cmd sys_path = sys_path + cmd name = name + cmd mode = cmd_mode if stat.S_ISDIR(mode): for entry in os.listdir(sys_path): yield from find(path + '/' + entry, excludes) elif stat.S_ISREG(mode): yield path if __name__ == "__main__": import sys for f in find(sys.argv[1], {'.git'}): print(f)