Repository: sphinx-gallery/sphinx-gallery

Issue:
`make html-noplot` instructions outdated
I think the instructions
```
html-noplot:
    $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
    @echo
    @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
```
given on https://sphinx-gallery.github.io/stable/configuration.html#without-execution are outdated for the newer versions of sphinx which have a catch-all target.

In my case it resulted in 
```
docs/> make html-noplot
sphinx-build -D plot_gallery=0 -b html  _build/html
usage: sphinx-build [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]
sphinx-build: error: the following arguments are required: outputdir, filenames
Makefile:15: recipe for target 'html-noplot' failed
make: *** [html-noplot] Error 2
```

The following would work (an adaption of the catch-all line):
```
html-noplot:
	@$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" -D plot_gallery=0 $(SPHINXOPTS) $(O)
```
but I don't know if there is a better/preferred way for this.

Discussion:
larsoner: Travis failure is unrelated https://github.com/sphinx-doc/sphinx/issues/7570 (looking into it now)
lucyleeow: @larsoner i just made an issue #659 - not sure how to tackle this one! I haven't looked into the other error with `test_backreferences`

Patch:
diff --git a/doc/configuration.rst b/doc/configuration.rst
index adc3dca..d826e7c 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -320,24 +320,25 @@ your Sphinx-Gallery configuration ``conf.py`` file with::
 
     sphinx_gallery_conf = {
         ...
-        # directory where function granular galleries are stored
+        # directory where function/class granular galleries are stored
         'backreferences_dir'  : 'gen_modules/backreferences',
 
-        # Modules for which function level galleries are created.  In
+        # Modules for which function/class level galleries are created. In
         # this case sphinx_gallery and numpy in a tuple of strings.
         'doc_module'          : ('sphinx_gallery', 'numpy')}
 
 The path you specify in ``backreferences_dir`` (here we choose
 ``gen_modules/backreferences``) will be populated with
-ReStructuredText files. Each will contain a reduced version of the
-gallery specific to every function used across all the examples
-galleries and belonging to the modules listed in ``doc_module``.
-``backreferences_dir` should be a string or ``pathlib.Path`` object that is
+ReStructuredText files. Each .rst file will contain a reduced version of the
+gallery specific to every function/class that is used across all the examples
+and belonging to the modules listed in ``doc_module``.
+``backreferences_dir`` should be a string or ``pathlib.Path`` object that is
 **relative** to the ``conf.py`` file, or ``None``. It is ``None`` by default.
 
-Then within your sphinx documentation ``.rst`` files you write these
-lines to include this reduced version of the Gallery, which has
-examples in use of a specific function, in this case ``numpy.exp``::
+Within your sphinx documentation ``.rst`` files, you can use easily
+add this reduced version of the Gallery. For example, the rst below adds
+the reduced version of the Gallery for ``numpy.exp``, which includes all
+examples that use the specific function ``numpy.exp``::
 
     .. include:: gen_modules/backreferences/numpy.exp.examples
     .. raw:: html
@@ -545,6 +546,10 @@ To remove the comment from the rendered example set the option::
         'remove_config_comments': True,
     }
 
+This only removes configuration comments from code blocks, not from text
+blocks. However, note that technically, configuration comments will work when
+put in either code blocks or text blocks.
+
 .. _own_notebook_cell:
 
 Add your own first and last notebook cell
@@ -818,7 +823,7 @@ your ``Makefile`` with:
 .. code-block:: Makefile
 
     html-noplot:
-        $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+        $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(SOURCEDIR) $(BUILDDIR)/html
         @echo
         @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
@@ -1063,7 +1068,7 @@ Controlling what output is captured
 
 .. note::
 
-    Configure ``capture_repr`` to be an empty tuple (i.e.,``capture_repr: ()``)
+    Configure ``capture_repr`` to be an empty tuple (i.e., `capture_repr: ()`)
     to return to the output capturing behaviour prior to release v0.5.0.
 
 The ``capture_repr`` configuration allows the user to control what output
@@ -1171,5 +1176,5 @@ etc. Similarly subclasses of 'matplotlib.axes' (e.g. 'matplotlib.axes.Axes',
     sphinx_gallery_conf = {
         ...
         'capture_repr': ('__repr__'),
-        'ignore_repr_types': r'matplotlib.text|matplotlib.axes',
+        'ignore_repr_types': r'matplotlib[text, axes]',
     }
diff --git a/doc/getting_started.rst b/doc/getting_started.rst
index e1b9e20..8c7b776 100644
--- a/doc/getting_started.rst
+++ b/doc/getting_started.rst
@@ -16,7 +16,7 @@ using the Sphinx extension Sphinx-Gallery, which will do the following:
   download.
 * Create a gallery with thumbnails for each of these examples
   (such as `the one that scikit-learn
-  <http://scikit-learn.org/stable/auto_examples/index.html>`_ uses)
+  <http://scikit-learn.org/stable/auto_examples/index.html>`_ uses).
 
 A `template repository <https://github.com/sphinx-gallery/sample-project>`_,
 with sample example galleries and basic configurations is also available to
@@ -93,7 +93,7 @@ this folder must have the following things:
       <https://www.sphinx-doc.org/en/master/man/sphinx-build.html>`_ selected)
       output of the script. Files without that prefix will be only parsed and
       presented in a rich literate programming fashion, without any output. To
-      change the default pattern for execution and capture see
+      change the default file pattern for execution and capture see
       :ref:`build_pattern`.
     * The output that is captured while executing the ``.py`` files and
       subsequently incorporated into the built documentation can be finely
@@ -126,8 +126,9 @@ This loads Sphinx-Gallery as one of your extensions, the ellipsis
 ``...`` represents your other loaded extensions.
 
 Next, create your configuration dictionary for Sphinx-Gallery. Here we will
-simply tell Sphinx-Gallery the location of the 'examples' directory
-(containing the gallery header file and our example Python scripts) and the
+simply set the minimal required configurations. We must set the location of
+the 'examples' directory (containing the gallery header file and our example
+Python scripts) and the
 directory to place the output files generated. The path to both of these
 directories should be relative to the ``doc/conf.py`` file.
 
@@ -143,8 +144,8 @@ The following configuration declares the location of the 'examples' directory
 After building your documentation, ``gallery_dirs`` will contain the following
 files and directories:
 
-* ``index.rst`` - the master document of the gallery containing the Gallery
-  Header, table of contents tree and thumbnails for each example. It will serve
+* ``index.rst`` - the master document of the gallery containing the gallery
+  header, table of contents tree and thumbnails for each example. It will serve
   as the welcome page for that gallery.
 * ``sg_execution_times.rst`` - execution time of all example ``.py`` files,
   summarised in table format (`original pull request on GitHub
@@ -152,7 +153,7 @@ files and directories:
 * ``images`` - directory containing images produced during execution of the
   example ``.py`` files (more details in :ref:`image_scrapers`) and thumbnail
   images for the gallery.
-* A directory for each sub-directory in ``'example_dirs'``. Within each 
+* A directory for each sub-directory in ``'example_dirs'``. Within each
   directory will be the above and below listed files for that 'sub-gallery'.
 
 Additionally for **each** ``.py`` file, a file with the following suffix is
diff --git a/doc/syntax.rst b/doc/syntax.rst
index 7a1fdfd..4111d32 100644
--- a/doc/syntax.rst
+++ b/doc/syntax.rst
@@ -69,18 +69,26 @@ rST in the built gallery examples. For example::
   print('my variable plus 2 is {}'.format(myvariable + 2))
 
 The ``#%%`` and ``# %%`` syntax is consistent with the 'code block' (or
-'code cell') separator syntax in `Jupyter VSCode plugin
-<https://code.visualstudio.com/docs/python/jupyter-support-py>`_, `Jupytext
-<https://jupytext.readthedocs.io/en/latest/introduction.html>`_, `Pycharm
+'code cell') separator syntax in `Visual Studio Code Python extension
+<https://code.visualstudio.com/docs/python/jupyter-support-py#_jupyter-code-cells>`_,
+`Visual Studio Python Tools
+<https://docs.microsoft.com/en-us/visualstudio/python/python-interactive-repl-in-visual-studio?view=vs-2019#work-with-code-cells>`_,
+`Jupytext
+<https://jupytext.readthedocs.io/en/latest/formats.html#the-percent-format>`_,
+`Pycharm Professional
 <https://www.jetbrains.com/help/pycharm/running-jupyter-notebook-cells.html>`_,
 `Hydrogen plugin (for Atom)
-<https://nteract.gitbooks.io/hydrogen/>`_ and `Spyder
-<https://docs.spyder-ide.org/editor.html>`_. Note that although the
+<https://nteract.gitbooks.io/hydrogen/docs/Usage/Cells.html#example-definitions>`_
+and `Spyder
+<https://docs.spyder-ide.org/editor.html#defining-code-cells>`_.
+Note that although the
 documentation may only mention one of ``#%%`` or ``# %%``, in practice both
-work in these editors. In these IDEs (or with these IDE plugins), ``#%%`` or
+work. With these editors/IDEs, ``#%%`` or
 ``# %%`` at the start of a line signifies the start of a new code block.
-Code within a code block can be easily executed together, at the same time. This
-functionality can be helpful when writing a Sphinx-Gallery ``.py`` example as
+Code blocks allow you to separate your code into chunks, like in Jupyter
+Notebooks. All the code within a code block can be easily executed together.
+This functionality can be helpful when writing a Sphinx-Gallery ``.py``
+example as
 the blocks allow you to easily create pairs of subsequent Sphinx-Gallery text
 and code blocks.
 
diff --git a/examples/plot_1_exp.py b/examples/plot_1_exp.py
index 3872067..4c133c1 100644
--- a/examples/plot_1_exp.py
+++ b/examples/plot_1_exp.py
@@ -29,11 +29,13 @@ def main():
     plt.plot(x, y)
     plt.xlabel('$x$')
     plt.ylabel('$\exp(x)$')
+    plt.title('Exponential function')
 
     plt.figure()
     plt.plot(x, -np.exp(-x))
     plt.xlabel('$x$')
     plt.ylabel('$-\exp(-x)$')
+    plt.title('Negative exponential\nfunction')
     # To avoid matplotlib text output
     plt.show()
 
diff --git a/sphinx_gallery/scrapers.py b/sphinx_gallery/scrapers.py
index b43d122..97c6306 100644
--- a/sphinx_gallery/scrapers.py
+++ b/sphinx_gallery/scrapers.py
@@ -13,6 +13,8 @@ live in modules that will support them (e.g., PyVista, Plotly).
 
 import os
 import sys
+import re
+from textwrap import indent
 
 from .utils import scale_image
 
@@ -47,6 +49,23 @@ def _import_matplotlib():
     return matplotlib, plt
 
 
+def _matplotlib_fig_titles(fig):
+    titles = []
+    # get supertitle if exists
+    suptitle = getattr(fig, "_suptitle", None)
+    if suptitle is not None:
+        titles.append(suptitle.get_text())
+    # get titles from all axes, for all locs
+    title_locs = ['left', 'center', 'right']
+    for ax in fig.axes:
+        for loc in title_locs:
+            text = ax.get_title(loc=loc)
+            if text:
+                titles.append(text)
+    fig_titles = ', '.join(titles)
+    return fig_titles
+
+
 def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs):
     """Scrape Matplotlib images.
 
@@ -73,7 +92,7 @@ def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs):
     """
     matplotlib, plt = _import_matplotlib()
     image_path_iterator = block_vars['image_path_iterator']
-    image_paths = list()
+    image_rsts = []
     for fig_num, image_path in zip(plt.get_fignums(), image_path_iterator):
         if 'format' in kwargs:
             image_path = '%s.%s' % (os.path.splitext(image_path)[0],
@@ -81,6 +100,8 @@ def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs):
         # 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)
+        # 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
@@ -92,9 +113,20 @@ def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs):
                     attr not in kwargs:
                 these_kwargs[attr] = fig_attr
         fig.savefig(image_path, **these_kwargs)
-        image_paths.append(image_path)
+        image_rsts.append(
+            figure_rst([image_path], gallery_conf['src_dir'], fig_titles))
     plt.close('all')
-    return figure_rst(image_paths, gallery_conf['src_dir'])
+    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
 
 
 def mayavi_scraper(block, block_vars, gallery_conf):
@@ -242,7 +274,7 @@ def save_figures(block, block_vars, gallery_conf):
     return all_rst
 
 
-def figure_rst(figure_list, sources_dir):
+def figure_rst(figure_list, sources_dir, fig_titles=''):
     """Generate RST for a list of image filenames.
 
     Depending on whether we have one or more figures, we use a
@@ -254,6 +286,9 @@ def figure_rst(figure_list, sources_dir):
         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 = ''.
 
     Returns
     -------
@@ -264,18 +299,37 @@ def figure_rst(figure_list, sources_dir):
     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]
-        images_rst = SINGLE_IMAGE % figure_name
+        images_rst = SINGLE_IMAGE % (figure_name, alt)
     elif len(figure_paths) > 1:
         images_rst = HLIST_HEADER
         for figure_name in figure_paths:
-            images_rst += HLIST_IMAGE_TEMPLATE % figure_name
-
+            images_rst += HLIST_IMAGE_TEMPLATE % (figure_name, alt)
     return images_rst
 
 
+def _single_line_sanitize(s):
+    """Remove problematic newlines."""
+    # For example, when setting a :alt: for an image, it shouldn't have \n
+    # This is a function in case we end up finding other things to replace
+    return s.replace('\n', ' ')
+
+
 # The following strings are used when we have several pictures: we use
 # an html div tag that our CSS uses to turn the lists into horizontal
 # lists.
@@ -284,15 +338,21 @@ HLIST_HEADER = """
 
 """
 
+HLIST_IMAGE_MATPLOTLIB = """
+    *
+"""
+
 HLIST_IMAGE_TEMPLATE = """
     *
 
       .. image:: /%s
-            :class: sphx-glr-multi-img
+          :alt: %s
+          :class: sphx-glr-multi-img
 """
 
 SINGLE_IMAGE = """
 .. image:: /%s
+    :alt: %s
     :class: sphx-glr-single-img
 """
 


Test patch:
diff --git a/sphinx_gallery/tests/test_full.py b/sphinx_gallery/tests/test_full.py
index d444627..e6cb36e 100644
--- a/sphinx_gallery/tests/test_full.py
+++ b/sphinx_gallery/tests/test_full.py
@@ -24,7 +24,7 @@ from sphinx_gallery.utils import _get_image, scale_image
 
 import pytest
 
-N_TOT = 6
+N_TOT = 7
 N_FAILING = 1
 N_GOOD = N_TOT - N_FAILING
 N_RST = 14 + N_TOT
@@ -550,3 +550,23 @@ def test_rebuild(tmpdir_factory, sphinx_app):
         # mtimes for .ipynb files
         _assert_mtimes(copied_ipy_0, copied_ipy_1,
                        different=('plot_numpy_matplotlib.ipynb'))
+
+
+def test_alt_text(sphinx_app):
+    """Test alt text"""
+    src_dir = sphinx_app.srcdir
+    fname = op.join(src_dir, 'auto_examples', 'plot_matplotlib_alt.rst')
+    assert op.isfile(fname)
+    with codecs.open(fname, 'r', 'utf-8') as fid:
+        rst = fid.read()
+    # suptitle and axes titles
+    assert ':alt: This is a sup title, subplot 1, subplot 2' in rst
+    # multiple titles
+    assert ':alt: Left Title, Center Title, Right Title' in rst
+
+    fname = op.join(src_dir, 'auto_examples', 'plot_numpy_matplotlib.rst')
+    assert op.isfile(fname)
+    with codecs.open(fname, 'r', 'utf-8') as fid:
+        rst = fid.read()
+    # file name when no fig title
+    assert 'plot numpy matplotlib' in rst
diff --git a/sphinx_gallery/tests/test_scrapers.py b/sphinx_gallery/tests/test_scrapers.py
index 8d303c7..f313f76 100644
--- a/sphinx_gallery/tests/test_scrapers.py
+++ b/sphinx_gallery/tests/test_scrapers.py
@@ -175,6 +175,7 @@ def test_figure_rst(ext):
     image_rst = figure_rst(figure_list, '.')
     single_image = """
 .. image:: /sphx_glr_plot_1.{ext}
+    :alt: pl
     :class: sphx-glr-single-img
 """.format(ext=ext)
     assert image_rst == single_image
@@ -188,12 +189,14 @@ def test_figure_rst(ext):
     *
 
       .. image:: /sphx_glr_plot_1.{ext}
-            :class: sphx-glr-multi-img
+          :alt: pl
+          :class: sphx-glr-multi-img
 
     *
 
       .. image:: /second.{ext}
-            :class: sphx-glr-multi-img
+          :alt: pl
+          :class: sphx-glr-multi-img
 """.format(ext=ext)
     assert image_rst == image_list_rst
 
@@ -201,7 +204,7 @@ def test_figure_rst(ext):
     local_img = [os.path.join(os.getcwd(), 'third.' + ext)]
     image_rst = figure_rst(local_img, '.')
 
-    single_image = SINGLE_IMAGE % ("third." + ext)
+    single_image = SINGLE_IMAGE % ("third." + ext, '')
     assert image_rst == single_image
 
 
diff --git a/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py b/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py
new file mode 100644
index 0000000..6617c3c
--- /dev/null
+++ b/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py
@@ -0,0 +1,36 @@
+"""
+======================
+Matplotlib alt text
+======================
+
+This example tests that the alt text is generated correctly for matplotlib
+figures.
+"""
+
+import matplotlib.pyplot as plt
+
+fig, axs = plt.subplots(2, 1, constrained_layout=True)
+axs[0].plot([1, 2, 3])
+axs[0].set_title('subplot 1')
+axs[0].set_xlabel('x label')
+axs[0].set_ylabel('y lab')
+fig.suptitle('This is a\nsup title')
+
+axs[1].plot([2, 3, 4])
+axs[1].set_title('subplot 2')
+axs[1].set_xlabel('x label')
+axs[1].set_ylabel('y label')
+
+plt.show()
+
+
+# %%
+# Several titles.
+
+plt.plot(range(10))
+
+plt.title('Center Title')
+plt.title('Left Title', loc='left')
+plt.title('Right Title', loc='right')
+
+plt.show()