From 001607c41af918e444d1d72c2453e94bbd5bbaa0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 3 Aug 2022 12:21:43 -0400 Subject: [PATCH] Sync jinja sphinx theme templates from qiskit metapackage This commit syncs the jinja2 templates for the the sphinx layout and version/language table templates from the version in the metapackage. The metapackage has continued to develop these layout files independently to improve the layout of the hosted documentation and this commit syncs those local changes back to the theme as a precursor to unifying the theme across projects. At the same time the custom sphinx extension versionutils which is used to extract a version list for the stable version list. Some work will be needed to manage the sphinx extension interface to enable configuring the use of previous versions and the translation list. Right now these are hard coded in the plugin code, but before the sphinx extension can be used these will need to be configurable by a user and documented. --- qiskit_sphinx_theme/layout.html | 18 +- qiskit_sphinx_theme/stable_versions.html | 8 + qiskit_sphinx_theme/versions.html | 59 +++--- qiskit_sphinx_theme/versionutils.py | 247 +++++++++++++++++++++++ 4 files changed, 289 insertions(+), 43 deletions(-) create mode 100644 qiskit_sphinx_theme/stable_versions.html create mode 100644 qiskit_sphinx_theme/versionutils.py diff --git a/qiskit_sphinx_theme/layout.html b/qiskit_sphinx_theme/layout.html index 78e0a696..a2c1f194 100644 --- a/qiskit_sphinx_theme/layout.html +++ b/qiskit_sphinx_theme/layout.html @@ -97,7 +97,7 @@
  • - Qiskit Providers + Partners
  • @@ -127,7 +127,9 @@
  • - +
  • + Experiments +
  • - Github + GitHub
  • @@ -166,7 +168,6 @@ {% block extrabody %} {% endblock %} {# SIDE NAV, TOGGLES ON MOBILE #} - {% include "versions.html" %} @@ -331,7 +333,7 @@ Tutorials
  • - Qiskit Providers + Partners

  • @@ -354,9 +356,11 @@ Optimization
  • -
    - +
  • + Experiments +
  • +
  • Resources
  • diff --git a/qiskit_sphinx_theme/stable_versions.html b/qiskit_sphinx_theme/stable_versions.html new file mode 100644 index 00000000..db8bd5cb --- /dev/null +++ b/qiskit_sphinx_theme/stable_versions.html @@ -0,0 +1,8 @@ +
    +
    +
    Previous Releases
    + {% for version in version_list | sort(reverse=True) %} +
    {{ version }}
    + {% endfor %} +
    +
    diff --git a/qiskit_sphinx_theme/versions.html b/qiskit_sphinx_theme/versions.html index 4d78287a..d8699a59 100644 --- a/qiskit_sphinx_theme/versions.html +++ b/qiskit_sphinx_theme/versions.html @@ -1,37 +1,24 @@ -{% if READTHEDOCS %} -{# Add rst-badge after rst-versions for small badge style. #} -
    - - Read the Docs - v: {{ current_version }} - - -
    -
    -
    {{ _('Versions') }}
    - {% for slug, url in versions %} -
    {{ slug }}
    - {% endfor %} -
    -
    -
    {{ _('Downloads') }}
    - {% for type, url in downloads %} -
    {{ type }}
    - {% endfor %} -
    -
    -
    {{ _('On Read the Docs') }}
    -
    - {{ _('Project Home') }} -
    -
    - {{ _('Builds') }} -
    -
    -
    - {% trans %}Free document hosting provided by Read the Docs.{% endtrans %} - -
    +
    + + {{ language_label }} + + +
    + {% if translations %} +
    +
    {{ _('Languages') }}
    + {% for code, language in translations_list %} +
    {{ language }}
    + {% endfor %} +
    + {% endif %}
    -{% endif %} - + +
    diff --git a/qiskit_sphinx_theme/versionutils.py b/qiskit_sphinx_theme/versionutils.py new file mode 100644 index 00000000..af772976 --- /dev/null +++ b/qiskit_sphinx_theme/versionutils.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +import os +import re +import subprocess +import tempfile +from functools import partial + +from docutils import nodes +from docutils.parsers.rst.directives.tables import Table +from docutils.parsers.rst import Directive, directives +from sphinx.util import logging + + +logger = logging.getLogger(__name__) + +translations_list = [ + ('en', 'English'), + ('bn_BN', 'Bengali'), + ('fr_FR', 'French'), + ('de_DE', 'German'), + ('ja_JP', 'Japanese'), + ('ko_KR', 'Korean'), + ('pt_UN', 'Portuguese'), + ('es_UN', 'Spanish'), + ('ta_IN', 'Tamil'), +] + +default_language = 'en' + + +def setup(app): + app.connect('config-inited', _extend_html_context) + app.add_config_value('content_prefix', '', '') + app.add_config_value('translations', True, 'html') + app.add_directive('version-history', _VersionHistory) + + +def _extend_html_context(app, config): + context = config.html_context + context['translations'] = config.translations + context['translations_list'] = translations_list + context['version_list'] = _get_version_list() + context['current_translation'] = _get_current_translation(config) or config.language + context['translation_url'] = partial(_get_translation_url, config) + context['version_label'] = _get_version_label(config) + context['language_label'] = _get_language_label(config) + + +def _get_current_translation(config): + language = config.language or default_language + try: + found = next(v for k, v in translations_list if k == language) + except StopIteration: + found = None + return found + + +def _get_translation_url(config, code, pagename): + base = '/locale/%s' % code if code and code != default_language else '' + return _get_url(config, base, pagename) + + +def _get_version_label(config): + proc = subprocess.run( + ['git', 'describe', '--abbrev=0', '--tags', 'HEAD'], + encoding='utf8', capture_output=True) + return proc.stdout + +def _get_language_label(config): + return '%s' % (_get_current_translation(config) or config.language,) + + + +def _get_version_list(): + start_version = (0, 24, 0) + proc = subprocess.run(['git', 'describe', '--abbrev=0'], + capture_output=True) + proc.check_returncode() + current_version = proc.stdout.decode('utf8') + current_version_info = current_version.split('.') + if current_version_info[0] == '0': + version_list = [ + '0.%s' % x for x in range(start_version[1], + int(current_version_info[1]) + 1)] + else: + #TODO: When 1.0.0 add code to handle 0.x version list + version_list = [] + pass + # Prepend version 0.19 which was built and uploaded manually: + version_list.insert(0, '0.19') + return version_list + + +def _get_url(config, base, pagename): + return _add_content_prefix(config, '%s/%s.html' % (base, pagename)) + + +def _add_content_prefix(config, url): + prefix = '' + if config.content_prefix: + prefix = '/%s' % config.content_prefix + return '%s%s' % (prefix, url) + + +class _VersionHistory(Table): + + headers = ["Qiskit Metapackage Version", "qiskit-terra", "qiskit-aer", + "qiskit-ignis", "qiskit-ibmq-provider", "qiskit-aqua", + "Release Date"] + repo_root = os.path.abspath(os.path.dirname(__file__)) + + def _get_setup_py(self, version, git_dir): + cmd = ['git', 'show', '%s:setup.py' % version] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=git_dir) + stdout, stderr = proc.communicate() + if proc.returncode > 0: + logger.warn("%s failed with:\nstdout:\n%s\nstderr:\n%s\n" + % (cmd, stdout, stderr)) + return '' + return stdout.decode('utf8') + + def _get_date(self, version, git_dir): + cmd = ['git', 'log', '--format=%ai', str(version), '-1'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=git_dir) + stdout, stderr = proc.communicate() + if proc.returncode > 0: + logger.warn("%s failed with:\nstdout:\n%s\nstderr:\n%s\n" + % (cmd, stdout, stderr)) + return '' + return stdout.decode('utf8').split(' ')[0] + + def get_versions(self, tags, git_dir): + versions = {} + for tag in tags: + version = {} + setup_py = self._get_setup_py(tag, git_dir) + version['Release Date'] = self._get_date(tag, git_dir) + for package in self.headers[1:] + ['qiskit_terra']: + version_regex = re.compile(package + '[=|>]=(.*)\"') + match = version_regex.search(setup_py) + if match: + ver = match[1] + if '<' in match[1]: + ver = '>=' + ver + if package != 'qiskit_terra': + version[package] = ver + else: + version['qiskit-terra'] = ver + if version: + versions[tag] = version + return versions + + def build_table(self, versions): + table = nodes.table() + table['classes'] += ['colwidths-auto'] + tgroup = nodes.tgroup(cols=len(self.headers)) + table += tgroup + self.options['widths'] = [30, 15, 15, 15, 20, 15] + tgroup.extend( + nodes.colspec(colwidth=col_width, colname='c' + str(idx)) + for idx, col_width in enumerate(self.col_widths) + ) + + thead = nodes.thead() + tgroup += thead + + row_node = nodes.row() + thead += row_node + row_node.extend(nodes.entry(h, nodes.paragraph(text=h)) + for h in self.headers) + + tbody = nodes.tbody() + tgroup += tbody + + rows = [] + for version in versions: + row_node = nodes.row() + entry = nodes.entry() + entry += nodes.paragraph(text=version) + row_node += entry + for cell in self.headers[1:]: + if cell in versions[version]: + entry = nodes.entry() + text = versions[version][cell] + entry += nodes.paragraph(text=text) + else: + entry = nodes.entry() + row_node += entry + rows.append(row_node) + tbody.extend(rows) + return table + + def run(self): + with tempfile.TemporaryDirectory() as tmp_dir: + tags, git_dir = _get_qiskit_metapackage_git_tags(tmp_dir) + versions = self.get_versions(tags, git_dir) + self.max_cols = len(self.headers) + self.col_widths = self.get_column_widths(self.max_cols) + table_node = self.build_table(versions) + title, messages = self.make_title() + if title: + table_node.insert(0, title) + return [table_node] + messages + + +def _get_qiskit_metapackage_git_tags(tmp_dir): + cmd = ['git', 'clone', 'https://github.com/Qiskit/qiskit.git'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, cwd=tmp_dir) + stdout, stderr = proc.communicate() + if proc.returncode > 0: + logger.warn("%s failed with:\nstdout:\n%s\nstderr:\n%s\n" + % (cmd, stdout, stderr)) + return [] + else: + + return _get_git_tags(os.path.join(tmp_dir, 'qiskit')) + + +def _get_git_tags(git_dir): + cmd = ['git', 'tag', '--sort=-creatordate'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, cwd=git_dir) + stdout, stderr = proc.communicate() + if proc.returncode > 0: + logger.warn("%s failed with:\nstdout:\n%s\nstderr:\n%s\n" + % (cmd, stdout, stderr)) + return [] + + return stdout.decode('utf8').splitlines(), git_dir