import io
import posixpath
import zipfile
import itertools
import contextlib
import sys
import pathlib
if sys.version_info < (3, 7):
    from collections import OrderedDict
else:
    OrderedDict = dict
__all__ = ['Path']
def _parents(path):
    
    return itertools.islice(_ancestry(path), 1, None)
def _ancestry(path):
    
    path = path.rstrip(posixpath.sep)
    while path and path != posixpath.sep:
        yield path
        path, tail = posixpath.split(path)
_dedupe = OrderedDict.fromkeys
def _difference(minuend, subtrahend):
    
    return itertools.filterfalse(set(subtrahend).__contains__, minuend)
class CompleteDirs(zipfile.ZipFile):
    
    @staticmethod
    def _implied_dirs(names):
        parents = itertools.chain.from_iterable(map(_parents, names))
        as_dirs = (p + posixpath.sep for p in parents)
        return _dedupe(_difference(as_dirs, names))
    def namelist(self):
        names = super(CompleteDirs, self).namelist()
        return names + list(self._implied_dirs(names))
    def _name_set(self):
        return set(self.namelist())
    def resolve_dir(self, name):
        
        names = self._name_set()
        dirname = name + '/'
        dir_match = name not in names and dirname in names
        return dirname if dir_match else name
    @classmethod
    def make(cls, source):
        
        if isinstance(source, CompleteDirs):
            return source
        if not isinstance(source, zipfile.ZipFile):
            return cls(_pathlib_compat(source))
                if 'r' not in source.mode:
            cls = CompleteDirs
        source.__class__ = cls
        return source
class FastLookup(CompleteDirs):
    
    def namelist(self):
        with contextlib.suppress(AttributeError):
            return self.__names
        self.__names = super(FastLookup, self).namelist()
        return self.__names
    def _name_set(self):
        with contextlib.suppress(AttributeError):
            return self.__lookup
        self.__lookup = super(FastLookup, self)._name_set()
        return self.__lookup
def _pathlib_compat(path):
    
    try:
        return path.__fspath__()
    except AttributeError:
        return str(path)
class Path:
    
    __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
    def __init__(self, root, at=""):
        
        self.root = FastLookup.make(root)
        self.at = at
    def open(self, mode='r', *args, pwd=None, **kwargs):
        
        if self.is_dir():
            raise IsADirectoryError(self)
        zip_mode = mode[0]
        if not self.exists() and zip_mode == 'r':
            raise FileNotFoundError(self)
        stream = self.root.open(self.at, zip_mode, pwd=pwd)
        if 'b' in mode:
            if args or kwargs:
                raise ValueError("encoding args invalid for binary operation")
            return stream
        return io.TextIOWrapper(stream, *args, **kwargs)
    @property
    def name(self):
        return pathlib.Path(self.at).name or self.filename.name
    @property
    def suffix(self):
        return pathlib.Path(self.at).suffix or self.filename.suffix
    @property
    def suffixes(self):
        return pathlib.Path(self.at).suffixes or self.filename.suffixes
    @property
    def stem(self):
        return pathlib.Path(self.at).stem or self.filename.stem
    @property
    def filename(self):
        return pathlib.Path(self.root.filename).joinpath(self.at)
    def read_text(self, *args, **kwargs):
        with self.open('r', *args, **kwargs) as strm:
            return strm.read()
    def read_bytes(self):
        with self.open('rb') as strm:
            return strm.read()
    def _is_child(self, path):
        return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
    def _next(self, at):
        return self.__class__(self.root, at)
    def is_dir(self):
        return not self.at or self.at.endswith("/")
    def is_file(self):
        return self.exists() and not self.is_dir()
    def exists(self):
        return self.at in self.root._name_set()
    def iterdir(self):
        if not self.is_dir():
            raise ValueError("Can't listdir a file")
        subs = map(self._next, self.root.namelist())
        return filter(self._is_child, subs)
    def __str__(self):
        return posixpath.join(self.root.filename, self.at)
    def __repr__(self):
        return self.__repr.format(self=self)
    def joinpath(self, *other):
        next = posixpath.join(self.at, *map(_pathlib_compat, other))
        return self._next(self.root.resolve_dir(next))
    __truediv__ = joinpath
    @property
    def parent(self):
        if not self.at:
            return self.filename.parent
        parent_at = posixpath.dirname(self.at.rstrip('/'))
        if parent_at:
            parent_at += '/'
        return self._next(parent_at)