Repository: quantumlib/Cirq

Issue:
QPolish - revamp Cirq documentation 
Readthedocs.io for Cirq was a great start and served us well, but it has limitations and also is a standalone site for Cirq. We need a new site that fits nicely into the rest of the Quantum AI OSS efforts: OpenFermion, ReCirq, qsim, Tensorflow Quantum. The QPolish project is intended to deliver in 2020 a complete revamp of the documentation and to roll out a new website to unify our OSS presence on the web.


Discussion:

Resolving patch:
diff --git a/rtd_docs/conf.py b/rtd_docs/conf.py
--- a/rtd_docs/conf.py
+++ b/rtd_docs/conf.py
@@ -15,143 +15,21 @@
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #
-import inspect
-import re
-from typing import List, Any
 
 import os
 import sys
-import shutil
-
-import pypandoc
 
 cirq_root_path = os.path.dirname(os.path.dirname(__file__))
 sys.path.insert(0, cirq_root_path)
-from cirq import _doc
-
-
-def setup(app):
-    # just in case it exists (locally) remove the copied docs folder
-    shutil.rmtree("./docs", ignore_errors=True)
-    # copy recursively the actual content from the devsite folder
-    # to rtd_docs/docs
-    shutil.copytree(src="../docs", dst="./docs")
-    app.add_config_value('pandoc_use_parser', 'markdown', True)
-    app.connect('autodoc-process-docstring', autodoc_process)
-    app.connect('autodoc-skip-member', autodoc_skip_member)
-    app.connect('source-read', source_read)
-    app.connect('missing-reference', on_missing_reference)
-
-
-def convert_markdown_mathjax_for_rst(lines: List[str]) -> List[str]:
-    if all('$$' not in line for line in lines):
-        return lines
-
-    data = '\n'.join(lines)
-    sections = data.split('$$')
-    if len(sections) % 2 != 1:
-        raise ValueError('Mismatched number of "$$" latex tokens.')
-
-    result = []
-    for i, s in enumerate(sections):
-        if i % 2:
-            # Avoid getting split across divs.
-            s = ' '.join(s.split('\n'))
-            # Avoid intermediate layers turning our newlines into slashes.
-            s = s.replace('\\\\', r'\newline')
-            # Turn latex like "|x\rangle" into "|x \rangle".
-            # The extra space seems to be necessary to survive a later pass.
-            s = re.sub(r'([a-zA-Z0-9])\\', r'\1 \\', s)
-            # Keep the $$ so MathJax can find it.
-            result.append('$${}$$'.format(s))
-        else:
-            # Work around bad table detection in pandoc by concatenating
-            # lines from the same paragraph.
-            s = '\n\n'.join(e.replace('\n', ' ') for e in s.split('\n\n'))
-
-            # Convert markdown to rst.
-            out = pypandoc.convert(s, to='rst', format='markdown_github')
-
-            # Not sure why pandoc is escaping these...
-            out = out.replace(r'\|', '|')
-
-            result.extend(out.split('\n'))
-
-    return result
-
-
-def autodoc_skip_member(
-    app,
-    what: str,
-    name: str,
-    obj: Any,
-    skip: bool,
-    options,
-) -> bool:
-    """Public members already kept. Also include members marked as documented."""
-    # Never skip if explicitly whitelisted.
-    if id(obj) in _doc.RECORDED_CONST_DOCS:
-        return False
-    # Skip all private methods.
-    if name.startswith('_'):
-        return True
-    # Fallback to default.
-    return skip
-
-
-def autodoc_process(app, what: str, name: str, obj: Any, options, lines: List[str]) -> None:
-    # Try to lookup in documented dictionary.
-    doc_string = _doc.RECORDED_CONST_DOCS.get(id(obj))
-    if name.startswith('cirq') and doc_string is not None:
-        # Override docstring if requested.
-        if doc_string is not None:
-            new_doc_string = inspect.cleandoc(doc_string)
-            lines[:] = new_doc_string.split('\n')
-    elif not (getattr(obj, '__module__', 'cirq') or '').startswith('cirq'):
-        # Don't convert objects from other modules.
-        return
-
-    # Don't convert output from Napoleon extension, which is already rst.
-    i = 0
-    while i < len(lines) and not lines[i].startswith(':'):
-        i += 1
-    if not i:
-        return
-
-    converted_lines = convert_markdown_mathjax_for_rst(lines[:i])
-    kept_lines = lines[i:]
-
-    data = pypandoc.convert(
-        '\n'.join(converted_lines),
-        to='rst',
-        format='markdown_github',
-    )
-
-    lines[:] = data.split('\n') + kept_lines
-
-
-def source_read(app, docname, source):
-    source[0] = re.sub(r'"##### (Copyright 20\d\d The Cirq Developers)"', r'""', source[0])
-    source[0] = re.sub(
-        r'(\{\s*?"cell_type": "code".*?"#@title.*License.".*?\},)', r'', source[0], flags=re.S
-    )
-
-    source[0] = re.sub(r'"<table.*tfo-notebook-buttons.*"</table>"', r'""', source[0], flags=re.S)
-
 
 # -- Project information -----------------------------------------------------
 
 project = 'Cirq'
-copyright = '2018, The Cirq Developers'  # pylint: disable=redefined-builtin
+copyright = '2018-2020, The Cirq Developers'  # pylint: disable=redefined-builtin
 author = 'The Cirq Developers'
 
-# The full version, including alpha/beta/rc tags
-__version__ = ''
-exec(open(os.path.join(cirq_root_path, 'cirq', '_version.py')).read())
-release = __version__
-
 # The short X.Y version
-version = release  # '.'.join(release.split('.')[:2])
+version = ""  # '.'.join(release.split('.')[:2])
 
 # -- General configuration ---------------------------------------------------
 
@@ -163,14 +41,7 @@ def source_read(app, docname, source):
 # ones.
 extensions = [
     'myst_parser',
-    'nbsphinx',
-    'sphinx.ext.autodoc',
-    'sphinx.ext.autosummary',
-    'sphinx.ext.doctest',
-    'sphinx.ext.mathjax',
-    'sphinx.ext.napoleon',
-    'sphinx.ext.viewcode',
-    'sphinx_markdown_tables',
+    'notfound.extension',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -200,11 +71,8 @@ def source_read(app, docname, source):
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path .
 exclude_patterns = [
-    '_build',
     'Thumbs.db',
     '.DS_Store',
-    'docs/citation.md',
-    'docs/tutorials/educators/*',
 ]
 
 # The name of the Pygments (syntax highlighting) style to use.
@@ -216,11 +84,6 @@ def source_read(app, docname, source):
 html_favicon = 'favicon.ico'
 # html_theme_options = {}
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
 # Custom sidebar templates, must be a dictionary that maps document names
 # to template names.
 #
@@ -231,77 +94,5 @@ def source_read(app, docname, source):
 #
 # html_sidebars = {}
 
-html_logo = 'docs/images/Cirq_logo_notext.png'
+html_logo = 'Cirq_logo_notext.png'
 html_css_files = ['tweak-style.css']
-
-# -- Options for HTMLHelp output -----------------------------------------
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Cirqdoc'
-
-# -- Options for LaTeX output --------------------------------------------
-
-latex_elements = {
-    # The paper size ('letterpaper' or 'a4paper').
-    'papersize': 'letterpaper',
-    # The font size ('10pt', '11pt' or '12pt').
-    # 'pointsize': '10pt',
-    # Additional stuff for the LaTeX preamble.
-    # 'preamble': '',
-    # Latex figure (float) alignment
-    # 'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, 'Cirq.tex', 'Cirq Documentation', 'The Cirq Developers', 'manual'),
-]
-
-# -- Options for manual page output --------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [(master_doc, 'cirq', 'Cirq Documentation', [author], 1)]
-
-# -- Options for Texinfo output ------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-    (
-        master_doc,
-        'Cirq',
-        'Cirq Documentation',
-        author,
-        'Cirq',
-        'A python library for NISQ circuits.',
-        'Miscellaneous',
-    ),
-]
-
-# -- Extension configuration -------------------------------------------------
-
-# Generate subpages for reference docs automatically.
-# http://www.sphinx-doc.org/en/master/ext/autosummary.html#generating-stub-pages-automatically
-autosummary_generate = True
-
-# to resolve name clashes between the generated files
-autosummary_filename_map = {"cirq.QFT": "cirq.QFT_deprecated"}
-
-myst_update_mathjax = False
-
-# To allow for google.colab temporarily in notebooks
-# TODO: after https://github.com/quantumlib/Cirq/issues/3368 turn this back off
-nbsphinx_allow_errors = True
-
-devsite_only_links = ["tutorials", "/education", "/reference/python/cirq"]
-
-
-def on_missing_reference(app, env, node, contnode):
-    if node['reftarget'] in devsite_only_links:
-        return contnode
-    else:
-        return None


Test patch:
diff --git a/rtd_docs/docs_coverage_test.py b/rtd_docs/docs_coverage_test.py
deleted file mode 100644
--- a/rtd_docs/docs_coverage_test.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import inspect
-import pathlib
-from typing import Set, Dict, Tuple, Any, List
-
-import cirq
-
-
-def _all_public() -> Set[str]:
-    module_scopes = [
-        (cirq, 'cirq'),
-        (cirq.experiments, 'cirq.experiments'),
-        (cirq.google, 'cirq.google'),
-        (cirq.testing, 'cirq.testing'),
-    ]
-
-    by_name: Dict[str, Tuple[str, Any]] = {}
-    result: Set[str] = set()
-    for module, scope in module_scopes:
-        for name, obj in inspect.getmembers(module):
-            if name.startswith('_') or inspect.ismodule(obj):
-                continue
-
-            full_name = f'{scope}.{name}'
-            if name in by_name:
-                old_full_name, old_obj = by_name[name]
-                if obj is not old_obj:
-                    # coverage: ignore
-                    raise ValueError(f'Ambiguous name:\n{old_full_name}\n{full_name}\n')
-                if len(full_name) > len(old_full_name):
-                    continue
-
-                # coverage: ignore
-                result.remove(old_full_name)
-
-            by_name[name] = (full_name, obj)
-            result.add(full_name)
-    return result
-
-
-def _api_rst_fullnames_per_section() -> List[List[str]]:
-    result: List[List[str]] = []
-    section: List[str] = []
-    seen: Set[str] = set()
-    with open(pathlib.Path(__file__).parent / 'api.rst', mode='r') as f:
-        for line in f.readlines():
-            if line.strip() == '.. autosummary::':
-                if section:
-                    result.append(section)
-                    section = []
-            elif (
-                '    cirq.' in line
-                or '    .. autoclass:: cirq.' in line
-                or '    .. autofunction:: cirq.' in line
-            ):
-                fullname = line[line.find('cirq') :].strip()
-                if fullname in seen:
-                    # coverage: ignore
-                    raise ValueError(f'{fullname} appears twice in api.rst')
-                section.append(fullname)
-                seen.add(fullname)
-        if section:
-            result.append(section)
-    return result
-
-
-def test_public_values_equals_documented_values():
-    in_actual_api = _all_public()
-    in_api_reference = {
-        fullname for section in _api_rst_fullnames_per_section() for fullname in section
-    }
-    unlisted = in_actual_api - in_api_reference
-    hidden = {
-        fullname
-        for fullname in in_api_reference - in_actual_api
-        if not fullname.startswith('cirq.contrib.')
-    }
-    assert (
-        not unlisted
-    ), 'Public class/method/value not listed in rtd_docs/api.rst:\n    ' + '\n    '.join(
-        sorted(unlisted)
-    )
-    assert not hidden, (
-        'Private or non-existent class/method/value listed in rtd_docs/api.rst:'
-        '\n    ' + '\n    '.join(sorted(hidden))
-    )
-
-
-def test_api_rst_sorted():
-    def order(fullname: str) -> Any:
-        name = fullname.split('.')[-1]
-        start = fullname[: -len(name)]
-        return (
-            # First sort by package.
-            start,
-            # Then by tiny-ness (len=1 then len=2 then other).
-            min(len(name), 3),
-            # Then by type (constants then methods then classes).
-            name != name.upper(),
-            name != name.lower(),
-            # Then by name.
-            fullname,
-        )
-
-    for section in _api_rst_fullnames_per_section():
-        sorted_section = sorted(section, key=order)
-        assert (
-            section == sorted_section
-        ), f"A section in api.rst is not sorted. Should be:\n    " + '\n    '.join(sorted_section)