From 1481fc31f6500d899e2b8ea4f5ec5c80cfe36fbf Mon Sep 17 00:00:00 2001 From: Cristian Maureira-Fredes Date: Mon, 8 Feb 2021 16:16:19 +0100 Subject: doc: add tool to generate examples gallery This script generates a gallery for all the example currently in pyside-setup/examples. Using this tool will overwrite the index rst file for the examples located in 'sources/pyside6/doc/examples/index.rst Additionally, to display the code of each example, this will generate one extra .rst file for each example that contains a .pyproject file, for example: 'sources/pysides6/doc/examples/example_widgets__tetrix.rst' Currently, the usage of this tool is not incorporated in the documentation building process. Task-number: PYSIDE-1490 Change-Id: I78546d4c7905fd8b521f4112457980b4d1d56860 Reviewed-by: Christian Tismer Reviewed-by: Friedemann Kleint (cherry picked from commit e8eac85a5db8386689ffe174694f82c1e5dad854) Reviewed-by: Qt Cherry-pick Bot --- .../doc/_themes/pysidedocs/static/pyside.css | 8 +- .../doc/_themes/pysidedocs/static/pyside.css | 24 +- tools/example_gallery/main.py | 256 +++++++++++++++++++++ 3 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 tools/example_gallery/main.py diff --git a/sources/pyside6/doc/_themes/pysidedocs/static/pyside.css b/sources/pyside6/doc/_themes/pysidedocs/static/pyside.css index a9e7a0b05..625a2d39b 100644 --- a/sources/pyside6/doc/_themes/pysidedocs/static/pyside.css +++ b/sources/pyside6/doc/_themes/pysidedocs/static/pyside.css @@ -2044,6 +2044,8 @@ table.special { border-collapse: separate; border-spacing: 20px; line-height: 1.5em; + table-layout: fixed; + width: 80%; } .special p { @@ -2079,12 +2081,14 @@ table.special td { -moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; + overflow: hidden; } .special td:hover { - padding-top: 2px; - padding-bottom: 2px; + padding-top: 14px; + padding-bottom: 10px; border-bottom: 4px solid #41cd52; + overflow: hidden; } .command { diff --git a/sources/shiboken6/doc/_themes/pysidedocs/static/pyside.css b/sources/shiboken6/doc/_themes/pysidedocs/static/pyside.css index 64d7f1752..625a2d39b 100644 --- a/sources/shiboken6/doc/_themes/pysidedocs/static/pyside.css +++ b/sources/shiboken6/doc/_themes/pysidedocs/static/pyside.css @@ -52,7 +52,7 @@ div.body p.centered { margin-top: 25px; } -div.warning, div.seealso, div.note { +div.warning, div.seealso, div.note, div.important { padding: 6px 0px 6px 10px; border: none; } @@ -61,12 +61,18 @@ div.warning { background-color: #ffe4e4; } +div.important { + background-color: #fef9f3; + border-left: 5px solid #feeec8; +} + div.seealso { background-color: #fff2d6; } div.note { - background-color: #f3f3f4; + background-color: #c5d3f4; + border-left: 5px solid #7899f4; } table.docutils { @@ -346,7 +352,7 @@ tt.descname { #detailed-description dd > blockquote, #detailed-description dd > .field-list { - font-family: 'Droid Sans Mono'; + font-family: 'Droid Sans Mono', monospace; font-size: small; border-left: 10px solid #e2e2e2; padding-left: 10px; @@ -443,7 +449,7 @@ tt.descname { } #detailed-description .attribute td:nth-child(1) { - font-family: 'Droid Sans Mono'; + font-family: 'Droid Sans Mono', monospace; } /* Qt theme */ @@ -895,7 +901,7 @@ tt.descname { /* Legacy iOS */ } @font-face { - font-family:'Droid Sans Mono'; + font-family: 'Droid Sans Mono', monospace; font-style:normal; font-weight:400; src:local("Droid Sans Mono"),local("DroidSansMono"),url(//fonts.gstatic.com/s/droidsansmono/v7/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff) format("woff") @@ -2038,6 +2044,8 @@ table.special { border-collapse: separate; border-spacing: 20px; line-height: 1.5em; + table-layout: fixed; + width: 80%; } .special p { @@ -2073,12 +2081,14 @@ table.special td { -moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; + overflow: hidden; } .special td:hover { - padding-top: 2px; - padding-bottom: 2px; + padding-top: 14px; + padding-bottom: 10px; border-bottom: 4px solid #41cd52; + overflow: hidden; } .command { diff --git a/tools/example_gallery/main.py b/tools/example_gallery/main.py new file mode 100644 index 000000000..bdb983014 --- /dev/null +++ b/tools/example_gallery/main.py @@ -0,0 +1,256 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############### + + +""" +This tool reads all the examples from the main repository that have a +'.pyproject' file, and generates a special table/gallery in the documentation +page. + +For the usage, simply run: + python tools/example_gallery/main.py +since there is no special requirements. +""" + +import json +import math +from pathlib import Path +from textwrap import dedent + + +def ind(x): + return " " * 4 * x + + +def get_colgroup(columns, indent=2): + width = 80 # percentage + width_column = width // columns + return f'{ind(indent)}\n' * columns + + +def add_indent(s, level): + new_s = "" + for line in s.splitlines(): + if line.strip(): + new_s += f"{ind(level)}{line}\n" + else: + new_s += "\n" + return new_s + + +def get_module_gallery(examples): + """ + This function takes a list of dictionaries, that contain examples + information, from one specific module. + """ + + gallery = dedent(f"""\ + + +{get_colgroup(columns, indent=3)} + + """ + ) + + # Iteration per rows + for i in range(math.ceil(len(examples) / columns)): + gallery += f"{ind(1)}\n" + # Iteration per columns + for j in range(columns): + # We use a 'try-except' to handle when the examples are + # not an exact 'rows x columns', meaning that some cells + # will be empty. + try: + e = examples[i * columns + j] + url = e["rst"].replace(".rst", ".html") + name = e["example"] + underline = f'{e["module"]}' + if e["extra"]: + underline += f'/{e["extra"]}' + gallery += ( + f'{ind(2)}\n" + ) + except IndexError: + # We use display:none to hide the cell + gallery += f'{ind(2)}\n' + gallery += f"{ind(1)}\n" + + gallery += dedent("""\ +

{name}
' + f"({underline})

+ """ + ) + return gallery + + +def remove_licenses(s): + new_s = [] + for line in s.splitlines(): + if line.strip().startswith(("/*", "**", "##")): + continue + new_s.append(line) + return "\n".join(new_s) + + +if __name__ == "__main__": + # Only examples with a '.pyproject' file will be listed. + DIR = Path(__file__).parent + EXAMPLES_DOC = f"{DIR}/../../sources/pyside6/doc/examples" + EXAMPLES_DIR = Path(f"{DIR}/../../examples/") + columns = 5 + gallery = "" + + + # This main loop will be in charge of: + # * Getting all the .pyproject files, + # * Gather the information of the examples and store them in 'examples' + # * Read the .pyproject file to output the content of each file + # on the final .rst file for that specific example. + examples = {} + for f_path in EXAMPLES_DIR.glob("**/*.pyproject"): + if str(f_path).endswith("examples.pyproject"): + continue + + parts = f_path.parts[len(EXAMPLES_DIR.parts) : -1] + + module_name = parts[0] + example_name = parts[-1] + # handling subdirectories besides the module level and the example + extra_names = "" if len(parts) == 2 else "_".join(parts[1:-1]) + + rst_file = f"example_{module_name}_{extra_names}_{example_name}.rst" + + if module_name not in examples: + examples[module_name] = [] + + examples[module_name].append( + { + "example": example_name, + "module": module_name, + "extra": extra_names, + "rst": rst_file, + "abs_path": str(f_path), + } + ) + + pyproject = "" + with open(str(f_path), "r") as pyf: + pyproject = json.load(pyf) + + if pyproject: + with open(f"{EXAMPLES_DOC}/{rst_file}", "w") as out_f: + content_f = ( + "..\n This file was auto-generated by the 'examples_gallery' " + "script.\n Any change will be lost!\n\n" + ) + for project_file in pyproject["files"]: + if project_file.split(".")[-1] in ("png", "pyc"): + continue + length = len(project_file) + content_f += f"{project_file}\n{'=' * length}\n\n::\n\n" + + _path = f_path.resolve().parents[0] / project_file + _content = "" + with open(_path, "r") as _f: + _content = remove_licenses(_f.read()) + + content_f += add_indent(_content, 1) + content_f += "\n\n" + out_f.write(content_f) + print(f"Written: {EXAMPLES_DOC}/{rst_file}") + else: + print("Empty '.pyproject' file, skipping") + + base_content = dedent("""\ + .. + This file was auto-generated from the 'pyside-setup/tools/example_gallery' + All editions in this file will be lost. + + |project| Examples + =================== + + A collection of examples are provided with |project| to help new users + to understand different use cases of the module. + + .. toctree:: + :maxdepth: 1 + + tabbedbrowser.rst + ../pyside-examples/all-pyside-examples.rst + + Gallery + ------- + + You can find all these examples inside the ``pyside-setup`` on the ``examples`` + directory, or you can access them after installing |pymodname| from ``pip`` + inside the ``site-packages/PySide6/examples`` directory. + + .. raw:: html + + """ + ) + + # We generate a 'toctree' at the end of the file, to include the new + # 'example' rst files, so we get no warnings, and also that users looking + # for them will be able to, since they are indexed. + # Notice that :hidden: will not add the list of files by the end of the + # main examples HTML page. + footer_index = dedent("""\ + .. toctree:: + :hidden: + :maxdepth: 1 + + """ + ) + + # Writing the main example rst file. + index_files = [] + with open(f"{EXAMPLES_DOC}/index.rst", "w") as f: + f.write(base_content) + for module_name, e in sorted(examples.items()): + for i in e: + index_files.append(i["rst"]) + f.write(f"{ind(1)}

{module_name.title()}

\n") + f.write(add_indent(get_module_gallery(e), 1)) + f.write("\n\n") + f.write(footer_index) + for i in index_files: + f.write(f" {i}\n") + + print(f"Written index: {EXAMPLES_DOC}/index.rst") -- cgit v1.2.3