Extract archived experiments

        Parameters
        ----------
        experiments: list of str
            The experiments to extract. If None the current experiment is used
        archive: str
            The path to an archive to extract the experiments from. If None,
            we assume that the path to the archive has been stored in the
            configuration when using the :meth:`archive` command
        complete: bool
            If True, archives are extracted completely, not only the experiment
            (implies ``project_data = True``)
        project_data: bool
            If True, the data for the project is extracted as well
        replace_project_config: bool
            If True and the project does already exist in the configuration, it
            is updated with what is stored in the archive
        root: str
            An alternative root directory to use. Otherwise the experiment will
            be exctracted to

            1. the root directory specified in the configuration files
               (if the project exists in it) and `replace_project_config` is
               False
            2. the root directory as stored in the archive
        projectname: str
            The projectname to use. If None, use the one specified in the
            archive
        fmt: { 'gztar' | 'bztar' | 'tar' | 'zip' }
            The format of the archive. If None, it is inferred
        force: bool
            If True, force to overwrite the configuration of all experiments
            from what is stored in `archive`. Otherwise, the configuration of
            the experiments in `archive` are only used if missing in the
            current configuration

def unarchive(self, experiments=None, archive=None, complete=False,
                  project_data=False, replace_project_config=False, root=None,
                  projectname=None, fmt=None, force=False, **kwargs):
        """
        Extract archived experiments

        Parameters
        ----------
        experiments: list of str
            The experiments to extract. If None the current experiment is used
        archive: str
            The path to an archive to extract the experiments from. If None,
            we assume that the path to the archive has been stored in the
            configuration when using the :meth:`archive` command
        complete: bool
            If True, archives are extracted completely, not only the experiment
            (implies ``project_data = True``)
        project_data: bool
            If True, the data for the project is extracted as well
        replace_project_config: bool
            If True and the project does already exist in the configuration, it
            is updated with what is stored in the archive
        root: str
            An alternative root directory to use. Otherwise the experiment will
            be exctracted to

            1. the root directory specified in the configuration files
               (if the project exists in it) and `replace_project_config` is
               False
            2. the root directory as stored in the archive
        projectname: str
            The projectname to use. If None, use the one specified in the
            archive
        fmt: { 'gztar' | 'bztar' | 'tar' | 'zip' }
            The format of the archive. If None, it is inferred
        force: bool
            If True, force to overwrite the configuration of all experiments
            from what is stored in `archive`. Otherwise, the configuration of
            the experiments in `archive` are only used if missing in the
            current configuration
        """
        def extract_file(path):
            if atype == 'zip':
                return file_obj.open(path)
            else:
                return file_obj.extractfile(path)

        self.app_main(**kwargs)

        logger = self.logger

        project_config = None

        all_exps = self.config.experiments
        all_projects = self.config.projects
        # ---- set archive
        # if archive is None, check for the archives listed in `experiments`
        # and raise an error if one has not been archived yet or if they belong
        # to different files
        if archive is None:
            # ---- set experiments
            # if experiments is None, use the current experiment. If complete
            # is True, this will be replaced below
            if experiments is None:
                experiments = [self.experiment]
            archives = list(filter(utils.isstring,
                                   map(self.is_archived, experiments)))
            if len(archives) > 1:
                raise ValueError(
                    'The given experiments belong to multiple archives %s!' % (
                        ', '.join(archives)))
            archive = next(iter(archives))
        elif not complete and experiments is None:
            experiments = [self.experiment]

        logger.info('Unarchiving from %s', archive)

        # --- infer compression type
        ext_map, fmt_map = self._archive_extensions()
        if fmt is None:
            try:
                fmt = next(fmt for ext, fmt in ext_map.items()
                           if archive.endswith(ext))
            except StopIteration:
                raise IOError(
                    "Could not infer archive format of {}! Please specify it "
                    "manually using the `fmt` parameter!".format(archive))

        # if no root directory is specified but a projectname, we take the root
        # directory from the configuration if, and only if, the configuration
        # should not be replaced
        if (root is None and projectname is not None and
                not replace_project_config):
            all_projects.get(projectname, {}).get('root')

        # ---- open the archive
        modes = {'bztar': 'r:bz2', 'gztar': 'r:gz', 'tar': 'r', 'zip': 'r'}
        atype = 'zip' if fmt == 'zip' else 'tar'
        if atype == 'tar':
            from tarfile import open as open_file
        else:
            from zipfile import ZipFile as open_file
        file_obj = open_file(archive, modes[fmt])

        # ---- if root is None, get it from the archive
        if root is None:
            fp = extract_file(osp.join('.project', '.project.yml'))
            try:
                project_config = ordered_yaml_load(fp)
                # use the projectname in archive only, if nothing is specified
                # here
                projectname = projectname or project_config['name']
            except:
                raise
            finally:
                fp.close()
            # if the projectname is existent in our configuration and already
            # specified, use this one
            if (projectname in self.config.projects and
                    not replace_project_config):
                root = self.config.projects[projectname].get('root')
            else:
                root = project_config.get('root')
        # if we still don't have it, because it was not specified in the
        # archive or the configuration, raise an error
        if root is None:
            raise ValueError("Could not find a root directory path for the "
                             "project. Please specify manually!")

        logger.info('Root directory for the project: %s', root)

        t = str(dt.datetime.now())  # time at the beginning of extraction

        config_files = []

        def fname_filter(m):
            fname = get_fname(m)
            if (dir_contains('.project', fname) and
                    not osp.basename(fname).startswith('.')):
                config_files.append(fname)
            return (
                complete or fname in config_files or
                (project_data and not dir_contains('experiments', fname)) or
                any(dir_contains(d, fname) for d in dirs))

        if not complete:
            dirs = [osp.join('experiments', exp) for exp in experiments]
            dirs.append(osp.join('.project', '.project.yml'))
        dir_contains = partial(utils.dir_contains, exists=False)
        if atype == 'zip':
            def get_fname(m):
                return m
            members = list(filter(fname_filter, file_obj.namelist()))
        else:
            def get_fname(m):
                return m.name
            members = list(filter(fname_filter, file_obj.getmembers()))
        logger.debug('Extracting %s files from archive to %s',
                     len(members), root)
        file_obj.extractall(root, members=members)

        # if the project_config yet not has been red, read it now
        if not project_config:
            with open(osp.join(root, '.project', '.project.yml')) as fp:
                project_config = ordered_yaml_load(fp)
            if projectname:
                project_config['name'] = projectname
            else:
                projectname = project_config['name']
        if projectname not in all_projects or replace_project_config:
            all_projects[projectname] = project_config
        else:
            all_projects[projectname]['root'] = root

        # get all experiment names in the archive
        arc_exps = [osp.splitext(osp.basename(f))[0] for f in config_files]
        if complete:
            experiments = arc_exps
        else:
            for exp in filter(lambda exp: exp not in arc_exps, experiments[:]):
                logger.warn('Experiment %s was not found in archive!', exp)
                experiments.remove(exp)
        for exp in experiments:
            if force or exp not in self.config.experiments or self.is_archived(
                    exp):
                with open(osp.join(root, '.project', exp + '.yml')) as fexp:
                    exp_config = ordered_yaml_load(fexp)
                logger.debug('Update configuration for %s', exp)
                all_exps[exp] = self.fix_paths(exp_config)
            else:
                exp_config = all_exps[exp]
            exp_config['project'] = projectname
            exp_config['timestamps']['unarchive'] = t

        logger.debug('Done.')