def _complete_gallery_conf(sphinx_gallery_conf, src_dir, plot_gallery,
                           abort_on_example_error, lang='python',
                           builder_name='html', app=None, check_keys=True):
    gallery_conf = copy.deepcopy(DEFAULT_GALLERY_CONF)
    options = sorted(gallery_conf)
    extra_keys = sorted(set(sphinx_gallery_conf) - set(options))
    if extra_keys and check_keys:
        msg = 'Unknown key(s) in sphinx_gallery_conf:\n'
        for key in extra_keys:
            options = get_close_matches(key, options, cutoff=0.66)
            msg += repr(key)
            if len(options) == 1:
                msg += ', did you mean %r?' % (options[0],)
            elif len(options) > 1:
                msg += ', did you mean one of %r?' % (options,)
            msg += '\n'
        raise ConfigError(msg.strip())
    gallery_conf.update(sphinx_gallery_conf)
    if sphinx_gallery_conf.get('find_mayavi_figures', False):
        logger.warning(
            "Deprecated image scraping variable `find_mayavi_figures`\n"
            "detected, use `image_scrapers` instead as:\n\n"
            "   image_scrapers=('matplotlib', 'mayavi')",
            type=DeprecationWarning)
        gallery_conf['image_scrapers'] += ('mayavi',)
    gallery_conf.update(plot_gallery=plot_gallery)
    gallery_conf.update(abort_on_example_error=abort_on_example_error)
    # XXX anything that can only be a bool (rather than str) should probably be
    # evaluated this way as it allows setting via -D on the command line
    for key in ('run_stale_examples',):
        gallery_conf[key] = _bool_eval(gallery_conf[key])
    gallery_conf['src_dir'] = src_dir
    gallery_conf['app'] = app

    # Check capture_repr
    capture_repr = gallery_conf['capture_repr']
    supported_reprs = ['__repr__', '__str__', '_repr_html_']
    if isinstance(capture_repr, tuple):
        for rep in capture_repr:
            if rep not in supported_reprs:
                raise ConfigError("All entries in 'capture_repr' must be one "
                                  "of %s, got: %s" % (supported_reprs, rep))
    else:
        raise ConfigError("'capture_repr' must be a tuple, got: %s"
                          % (type(capture_repr),))
    # Check ignore_repr_types
    if not isinstance(gallery_conf['ignore_repr_types'], str):
        raise ConfigError("'ignore_repr_types' must be a string, got: %s"
                          % (type(gallery_conf['ignore_repr_types']),))

    # deal with show_memory
    gallery_conf['memory_base'] = 0.
    if gallery_conf['show_memory']:
        if not callable(gallery_conf['show_memory']):  # True-like
            try:
                from memory_profiler import memory_usage  # noqa
            except ImportError:
                logger.warning("Please install 'memory_profiler' to enable "
                               "peak memory measurements.")
                gallery_conf['show_memory'] = False
            else:
                def call_memory(func):
                    mem, out = memory_usage(func, max_usage=True, retval=True,
                                            multiprocess=True)
                    try:
                        mem = mem[0]  # old MP always returned a list
                    except TypeError:  # 'float' object is not subscriptable
                        pass
                    return mem, out
                gallery_conf['call_memory'] = call_memory
                gallery_conf['memory_base'] = _get_memory_base(gallery_conf)
        else:
            gallery_conf['call_memory'] = gallery_conf['show_memory']
    if not gallery_conf['show_memory']:  # can be set to False above
        def call_memory(func):
            return 0., func()
        gallery_conf['call_memory'] = call_memory
    assert callable(gallery_conf['call_memory'])

    # deal with scrapers
    scrapers = gallery_conf['image_scrapers']
    if not isinstance(scrapers, (tuple, list)):
        scrapers = [scrapers]
    scrapers = list(scrapers)
    for si, scraper in enumerate(scrapers):
        if isinstance(scraper, str):
            if scraper in _scraper_dict:
                scraper = _scraper_dict[scraper]
            else:
                orig_scraper = scraper
                try:
                    scraper = import_module(scraper)
                    scraper = getattr(scraper, '_get_sg_image_scraper')
                    scraper = scraper()
                except Exception as exp:
                    raise ConfigError('Unknown image scraper %r, got:\n%s'
                                      % (orig_scraper, exp))
            scrapers[si] = scraper
        if not callable(scraper):
            raise ConfigError('Scraper %r was not callable' % (scraper,))
    gallery_conf['image_scrapers'] = tuple(scrapers)
    del scrapers
    # Here we try to set up matplotlib but don't raise an error,
    # we will raise an error later when we actually try to use it
    # (if we do so) in scrapers.py.
    # In principle we could look to see if there is a matplotlib scraper
    # in our scrapers list, but this would be backward incompatible with
    # anyone using or relying on our Agg-setting behavior (e.g., for some
    # custom matplotlib SVG scraper as in our docs).
    # Eventually we can make this a config var like matplotlib_agg or something
    # if people need us not to set it to Agg.
    try:
        _import_matplotlib()
    except (ImportError, ValueError):
        pass

    # compress_images
    compress_images = gallery_conf['compress_images']
    if isinstance(compress_images, str):
        compress_images = [compress_images]
    elif not isinstance(compress_images, (tuple, list)):
        raise ConfigError('compress_images must be a tuple, list, or str, '
                          'got %s' % (type(compress_images),))
    compress_images = list(compress_images)
    allowed_values = ('images', 'thumbnails')
    pops = list()
    for ki, kind in enumerate(compress_images):
        if kind not in allowed_values:
            if kind.startswith('-'):
                pops.append(ki)
                continue
            raise ConfigError('All entries in compress_images must be one of '
                              '%s or a command-line switch starting with "-", '
                              'got %r' % (allowed_values, kind))
    compress_images_args = [compress_images.pop(p) for p in pops[::-1]]
    if len(compress_images) and not _has_optipng():
        logger.warning(
            'optipng binaries not found, PNG %s will not be optimized'
            % (' and '.join(compress_images),))
        compress_images = ()
    gallery_conf['compress_images'] = compress_images
    gallery_conf['compress_images_args'] = compress_images_args

    # deal with resetters
    resetters = gallery_conf['reset_modules']
    if not isinstance(resetters, (tuple, list)):
        resetters = [resetters]
    resetters = list(resetters)
    for ri, resetter in enumerate(resetters):
        if isinstance(resetter, str):
            if resetter not in _reset_dict:
                raise ConfigError('Unknown module resetter named %r'
                                  % (resetter,))
            resetters[ri] = _reset_dict[resetter]
        elif not callable(resetter):
            raise ConfigError('Module resetter %r was not callable'
                              % (resetter,))
    gallery_conf['reset_modules'] = tuple(resetters)

    if not isinstance(gallery_conf['reset_modules_order'], str):
        raise ConfigError('reset_modules_order must be a str, '
                          'got %r' % gallery_conf['reset_modules_order'])
    if gallery_conf['reset_modules_order'] not in ['before', 'after', 'both']:
        raise ConfigError("reset_modules_order must be in"
                          "['before', 'after', 'both'], "
                          'got %r' % gallery_conf['reset_modules_order'])

    lang = lang if lang in ('python', 'python3', 'default') else 'python'
    gallery_conf['lang'] = lang
    del resetters

    # Ensure the first cell text is a string if we have it
    first_cell = gallery_conf.get("first_notebook_cell")
    if (not isinstance(first_cell, str)) and (first_cell is not None):
        raise ConfigError("The 'first_notebook_cell' parameter must be type "
                          "str or None, found type %s" % type(first_cell))
    # Ensure the last cell text is a string if we have it
    last_cell = gallery_conf.get("last_notebook_cell")
    if (not isinstance(last_cell, str)) and (last_cell is not None):
        raise ConfigError("The 'last_notebook_cell' parameter must be type str"
                          " or None, found type %s" % type(last_cell))
    # Check pypandoc
    pypandoc = gallery_conf['pypandoc']
    if not isinstance(pypandoc, (dict, bool)):
        raise ConfigError("'pypandoc' parameter must be of type bool or dict,"
                          "got: %s." % type(pypandoc))
    gallery_conf['pypandoc'] = dict() if pypandoc is True else pypandoc
    has_pypandoc, version = _has_pypandoc()
    if isinstance(gallery_conf['pypandoc'], dict) and has_pypandoc is None:
        logger.warning("'pypandoc' not available. Using Sphinx-Gallery to "
                       "convert rst text blocks to markdown for .ipynb files.")
        gallery_conf['pypandoc'] = False
    elif isinstance(gallery_conf['pypandoc'], dict):
        logger.info("Using pandoc version: %s to convert rst text blocks to "
                    "markdown for .ipynb files" % (version,))
    else:
        logger.info("Using Sphinx-Gallery to convert rst text blocks to "
                    "markdown for .ipynb files.")
    if isinstance(pypandoc, dict):
        accepted_keys = ('extra_args', 'filters')
        for key in pypandoc:
            if key not in accepted_keys:
                raise ConfigError("'pypandoc' only accepts the following key "
                                  "values: %s, got: %s."
                                  % (accepted_keys, key))

    # Make it easy to know which builder we're in
    gallery_conf['builder_name'] = builder_name
    gallery_conf['titles'] = {}
    # Ensure 'backreferences_dir' is str, pathlib.Path or None
    backref = gallery_conf['backreferences_dir']
    if (not isinstance(backref, (str, pathlib.Path))) and \
            (backref is not None):
        raise ConfigError("The 'backreferences_dir' parameter must be of type "
                          "str, pathlib.Path or None, "
                          "found type %s" % type(backref))
    # if 'backreferences_dir' is pathlib.Path, make str for Python <=3.5
    # compatibility
    if isinstance(backref, pathlib.Path):
        gallery_conf['backreferences_dir'] = str(backref)

    # binder
    gallery_conf['binder'] = check_binder_conf(gallery_conf['binder'])

    if not isinstance(gallery_conf['css'], (list, tuple)):
        raise ConfigError('gallery_conf["css"] must be list or tuple, got %r'
                          % (gallery_conf['css'],))
    for css in gallery_conf['css']:
        if css not in _KNOWN_CSS:
            raise ConfigError('Unknown css %r, must be one of %r'
                              % (css, _KNOWN_CSS))
        if gallery_conf['app'] is not None:  # can be None in testing
            gallery_conf['app'].add_css_file(css + '.css')

    _update_gallery_conf(gallery_conf)

    return gallery_conf def figure_rst(figure_list, sources_dir, fig_titles='', srcsetpaths=None):
    """Generate RST for a list of image filenames.

    Depending on whether we have one or more figures, we use a
    single rst call to 'image' or a horizontal list.

    Parameters
    ----------
    figure_list : list
        List of strings of the figures' absolute paths.
    sources_dir : str
        absolute path of Sphinx documentation sources
    fig_titles : str
        Titles of figures, empty string if no titles found. Currently
        only supported for matplotlib figures, default = ''.
    srcsetpaths : list or None
        List of dictionaries containing absolute paths.  If
        empty, then srcset field is populated with the figure path.
        (see ``image_srcset`` configuration option).  Otherwise,
        each dict is of the form
        {0: /images/image.png, 2.0: /images/image_2_0x.png}
        where the key is the multiplication factor and the contents
        the path to the image created above.

    Returns
    -------
    images_rst : str
        rst code to embed the images in the document

    The rst code will have a custom ``image-sg`` directive that allows
    multiple resolution images to be served e.g.:
    ``:srcset: /plot_types/imgs/img_001.png,
      /plot_types/imgs/img_2_0x.png 2.0x``

    """

    if srcsetpaths is None:
        # this should never happen, but figure_rst is public, so
        # this has to be a kwarg...
        srcsetpaths = [{0: fl} for fl in figure_list]

    figure_paths = [os.path.relpath(figure_path, sources_dir)
                    .replace(os.sep, '/').lstrip('/')
                    for figure_path in figure_list]

    # Get alt text
    alt = ''
    if fig_titles:
        alt = fig_titles
    elif figure_list:
        file_name = os.path.split(figure_list[0])[1]
        # remove ext & 'sphx_glr_' from start & n#'s from end
        file_name_noext = os.path.splitext(file_name)[0][9:-4]
        # replace - & _ with \s
        file_name_final = re.sub(r'[-,_]', ' ', file_name_noext)
        alt = file_name_final
    alt = _single_line_sanitize(alt)

    images_rst = ""
    if len(figure_paths) == 1:
        figure_name = figure_paths[0]
        hinames = srcsetpaths[0]
        srcset = _get_srcset_st(sources_dir, hinames)
        images_rst = SG_IMAGE % (figure_name, alt, srcset)

    elif len(figure_paths) > 1:
        images_rst = HLIST_HEADER
        for nn, figure_name in enumerate(figure_paths):
            hinames = srcsetpaths[nn]
            srcset = _get_srcset_st(sources_dir, hinames)

            images_rst += (HLIST_SG_TEMPLATE %
                           (figure_name, alt, srcset))

    return images_rst def mayavi_scraper(block, block_vars, gallery_conf):
    """Scrape Mayavi images.

    Parameters
    ----------
    block : tuple
        A tuple containing the (label, content, line_number) of the block.
    block_vars : dict
        Dict of block variables.
    gallery_conf : dict
        Contains the configuration of Sphinx-Gallery

    Returns
    -------
    rst : str
        The ReSTructuredText that will be rendered to HTML containing
        the images. This is often produced by :func:`figure_rst`.
    """
    from mayavi import mlab
    image_path_iterator = block_vars['image_path_iterator']
    image_paths = list()
    e = mlab.get_engine()
    for scene, image_path in zip(e.scenes, image_path_iterator):
        try:
            mlab.savefig(image_path, figure=scene)
        except Exception:
            mlab.close(all=True)
            raise
        # make sure the image is not too large
        scale_image(image_path, image_path, 850, 999)
        if 'images' in gallery_conf['compress_images']:
            optipng(image_path, gallery_conf['compress_images_args'])
        image_paths.append(image_path)
    mlab.close(all=True)
    return figure_rst(image_paths, gallery_conf['src_dir']) SG_IMAGE = """
.. image-sg:: /%s
   :alt: %s
   :srcset: %s
   :class: sphx-glr-single-img
""" def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs):
    """Scrape Matplotlib images.

    Parameters
    ----------
    block : tuple
        A tuple containing the (label, content, line_number) of the block.
    block_vars : dict
        Dict of block variables.
    gallery_conf : dict
        Contains the configuration of Sphinx-Gallery
    **kwargs : dict
        Additional keyword arguments to pass to
        :meth:`~matplotlib.figure.Figure.savefig`, e.g. ``format='svg'``.
        The ``format`` kwarg in particular is used to set the file extension
        of the output file (currently only 'png', 'jpg', and 'svg' are
        supported).

    Returns
    -------
    rst : str
        The ReSTructuredText that will be rendered to HTML containing
        the images. This is often produced by :func:`figure_rst`.
    """
    matplotlib, plt = _import_matplotlib()
    from matplotlib.animation import Animation
    image_path_iterator = block_vars['image_path_iterator']
    image_rsts = []
    # Check for srcset hidpi images
    srcset = gallery_conf.get('image_srcset', [])
    srcset_mult_facs = [1]  # one is always supplied...
    for st in srcset:
        if (len(st) > 0) and (st[-1] == 'x'):
            # "2x" = "2.0"
            srcset_mult_facs += [float(st[:-1])]
        elif st == "":
            pass
        else:
            raise ExtensionError(
                f'Invalid value for image_srcset parameter: "{st}". '
                'Must be a list of strings with the multiplicative '
                'factor followed by an "x".  e.g. ["2.0x", "1.5x"]')

    # Check for animations
    anims = list()
    if gallery_conf.get('matplotlib_animations', False):
        for ani in block_vars['example_globals'].values():
            if isinstance(ani, Animation):
                anims.append(ani)
    # Then standard images
    for fig_num, image_path in zip(plt.get_fignums(), image_path_iterator):
        image_path = PurePosixPath(image_path)
        if 'format' in kwargs:
            image_path = image_path.with_suffix('.' + kwargs['format'])
        # Set the fig_num figure as the current figure as we can't
        # save a figure that's not the current figure.
        fig = plt.figure(fig_num)
        # Deal with animations
        cont = False
        for anim in anims:
            if anim._fig is fig:
                image_rsts.append(_anim_rst(anim,
                                            str(image_path), gallery_conf))
                cont = True
                break
        if cont:
            continue
        # get fig titles
        fig_titles = _matplotlib_fig_titles(fig)
        to_rgba = matplotlib.colors.colorConverter.to_rgba
        # shallow copy should be fine here, just want to avoid changing
        # "kwargs" for subsequent figures processed by the loop
        these_kwargs = kwargs.copy()
        for attr in ['facecolor', 'edgecolor']:
            fig_attr = getattr(fig, 'get_' + attr)()
            default_attr = matplotlib.rcParams['figure.' + attr]
            if to_rgba(fig_attr) != to_rgba(default_attr) and \
                    attr not in kwargs:
                these_kwargs[attr] = fig_attr

        # save the figures, and populate the srcsetpaths
        try:
            fig.savefig(image_path, **these_kwargs)
            dpi0 = matplotlib.rcParams['savefig.dpi']
            if dpi0 == 'figure':
                dpi0 = fig.dpi
            dpi0 = these_kwargs.get('dpi', dpi0)
            srcsetpaths = {0: image_path}

            # save other srcset paths, keyed by multiplication factor:
            for mult in srcset_mult_facs:
                if not (mult == 1):
                    multst = f'{mult}'.replace('.', '_')
                    name = f"{image_path.stem}_{multst}x{image_path.suffix}"
                    hipath = image_path.parent / PurePosixPath(name)
                    hikwargs = these_kwargs.copy()
                    hikwargs['dpi'] = mult * dpi0
                    fig.savefig(hipath, **hikwargs)
                    srcsetpaths[mult] = hipath
            srcsetpaths = [srcsetpaths]
        except Exception:
            plt.close('all')
            raise

        if 'images' in gallery_conf['compress_images']:
            optipng(str(image_path), gallery_conf['compress_images_args'])
            for hipath in srcsetpaths[0].items():
                optipng(str(hipath), gallery_conf['compress_images_args'])

        image_rsts.append(
            figure_rst([image_path], gallery_conf['src_dir'], fig_titles,
                       srcsetpaths=srcsetpaths))

    plt.close('all')
    rst = ''
    if len(image_rsts) == 1:
        rst = image_rsts[0]
    elif len(image_rsts) > 1:
        image_rsts = [re.sub(r':class: sphx-glr-single-img',
                             ':class: sphx-glr-multi-img',
                             image) for image in image_rsts]
        image_rsts = [HLIST_IMAGE_MATPLOTLIB + indent(image, u' ' * 6)
                      for image in image_rsts]
        rst = HLIST_HEADER + ''.join(image_rsts)
    return rst class ImagePathIterator(object):
    """Iterate over image paths for a given example.

    Parameters
    ----------
    image_path : str
        The template image path.
    """

    def __init__(self, image_path):
        self.image_path = image_path
        self.paths = list()
        self._stop = 1000000

    def __len__(self):
        """Return the number of image paths used.

        Returns
        -------
        n_paths : int
            The number of paths.
        """
        return len(self.paths)

    def __iter__(self):
        """Iterate over paths.

        Returns
        -------
        paths : iterable of str

        This enables the use of this Python pattern::

            >>> for epoch in epochs:  # doctest: +SKIP
            >>>     print(epoch)  # doctest: +SKIP

        Where ``epoch`` is given by successive outputs of
        :func:`mne.Epochs.next`.
        """
        # we should really never have 1e6, let's prevent some user pain
        for ii in range(self._stop):
            yield self.next()
        else:
            raise ExtensionError('Generated over %s images' % (self._stop,))

    def next(self):
        return self.__next__()

    def __next__(self):
        # The +1 here is because we start image numbering at 1 in filenames
        path = self.image_path.format(len(self) + 1)
        self.paths.append(path)
        return path def save_figures(block, block_vars, gallery_conf):
    """Save all open figures of the example code-block.

    Parameters
    ----------
    block : tuple
        A tuple containing the (label, content, line_number) of the block.
    block_vars : dict
        Dict of block variables.
    gallery_conf : dict
        Contains the configuration of Sphinx-Gallery

    Returns
    -------
    images_rst : str
        rst code to embed the images in the document.
    """
    image_path_iterator = block_vars['image_path_iterator']
    all_rst = u''
    prev_count = len(image_path_iterator)
    for scraper in gallery_conf['image_scrapers']:
        rst = scraper(block, block_vars, gallery_conf)
        if not isinstance(rst, str):
            raise ExtensionError('rst from scraper %r was not a string, '
                                 'got type %s:\n%r'
                                 % (scraper, type(rst), rst))
        n_new = len(image_path_iterator) - prev_count
        for ii in range(n_new):
            current_path, _ = _find_image_ext(
                image_path_iterator.paths[prev_count + ii])
            if not os.path.isfile(current_path):
                raise ExtensionError(
                    'Scraper %s did not produce expected image:'
                    '\n%s' % (scraper, current_path))
        all_rst += rst
    return all_rst _KNOWN_IMG_EXTS = ('png', 'svg', 'jpg', 'gif') def _reset_matplotlib(gallery_conf, fname):
    """Reset matplotlib."""
    mpl, plt = _import_matplotlib()
    plt.rcdefaults()
    importlib.reload(mpl.units)
    importlib.reload(mpl.dates)
    importlib.reload(mpl.category) def _get_image():
    try:
        from PIL import Image
    except ImportError as exc:  # capture the error for the modern way
        try:
            import Image
        except ImportError:
            raise ExtensionError(
                'Could not import pillow, which is required '
                'to rescale images (e.g., for thumbnails): %s' % (exc,))
    return Image
import os
import pytest
import sphinx_gallery
    import matplotlib.pyplot as plt  # nest these so that Agg can be set
    import matplotlib.pyplot as plt  # nest these so that Agg can be set
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib
from sphinx.errors import ConfigError, ExtensionError
from sphinx_gallery.gen_gallery import _complete_gallery_conf
from sphinx_gallery.scrapers import (figure_rst, mayavi_scraper, SG_IMAGE,
                                     matplotlib_scraper, ImagePathIterator,
                                     save_figures, _KNOWN_IMG_EXTS,
                                     _reset_matplotlib)
from sphinx_gallery.utils import _get_image
        from mayavi import mlab




@pytest.fixture(scope='function')
def gallery_conf(tmpdir):
    """Sets up a test sphinx-gallery configuration"""
    # Skip if numpy not installed
    pytest.importorskip("numpy")

    gallery_conf = _complete_gallery_conf({}, str(tmpdir), True, False)
    gallery_conf.update(examples_dir=str(tmpdir), gallery_dir=str(tmpdir))
    return gallery_conf


class matplotlib_svg_scraper():

    def __repr__(self):
        return self.__class__.__name__

    def __call__(self, *args, **kwargs):
        return matplotlib_scraper(*args, format='svg', **kwargs)


@pytest.mark.parametrize('ext', ('png', 'svg'))
def test_save_matplotlib_figures(gallery_conf, ext):
    """Test matplotlib figure save."""
    if ext == 'svg':
        gallery_conf['image_scrapers'] = (matplotlib_svg_scraper(),)
    plt.plot(1, 1)
    fname_template = os.path.join(gallery_conf['gallery_dir'], 'image{0}.png')

    image_path_iterator = ImagePathIterator(fname_template)