Example #1
0
 def test__doesnt_exist__0(self):
     bad = ExPath('/uzer/*')
     assert not bad.exists()
     assert not bad.is_dir()
     assert not bad.is_file()
     
     bad2 = ExPath('/*DOESNTEXIST')
     assert not bad2.exists()
     assert not bad2.is_dir()
     assert not bad2.is_file()
Example #2
0
def build_paths(exclude_parent, exclude_paths, ignore_paths) -> List[ExPath]:
    logger.debug(f'exclude_parent:', exclude_parent, 'exclude_paths:',
                 exclude_paths, 'ignore_paths:', ignore_paths)
    statusfiles = None
    paths: List[ExPath] = []
    skip_non_existent = False
    ignore_non_existent = False
    for f in ignore_paths:
        # * wildcard
        if "*" in f:
            paths.append(f)  # TODO: now that ExPath supports glob, ...
            continue

        # * index
        try:
            if not statusfiles:
                statusfiles = Status().files
            f = statusfiles[int(str(f))]
        except IndexError:
            i = input(
                f'Index error with {f} (len of files: {len(statusfiles)}), try again:\t'
            )
            f = statusfiles[int(i)]
        except ValueError:
            pass  # not a number

        path = ExPath(f)

        if not path.exists():
            if skip_non_existent:
                continue
            if not ignore_non_existent:
                key, choice = prompt.action(f'{path} does not exist',
                                            'skip',
                                            'ignore anyway',
                                            sA='skip all',
                                            iA='ignore all')
                if key == 'skip':
                    continue
                if key == 'sA':
                    skip_non_existent = True
                elif key == 'iA':
                    ignore_non_existent = True

                continue

        if path == exclude_parent:
            # path: '.config', exclude_paths: ['.config/dconf']
            for sub in filter(lambda p: p not in exclude_paths,
                              path.iterdir()):
                paths.append(sub)
        else:
            paths.append(path)
    if not paths:
        sys.exit(colors.brightred(f'no paths in {ExPath(".").absolute()}'))
    return paths
Example #3
0
 def backup(self, confirm: bool):
     if confirm and not prompt.confirm(f'Backup .gitignore to .gitignore.backup?'):
         print('aborting')
         return False
     absolute = self.file.absolute()
     
     try:
         shell.run(f'cp {absolute} {absolute}.backup', raiseexc='summary')
     except Exception as e:
         if not prompt.confirm('Backup failed, overwrite .gitignore anyway?', flowopts='debug'):
             print('aborting')
             return False
         return True
     else:
         backup = ExPath(f'{absolute}.backup')
         if not backup.exists() and not prompt.confirm(f'Backup command completed without error, but {backup} doesnt exist. overwrite .gitignore anyway?', flowopts='debug'):
             print('aborting')
             return False
         return True
Example #4
0
class Gitignore:
    # https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#Ignoring-Files
    def __init__(self):
        self.file = ExPath('.gitignore')
        if not self.file.exists():
            raise FileNotFoundError(f'Gitignore.__init__(): {self.file.absolute()} does not exist')
    
    def __getitem__(self, item):
        return self.values[item]
    
    def __contains__(self, item):
        path = ExPath(item)
        for ignored in self:
            if ignored == path:
                return True
        return False
    
    def __getattribute__(self, name: str) -> Any:
        try:
            return super().__getattribute__(name)
        except AttributeError as e:
            # support all ExPath methods
            return getattr(self.file, name)
    
    def __iter__(self):
        yield from self.values
    
    # @cachedprop # TODO: uncomment when possible to clear cache
    @property
    @memoize
    def values(self) -> List[ExPath]:
        with self.file.open(mode='r') as file:
            data = file.read()
        lines = data.splitlines()
        paths = []
        for x in lines:
            if not bool(x) or '#' in x or not WORD_RE.search(x):
                continue
            paths.append(ExPath(x))
        return [*paths, ExPath('.git')]
    
    def should_add_to_gitignore(self, p: ExPath, quiet=False) -> bool:
        for ignored in self.values:
            if ignored == p:
                if not quiet:
                    logging.warning(f'{p} already in gitignore, continuing')
                return False
            if ignored.parent_of(p):
                if quiet:
                    return False
                msg = colors.yellow(f"parent '{ignored}' of '{p}' already in gitignore")
                key, action = prompt.action(msg, 'skip', 'ignore anyway', flowopts=('debug', 'quit'))
                if action.value == 'skip':
                    print('skipping')
                    return False
        return True
    
    def backup(self, confirm: bool):
        if confirm and not prompt.confirm(f'Backup .gitignore to .gitignore.backup?'):
            print('aborting')
            return False
        absolute = self.file.absolute()
        
        try:
            shell.run(f'cp {absolute} {absolute}.backup', raiseexc='summary')
        except Exception as e:
            if not prompt.confirm('Backup failed, overwrite .gitignore anyway?', flowopts='debug'):
                print('aborting')
                return False
            return True
        else:
            backup = ExPath(f'{absolute}.backup')
            if not backup.exists() and not prompt.confirm(f'Backup command completed without error, but {backup} doesnt exist. overwrite .gitignore anyway?', flowopts='debug'):
                print('aborting')
                return False
            return True
    
    def write(self, paths, *, verify_paths=True, confirm=False, dry_run=False, backup=True):
        logging.debug(f'paths:', paths, 'verify_paths:', verify_paths, 'confirm:', confirm, 'dry_run:', dry_run, 'backup:', backup)
        writelines = []
        if verify_paths:
            should_add_to_gitignore = self.should_add_to_gitignore
        else:
            should_add_to_gitignore = lambda _p: True
        for p in filter(should_add_to_gitignore, paths):
            to_write = f'\n{p}'
            if confirm and not prompt.confirm(f'Add {p} to .gitignore?'):
                continue
            logging.info(f'Adding {p} to .gitignore. dry_run={dry_run}, backup={backup}, confirm={confirm}')
            writelines.append(to_write)
        if dry_run:
            print('dry_run, returning')
            return
        if backup:
            backup_ok = self.backup(confirm)
            if not backup_ok:
                return
        
        with self.file.open(mode='a') as file:
            file.write(''.join(sorted(writelines)))
        Gitignore.values.fget.clear_cache()
    
    def unignore(self, path, *, confirm=False, dry_run=False, backup=True):
        path = ExPath(path)
        newvals = []
        found = False
        for ignored in self:
            if ignored == path:
                breakpoint()
                found = True
                continue
            newvals.append(ignored)
        if not found:
            logging.warning(f'Gitignore.unignore(path={path}): not in self. returning')
            return
        
        if confirm and not prompt.confirm(f'Remove {path} from .gitignore?'):
            print('aborting')
            return
        self.write(newvals, verify_paths=False, dry_run=dry_run, backup=backup)
    
    def is_subpath_of_ignored(self, p) -> bool:
        """Returns True if `p` is strictly a subpath of a path in .gitignore"""
        path = ExPath(p)
        for ignored in self:
            if ignored.parent_of(path):
                return True
        return False
    
    def is_ignored(self, p) -> bool:
        """Returns True if `p` in .gitignore, or if `p` is a subpath of a path in .gitignore,
        or if `p` fullmatches any part of a path in .gitignore (i.e. if 'env' is ignored, then 'src/env' returns True)"""
        path = ExPath(p)
        for ignored in self:
            if ignored == path:
                return True
            if ignored.parent_of(path):
                return True
            if any(re.fullmatch('env', part) for part in path.parts):
                return True
        return False
    
    def paths_where(self, predicate: Callable[[Any], bool]) -> Generator[ExPath, None, None]:
        for ignored in self:
            if predicate(ignored):
                yield ignored
Example #5
0
 def test__mixed_dirs_and_files__multiple_wildcards(self):
     # assumes .yarn/ and .yarnrc exist
     yarn = ExPath('/home/*/.yarn*')
     assert yarn.exists()
     assert not yarn.is_dir()
     assert not yarn.is_file()