aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/create_changelog.py2
-rw-r--r--tools/example_gallery/main.py225
-rw-r--r--tools/missing_bindings.py456
-rw-r--r--tools/missing_bindings/config.py173
-rw-r--r--tools/missing_bindings/main.py330
-rw-r--r--tools/missing_bindings/requirements.txt8
-rw-r--r--tools/snippets_translate/README.md151
-rw-r--r--tools/snippets_translate/converter.py334
-rw-r--r--tools/snippets_translate/handlers.py519
-rw-r--r--tools/snippets_translate/main.py466
-rw-r--r--tools/snippets_translate/parse_utils.py145
-rw-r--r--tools/snippets_translate/requirements.txt2
-rw-r--r--tools/snippets_translate/snippets_translate.pyproject3
-rw-r--r--tools/snippets_translate/tests/test_converter.py439
14 files changed, 2718 insertions, 535 deletions
diff --git a/tools/create_changelog.py b/tools/create_changelog.py
index 914b87cc1..539df3fe2 100644
--- a/tools/create_changelog.py
+++ b/tools/create_changelog.py
@@ -64,7 +64,7 @@ information about a particular change.
"""
shiboken_header = """****************************************************************************
-* Shiboken2 *
+* Shiboken6 *
****************************************************************************
"""
diff --git a/tools/example_gallery/main.py b/tools/example_gallery/main.py
index afacab628..332f2a2f5 100644
--- a/tools/example_gallery/main.py
+++ b/tools/example_gallery/main.py
@@ -38,7 +38,7 @@
###############
-DESCRIPTION = """
+"""
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.
@@ -51,21 +51,30 @@ since there is no special requirements.
from argparse import ArgumentParser, RawTextHelpFormatter
import json
import math
+import shutil
from pathlib import Path
from textwrap import dedent
opt_quiet = False
+suffixes = {
+ ".py": "py",
+ ".qml": "js",
+ ".conf": "ini",
+ ".qrc": "xml",
+ ".ui": "xml",
+ ".xbel": "xml",
+}
def ind(x):
return " " * 4 * x
-def get_colgroup(columns, indent=2):
- width = 80 # percentage
- width_column = width // columns
- return f'{ind(indent)}<col style="width: {width_column}%" />\n' * columns
+def get_lexer(suffix):
+ if suffix in suffixes:
+ return suffixes[suffix]
+ return "text"
def add_indent(s, level):
@@ -84,43 +93,39 @@ def get_module_gallery(examples):
information, from one specific module.
"""
- gallery = dedent(f"""\
- <table class="special">
- <colgroup>
-{get_colgroup(columns, indent=3)}
- </colgroup>
- """
+ gallery = (
+ ".. panels::\n"
+ f"{ind(1)}:container: container-lg pb-3\n"
+ f"{ind(1)}:column: col-lg-4 col-md-4 col-sm-6 col-xs-12 p-2\n\n"
)
# Iteration per rows
- for i in range(math.ceil(len(examples) / columns)):
- gallery += f"{ind(1)}<tr>\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)}<td><a href="{url}"><p><strong>{name}</strong><br/>'
- f"({underline})</p></a></td>\n"
- )
- except IndexError:
- # We use display:none to hide the cell
- gallery += f'{ind(2)}<td style="display: none;"></td>\n'
- gallery += f"{ind(1)}</tr>\n"
-
- gallery += dedent("""\
- </table>
- """
- )
- return gallery
+ for i in range(math.ceil(len(examples))):
+ e = examples[i]
+ url = e["rst"].replace(".rst", ".html")
+ name = e["example"]
+ underline = f'{e["module"]}'
+
+
+ if e["extra"]:
+ underline += f'/{e["extra"]}'
+
+ if i > 0:
+ gallery += f"{ind(1)}---\n"
+ elif e["img_doc"]:
+ gallery += f"{ind(1)}---\n"
+
+ if e["img_doc"]:
+ gallery += f"{ind(1)}:img-top: {e['img_doc'].name}\n\n"
+ else:
+ gallery += "\n"
+
+
+ gallery += f"{ind(1)}`{name} <{url}>`_\n"
+ gallery += f"{ind(1)}+++\n"
+ gallery += f"{ind(1)}{underline}\n"
+
+ return f"{gallery}\n"
def remove_licenses(s):
@@ -132,18 +137,54 @@ def remove_licenses(s):
return "\n".join(new_s)
+def get_code_tabs(files, project_file):
+ content = "\n"
+
+ for i, project_file in enumerate(files):
+ pfile = Path(project_file)
+ if pfile.suffix in (".png", ".pyc"):
+ continue
+
+ content += f".. tabbed:: {project_file}\n\n"
+
+ lexer = get_lexer(pfile.suffix)
+ content += add_indent(f".. code-block:: {lexer}", 1)
+ content += "\n"
+
+ _path = f_path.resolve().parents[0] / project_file
+ _file_content = ""
+ with open(_path, "r") as _f:
+ _file_content = remove_licenses(_f.read())
+
+ content += add_indent(_file_content, 2)
+ content += "\n\n"
+ return content
+
+
+def get_header_title(f_path):
+ _title = f_path.stem
+ url_name = "/".join(f_path.parts[f_path.parts.index("examples")+1:-1])
+ url = f"{BASE_URL}/{url_name}"
+ return (
+ "..\n This file was auto-generated by the 'examples_gallery' "
+ "script.\n Any change will be lost!\n\n"
+ f"{_title}\n"
+ f"{'=' * len(_title)}\n\n"
+ f"(You can also check this code `in the repository <{url}>`_)\n\n"
+ )
+
+
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_DOC = Path(f"{DIR}/../../sources/pyside6/doc/examples")
EXAMPLES_DIR = Path(f"{DIR}/../../examples/")
+ BASE_URL = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples"
columns = 5
gallery = ""
- parser = ArgumentParser(description=DESCRIPTION,
- formatter_class=RawTextHelpFormatter)
- parser.add_argument('--quiet', '-q', action='store_true',
- help='Quiet')
+ parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
+ parser.add_argument("--quiet", "-q", action="store_true", help="Quiet")
options = parser.parse_args()
opt_quiet = options.quiet
@@ -153,11 +194,16 @@ if __name__ == "__main__":
# * Read the .pyproject file to output the content of each file
# on the final .rst file for that specific example.
examples = {}
+
+ # Create the 'examples' directory if it doesn't exist
+ if not EXAMPLES_DOC.is_dir():
+ EXAMPLES_DOC.mkdir()
+
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]
+ parts = f_path.parts[len(EXAMPLES_DIR.parts):-1]
module_name = parts[0]
example_name = parts[-1]
@@ -166,6 +212,27 @@ if __name__ == "__main__":
rst_file = f"example_{module_name}_{extra_names}_{example_name}.rst"
+ def check_img_ext(i):
+ EXT = (".png", ".jpg", ".jpeg")
+ if i.suffix in EXT:
+ return True
+ return False
+
+ # Check for a 'doc' directory inside the example
+ has_doc = False
+ img_doc = None
+ original_doc_dir = Path(f_path.parent / "doc")
+ if original_doc_dir.is_dir():
+ has_doc = True
+ images = [i for i in original_doc_dir.glob("*") if i.is_file() and check_img_ext(i)]
+ if len(images) > 0:
+ # We look for an image with the same example_name first, if not, we select the first
+ image_path = [i for i in images if example_name in str(i)]
+ if not image_path:
+ image_path = images[0]
+ else:
+ img_doc = image_path[0]
+
if module_name not in examples:
examples[module_name] = []
@@ -176,6 +243,8 @@ if __name__ == "__main__":
"extra": extra_names,
"rst": rst_file,
"abs_path": str(f_path),
+ "has_doc": has_doc,
+ "img_doc": img_doc,
}
)
@@ -184,32 +253,41 @@ if __name__ == "__main__":
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"
+ rst_file_full = EXAMPLES_DOC / rst_file
+
+ with open(rst_file_full, "w") as out_f:
+ if has_doc:
+ doc_path = Path(f_path.parent) / "doc"
+ doc_rst = doc_path / f"{example_name}.rst"
+
+ with open(doc_rst) as doc_f:
+ content_f = doc_f.read()
+
+ # Copy other files in the 'doc' directory, but
+ # excluding the main '.rst' file and all the
+ # directories.
+ for _f in doc_path.glob("*"):
+ if _f == doc_rst or _f.is_dir():
+ continue
+ src = _f
+ dst = EXAMPLES_DOC / _f.name
+
+ resource_written = shutil.copy(src, dst)
+ if not opt_quiet:
+ print("Written resource:", resource_written)
+ else:
+ content_f = get_header_title(f_path)
+ content_f += get_code_tabs(pyproject["files"], out_f)
out_f.write(content_f)
+
if not opt_quiet:
print(f"Written: {EXAMPLES_DOC}/{rst_file}")
else:
if not opt_quiet:
print("Empty '.pyproject' file, skipping")
- base_content = dedent("""\
+ base_content = dedent(
+ """\
..
This file was auto-generated from the 'pyside-setup/tools/example_gallery'
All editions in this file will be lost.
@@ -220,21 +298,10 @@ if __name__ == "__main__":
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
-
"""
)
@@ -243,7 +310,8 @@ if __name__ == "__main__":
# 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("""\
+ footer_index = dedent(
+ """\
.. toctree::
:hidden:
:maxdepth: 1
@@ -258,8 +326,9 @@ if __name__ == "__main__":
for module_name, e in sorted(examples.items()):
for i in e:
index_files.append(i["rst"])
- f.write(f"{ind(1)}<h3>{module_name.title()}</h3>\n")
- f.write(add_indent(get_module_gallery(e), 1))
+ f.write(f"{module_name.title()}\n")
+ f.write(f"{'*' * len(module_name.title())}\n")
+ f.write(get_module_gallery(e))
f.write("\n\n")
f.write(footer_index)
for i in index_files:
diff --git a/tools/missing_bindings.py b/tools/missing_bindings.py
deleted file mode 100644
index 63314c1ab..000000000
--- a/tools/missing_bindings.py
+++ /dev/null
@@ -1,456 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2017 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 script is used to generate a summary of missing types / classes
-# which are present in C++ Qt5, but are missing in PySide6.
-#
-# Required packages: bs4
-# Installed via: pip install bs4
-#
-# The script uses beautiful soup 4 to parse out the class names from
-# the online Qt documentation. It then tries to import the types from
-# PySide6.
-#
-# Example invocation of script:
-# python missing_bindings.py --qt-version 5.9 -w all
-# --qt-version - specify which version of qt documentation to load.
-# -w - if PyQt5 is an installed package, check if the tested
-# class also exists there.
-
-try:
- import urllib.request as urllib2
-except ImportError:
- import urllib2
-
-import argparse
-from bs4 import BeautifulSoup
-from collections import OrderedDict
-from time import gmtime, strftime
-import sys
-import os.path
-
-modules_to_test = OrderedDict()
-
-# Essentials
-modules_to_test['QtCore'] = 'qtcore-module.html'
-modules_to_test['QtGui'] = 'qtgui-module.html'
-modules_to_test['QtMultimedia'] = 'qtmultimedia-module.html'
-modules_to_test['QtMultimediaWidgets'] = 'qtmultimediawidgets-module.html'
-modules_to_test['QtNetwork'] = 'qtnetwork-module.html'
-modules_to_test['QtQml'] = 'qtqml-module.html'
-modules_to_test['QtQuick'] = 'qtquick-module.html'
-modules_to_test['QtQuickWidgets'] = 'qtquickwidgets-module.html'
-modules_to_test['QtSql'] = 'qtsql-module.html'
-modules_to_test['QtTest'] = 'qttest-module.html'
-modules_to_test['QtWidgets'] = 'qtwidgets-module.html'
-
-# Addons
-modules_to_test['Qt3DCore'] = 'qt3dcore-module.html'
-modules_to_test['Qt3DInput'] = 'qt3dinput-module.html'
-modules_to_test['Qt3DLogic'] = 'qt3dlogic-module.html'
-modules_to_test['Qt3DRender'] = 'qt3drender-module.html'
-modules_to_test['Qt3DAnimation'] = 'qt3danimation-module.html'
-modules_to_test['Qt3DExtras'] = 'qt3dextras-module.html'
-modules_to_test['QtConcurrent'] = 'qtconcurrent-module.html'
-#modules_to_test['QtNetworkAuth'] = 'qtnetworkauth-module.html'
-modules_to_test['QtHelp'] = 'qthelp-module.html'
-modules_to_test['QtLocation'] = 'qtlocation-module.html'
-modules_to_test['QtPrintSupport'] = 'qtprintsupport-module.html'
-modules_to_test['QtScxml'] = 'qtscxml-module.html'
-#modules_to_test['QtSpeech'] = 'qtspeech-module.html'
-modules_to_test['QtSvg'] = 'qtsvg-module.html'
-modules_to_test['QtUiTools'] = 'qtuitools-module.html'
-modules_to_test['QtWebChannel'] = 'qtwebchannel-module.html'
-modules_to_test['QtWebEngine'] = 'qtwebengine-module.html'
-modules_to_test['QtWebEngineCore'] = 'qtwebenginecore-module.html'
-modules_to_test['QtWebEngineWidgets'] = 'qtwebenginewidgets-module.html'
-modules_to_test['QtWebSockets'] = 'qtwebsockets-module.html'
-modules_to_test['QtMacExtras'] = 'qtmacextras-module.html'
-modules_to_test['QtX11Extras'] = 'qtx11extras-module.html'
-modules_to_test['QtWinExtras'] = 'qtwinextras-module.html'
-modules_to_test['QtXml'] = 'qtxml-module.html'
-modules_to_test['QtXmlPatterns'] = 'qtxmlpatterns-module.html'
-modules_to_test['QtCharts'] = 'qtcharts-module.html'
-modules_to_test['QtDataVisualization'] = 'qtdatavisualization-module.html'
-modules_to_test['QtOpenGL'] = 'qtopengl-module.html'
-modules_to_test['QtPositioning'] = 'qtpositioning-module.html'
-modules_to_test['QtRemoteObjects'] = 'qtremoteobjects-module.html'
-modules_to_test['QtScriptTools'] = 'qtscripttools-module.html'
-modules_to_test['QtSensors'] = 'qtsensors-module.html'
-modules_to_test['QtSerialPort'] = 'qtserialport-module.html'
-types_to_ignore = set()
-# QtCore
-types_to_ignore.add('QFlag')
-types_to_ignore.add('QFlags')
-types_to_ignore.add('QGlobalStatic')
-types_to_ignore.add('QDebug')
-types_to_ignore.add('QDebugStateSaver')
-types_to_ignore.add('QMetaObject.Connection')
-types_to_ignore.add('QPointer')
-types_to_ignore.add('QAssociativeIterable')
-types_to_ignore.add('QSequentialIterable')
-types_to_ignore.add('QStaticPlugin')
-types_to_ignore.add('QChar')
-types_to_ignore.add('QLatin1Char')
-types_to_ignore.add('QHash')
-types_to_ignore.add('QMultiHash')
-types_to_ignore.add('QLinkedList')
-types_to_ignore.add('QList')
-types_to_ignore.add('QMap')
-types_to_ignore.add('QMultiMap')
-types_to_ignore.add('QMap.key_iterator')
-types_to_ignore.add('QPair')
-types_to_ignore.add('QQueue')
-types_to_ignore.add('QScopedArrayPointer')
-types_to_ignore.add('QScopedPointer')
-types_to_ignore.add('QScopedValueRollback')
-types_to_ignore.add('QMutableSetIterator')
-types_to_ignore.add('QSet')
-types_to_ignore.add('QSet.const_iterator')
-types_to_ignore.add('QSet.iterator')
-types_to_ignore.add('QExplicitlySharedDataPointer')
-types_to_ignore.add('QSharedData')
-types_to_ignore.add('QSharedDataPointer')
-types_to_ignore.add('QEnableSharedFromThis')
-types_to_ignore.add('QSharedPointer')
-types_to_ignore.add('QWeakPointer')
-types_to_ignore.add('QStack')
-types_to_ignore.add('QLatin1String')
-types_to_ignore.add('QString')
-types_to_ignore.add('QStringRef')
-types_to_ignore.add('QStringList')
-types_to_ignore.add('QStringMatcher')
-types_to_ignore.add('QVarLengthArray')
-types_to_ignore.add('QVector')
-types_to_ignore.add('QFutureIterator')
-types_to_ignore.add('QHashIterator')
-types_to_ignore.add('QMutableHashIterator')
-types_to_ignore.add('QLinkedListIterator')
-types_to_ignore.add('QMutableLinkedListIterator')
-types_to_ignore.add('QListIterator')
-types_to_ignore.add('QMutableListIterator')
-types_to_ignore.add('QMapIterator')
-types_to_ignore.add('QMutableMapIterator')
-types_to_ignore.add('QSetIterator')
-types_to_ignore.add('QMutableVectorIterator')
-types_to_ignore.add('QVectorIterator')
-
-# QtGui
-types_to_ignore.add('QIconEnginePlugin')
-types_to_ignore.add('QImageIOPlugin')
-types_to_ignore.add('QGenericPlugin')
-types_to_ignore.add('QGenericPluginFactory')
-types_to_ignore.add('QGenericMatrix')
-types_to_ignore.add('QOpenGLExtraFunctions')
-types_to_ignore.add('QOpenGLFunctions')
-types_to_ignore.add('QOpenGLFunctions_1_0')
-types_to_ignore.add('QOpenGLFunctions_1_1')
-types_to_ignore.add('QOpenGLFunctions_1_2')
-types_to_ignore.add('QOpenGLFunctions_1_3')
-types_to_ignore.add('QOpenGLFunctions_1_4')
-types_to_ignore.add('QOpenGLFunctions_1_5')
-types_to_ignore.add('QOpenGLFunctions_2_0')
-types_to_ignore.add('QOpenGLFunctions_2_1')
-types_to_ignore.add('QOpenGLFunctions_3_0')
-types_to_ignore.add('QOpenGLFunctions_3_1')
-types_to_ignore.add('QOpenGLFunctions_3_2_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_3_2_Core')
-types_to_ignore.add('QOpenGLFunctions_3_3_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_3_3_Core')
-types_to_ignore.add('QOpenGLFunctions_4_0_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_4_0_Core')
-types_to_ignore.add('QOpenGLFunctions_4_1_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_4_1_Core')
-types_to_ignore.add('QOpenGLFunctions_4_2_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_4_2_Core')
-types_to_ignore.add('QOpenGLFunctions_4_3_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_4_3_Core')
-types_to_ignore.add('QOpenGLFunctions_4_4_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_4_4_Core')
-types_to_ignore.add('QOpenGLFunctions_4_5_Compatibility')
-types_to_ignore.add('QOpenGLFunctions_4_5_Core')
-types_to_ignore.add('QOpenGLFunctions_ES2')
-
-# QtWidgets
-types_to_ignore.add('QItemEditorCreator')
-types_to_ignore.add('QStandardItemEditorCreator')
-types_to_ignore.add('QStylePlugin')
-
-# QtSql
-types_to_ignore.add('QSqlDriverCreator')
-types_to_ignore.add('QSqlDriverPlugin')
-
-qt_documentation_website_prefixes = OrderedDict()
-qt_documentation_website_prefixes['5.6'] = 'http://doc.qt.io/qt-5.6/'
-qt_documentation_website_prefixes['5.8'] = 'http://doc.qt.io/qt-5.8/'
-qt_documentation_website_prefixes['5.9'] = 'http://doc.qt.io/qt-5.9/'
-qt_documentation_website_prefixes['5.10'] = 'http://doc.qt.io/qt-5.10/'
-qt_documentation_website_prefixes['5.11'] = 'http://doc.qt.io/qt-5.11/'
-qt_documentation_website_prefixes['5.11'] = 'http://doc.qt.io/qt-5.11/'
-qt_documentation_website_prefixes['5.12'] = 'http://doc.qt.io/qt-5.12/'
-qt_documentation_website_prefixes['5.13'] = 'http://doc.qt.io/qt-5.13/'
-qt_documentation_website_prefixes['5.14'] = 'http://doc.qt.io/qt-5.14/'
-qt_documentation_website_prefixes['5.15'] = 'http://doc.qt.io/qt-5/'
-qt_documentation_website_prefixes['dev'] = 'http://doc-snapshots.qt.io/qt5-dev/'
-
-
-def qt_version_to_doc_prefix(version):
- if version in qt_documentation_website_prefixes:
- return qt_documentation_website_prefixes[version]
- else:
- raise RuntimeError("The specified qt version is not supported")
-
-
-def create_doc_url(module_doc_page_url, version):
- return qt_version_to_doc_prefix(version) + module_doc_page_url
-
-parser = argparse.ArgumentParser()
-parser.add_argument("module",
- default='all',
- choices=list(modules_to_test.keys()).append('all'),
- nargs='?',
- type=str,
- help="the Qt module for which to get the missing types")
-parser.add_argument("--qt-version",
- "-v",
- default='5.15',
- choices=['5.6', '5.9', '5.11', '5.12', '5.13', '5.14', '5.15', 'dev'],
- type=str,
- dest='version',
- help="the Qt version to use to check for types")
-parser.add_argument("--which-missing",
- "-w",
- default='all',
- choices=['all', 'in-pyqt', 'not-in-pyqt'],
- type=str,
- dest='which_missing',
- help="Which missing types to show (all, or just those "
- "that are not present in PyQt)")
-
-args = parser.parse_args()
-
-if hasattr(args, "module") and args.module != 'all':
- saved_value = modules_to_test[args.module]
- modules_to_test.clear()
- modules_to_test[args.module] = saved_value
-
-pyside_package_name = "PySide6"
-pyqt_package_name = "PyQt5"
-
-total_missing_types_count = 0
-total_missing_types_count_compared_to_pyqt = 0
-total_missing_modules_count = 0
-
-wiki_file = open('missing_bindings_for_wiki_qt_io.txt', 'w')
-wiki_file.truncate()
-
-
-def log(*pargs, **kw):
- print(*pargs)
-
- computed_str = ''
- for arg in pargs:
- computed_str += str(arg)
-
- style = 'text'
- if 'style' in kw:
- style = kw['style']
-
- if style == 'heading1':
- computed_str = '= ' + computed_str + ' ='
- elif style == 'heading5':
- computed_str = '===== ' + computed_str + ' ====='
- elif style == 'with_newline':
- computed_str += '\n'
- elif style == 'bold_colon':
- computed_str = computed_str.replace(':', ":'''")
- computed_str += "'''"
- computed_str += '\n'
- elif style == 'error':
- computed_str = "''" + computed_str.strip('\n') + "''\n"
- elif style == 'text_with_link':
- computed_str = computed_str
- elif style == 'code':
- computed_str = ' ' + computed_str
- elif style == 'end':
- return
-
- print(computed_str, file=wiki_file)
-
-log('PySide6 bindings for Qt {}'.format(args.version), style='heading1')
-
-log("""Using Qt version {} documentation to find public API Qt types and test
-if the types are present in the PySide6 package.""".format(args.version))
-
-log("""Results are usually stored at
-https://wiki.qt.io/PySide6_Missing_Bindings
-so consider taking the contents of the generated
-missing_bindings_for_wiki_qt_io.txt file and updating the linked wiki page.""",
-style='end')
-
-log("""Similar report:
-https://gist.github.com/ethanhs/6c626ca4e291f3682589699296377d3a""",
-style='text_with_link')
-
-python_executable = os.path.basename(sys.executable or '')
-command_line_arguments = ' '.join(sys.argv)
-report_date = strftime("%Y-%m-%d %H:%M:%S %Z", gmtime())
-
-log("""
-This report was generated by running the following command:
- {} {}
-on the following date:
- {}
-""".format(python_executable, command_line_arguments, report_date))
-
-for module_name in modules_to_test.keys():
- log(module_name, style='heading5')
-
- url = create_doc_url(modules_to_test[module_name], args.version)
- log('Documentation link: {}\n'.format(url), style='text_with_link')
-
- # Import the tested module
- try:
- pyside_tested_module = getattr(__import__(pyside_package_name,
- fromlist=[module_name]), module_name)
- except Exception as e:
- log('\nCould not load {}.{}. Received error: {}. Skipping.\n'.format(
- pyside_package_name, module_name, str(e).replace("'", '')),
- style='error')
- total_missing_modules_count += 1
- continue
-
- try:
- pyqt_module_name = module_name
- if module_name == "QtCharts":
- pyqt_module_name = module_name[:-1]
-
- pyqt_tested_module = getattr(__import__(pyqt_package_name,
- fromlist=[pyqt_module_name]), pyqt_module_name)
- except Exception as e:
- log("\nCould not load {}.{} for comparison. "
- "Received error: {}.\n".format(pyqt_package_name, module_name,
- str(e).replace("'", '')), style='error')
-
- # Get C++ class list from documentation page.
- page = urllib2.urlopen(url)
- soup = BeautifulSoup(page, 'html.parser')
-
- # Extract the Qt type names from the documentation classes table
- links = soup.body.select('.annotated a')
- types_on_html_page = []
-
- for link in links:
- link_text = link.text
- link_text = link_text.replace('::', '.')
- if link_text not in types_to_ignore:
- types_on_html_page.append(link_text)
-
- log('Number of types in {}: {}'.format(module_name,
- len(types_on_html_page)), style='bold_colon')
-
- missing_types_count = 0
- missing_types_compared_to_pyqt = 0
- missing_types = []
- for qt_type in types_on_html_page:
- try:
- pyside_qualified_type = 'pyside_tested_module.'
-
- if "QtCharts" == module_name:
- pyside_qualified_type += 'QtCharts.'
- elif "DataVisualization" in module_name:
- pyside_qualified_type += 'QtDataVisualization.'
-
- pyside_qualified_type += qt_type
- eval(pyside_qualified_type)
- except:
- missing_type = qt_type
- missing_types_count += 1
- total_missing_types_count += 1
-
- is_present_in_pyqt = False
- try:
- pyqt_qualified_type = 'pyqt_tested_module.'
-
- if "Charts" in module_name:
- pyqt_qualified_type += 'QtCharts.'
- elif "DataVisualization" in module_name:
- pyqt_qualified_type += 'QtDataVisualization.'
-
- pyqt_qualified_type += qt_type
- eval(pyqt_qualified_type)
- missing_type += " (is present in PyQt5)"
- missing_types_compared_to_pyqt += 1
- total_missing_types_count_compared_to_pyqt += 1
- is_present_in_pyqt = True
- except:
- pass
-
- if args.which_missing == 'all':
- missing_types.append(missing_type)
- elif args.which_missing == 'in-pyqt' and is_present_in_pyqt:
- missing_types.append(missing_type)
- elif (args.which_missing == 'not-in-pyqt' and
- not is_present_in_pyqt):
- missing_types.append(missing_type)
-
- if len(missing_types) > 0:
- log('Missing types in {}:'.format(module_name), style='with_newline')
- missing_types.sort()
- for missing_type in missing_types:
- log(missing_type, style='code')
- log('')
-
- log('Number of missing types: {}'.format(missing_types_count),
- style='bold_colon')
- if len(missing_types) > 0:
- log('Number of missing types that are present in PyQt5: {}'
- .format(missing_types_compared_to_pyqt), style='bold_colon')
- log('End of missing types for {}\n'.format(module_name), style='end')
- else:
- log('', style='end')
-
-log('Summary', style='heading5')
-log('Total number of missing types: {}'.format(total_missing_types_count),
- style='bold_colon')
-log('Total number of missing types that are present in PyQt5: {}'
- .format(total_missing_types_count_compared_to_pyqt), style='bold_colon')
-log('Total number of missing modules: {}'
- .format(total_missing_modules_count), style='bold_colon')
-wiki_file.close()
diff --git a/tools/missing_bindings/config.py b/tools/missing_bindings/config.py
new file mode 100644
index 000000000..23a733463
--- /dev/null
+++ b/tools/missing_bindings/config.py
@@ -0,0 +1,173 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+
+modules_to_test = {
+ # 6.0
+ 'QtCore': 'qtcore-module.html',
+ 'QtGui': 'qtgui-module.html',
+ 'QtNetwork': 'qtnetwork-module.html',
+ 'QtQml': 'qtqml-module.html',
+ 'QtQuick': 'qtquick-module.html',
+ 'QtQuickWidgets': 'qtquickwidgets-module.html',
+ 'QtQuickControls2': 'qtquickcontrols2-module.html',
+ #QtQuick3D - no python bindings
+ 'QtSql': 'qtsql-module.html',
+ 'QtWidgets': 'qtwidgets-module.html',
+ 'QtConcurrent': 'qtconcurrent-module.html',
+ #QtDBUS - no python bindings
+ 'QtHelp': 'qthelp-module.html',
+ 'QtOpenGL': 'qtopengl-module.html',
+ 'QtPrintSupport': 'qtprintsupport-module.html',
+ 'QtSvg': 'qtsvg-module.html',
+ 'QtUiTools': 'qtuitools-module.html',
+ 'QtXml': 'qtxml-module.html',
+ 'QtTest': 'qttest-module.html',
+ #'QtXmlPatterns': 'qtxmlpatterns-module.html', # in Qt5 compat
+ 'Qt3DCore': 'qt3dcore-module.html',
+ 'Qt3DInput': 'qt3dinput-module.html',
+ 'Qt3DLogic': 'qt3dlogic-module.html',
+ 'Qt3DRender': 'qt3drender-module.html',
+ 'Qt3DAnimation': 'qt3danimation-module.html',
+ 'Qt3DExtras': 'qt3dextras-module.html',
+ #'QtNetworkAuth': 'qtnetworkauth-module.html', # no python bindings
+ #'QtCoAp' -- TODO
+ #'QtMqtt' -- TODO
+ #'QtOpcUA' -- TODO
+
+ # 6.1
+ #'QtScxml': 'qtscxml-module.html',
+ #'QtCharts': 'qtcharts-module.html',
+ #'QtDataVisualization': 'qtdatavisualization-module.html',
+
+ # 6.2
+ #'QtPositioning': 'qtpositioning-module.html',
+ #'QtMultimedia': 'qtmultimedia-module.html',
+ #'QtRemoteObjects': 'qtremoteobjects-module.html',
+ #'QtSensors': 'qtsensors-module.html',
+ #'QtSerialPort': 'qtserialport-module.html',
+ #'QtWebChannel': 'qtwebchannel-module.html',
+ #'QtWebEngine': 'qtwebengine-module.html',
+ #'QtWebEngineCore': 'qtwebenginecore-module.html',
+ #'QtWebEngineWidgets': 'qtwebenginewidgets-module.html',
+ #'QtWebSockets': 'qtwebsockets-module.html',
+
+ # 6.x
+ #'QtSpeech': 'qtspeech-module.html',
+ #'QtMultimediaWidgets': 'qtmultimediawidgets-module.html',
+ #'QtLocation': 'qtlocation-module.html',
+
+ # Not in 6
+ #'QtScriptTools': 'qtscripttools-module.html',
+ #'QtMacExtras': 'qtmacextras-module.html',
+ #'QtX11Extras': 'qtx11extras-module.html',
+ #'QtWinExtras': 'qtwinextras-module.html',
+}
+
+types_to_ignore = {
+ # QtCore
+ 'QFlag',
+ 'QFlags',
+ 'QGlobalStatic',
+ 'QDebug',
+ 'QDebugStateSaver',
+ 'QMetaObject.Connection',
+ 'QPointer',
+ 'QAssociativeIterable',
+ 'QSequentialIterable',
+ 'QStaticPlugin',
+ 'QChar',
+ 'QLatin1Char',
+ 'QHash',
+ 'QMultiHash',
+ 'QLinkedList',
+ 'QList',
+ 'QMap',
+ 'QMultiMap',
+ 'QMap.key_iterator',
+ 'QPair',
+ 'QQueue',
+ 'QScopedArrayPointer',
+ 'QScopedPointer',
+ 'QScopedValueRollback',
+ 'QMutableSetIterator',
+ 'QSet',
+ 'QSet.const_iterator',
+ 'QSet.iterator',
+ 'QExplicitlySharedDataPointer',
+ 'QSharedData',
+ 'QSharedDataPointer',
+ 'QEnableSharedFromThis',
+ 'QSharedPointer',
+ 'QWeakPointer',
+ 'QStack',
+ 'QLatin1String',
+ 'QString',
+ 'QStringRef',
+ 'QStringList',
+ 'QStringMatcher',
+ 'QVarLengthArray',
+ 'QVector',
+ 'QFutureIterator',
+ 'QHashIterator',
+ 'QMutableHashIterator',
+ 'QLinkedListIterator',
+ 'QMutableLinkedListIterator',
+ 'QListIterator',
+ 'QMutableListIterator',
+ 'QMapIterator',
+ 'QMutableMapIterator',
+ 'QSetIterator',
+ 'QMutableVectorIterator',
+ 'QVectorIterator',
+ # QtGui
+ 'QIconEnginePlugin',
+ 'QImageIOPlugin',
+ 'QGenericPlugin',
+ 'QGenericPluginFactory',
+ 'QGenericMatrix',
+ 'QOpenGLExtraFunctions',
+ # QtWidgets
+ 'QItemEditorCreator',
+ 'QStandardItemEditorCreator',
+ 'QStylePlugin',
+ # QtSql
+ 'QSqlDriverCreator',
+ 'QSqlDriverPlugin',
+}
diff --git a/tools/missing_bindings/main.py b/tools/missing_bindings/main.py
new file mode 100644
index 000000000..7390687ff
--- /dev/null
+++ b/tools/missing_bindings/main.py
@@ -0,0 +1,330 @@
+#############################################################################
+##
+## 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 script is used to generate a summary of missing types / classes
+# which are present in C++ Qt6, but are missing in PySide6.
+#
+# Required packages: bs4
+# Installed via: pip install bs4
+#
+# The script uses beautiful soup 4 to parse out the class names from
+# the online Qt documentation. It then tries to import the types from
+# PySide6.
+#
+# Example invocation of script:
+# python missing_bindings.py --qt-version 6.0 -w all
+# --qt-version - specify which version of qt documentation to load.
+# -w - if PyQt6 is an installed package, check if the tested
+# class also exists there.
+
+import argparse
+import os.path
+import sys
+from textwrap import dedent
+from time import gmtime, strftime
+from urllib import request
+
+from bs4 import BeautifulSoup
+
+from config import modules_to_test, types_to_ignore
+
+qt_documentation_website_prefixes = {
+ "6.0": "http://doc.qt.io/qt-6/",
+ "dev": "http://doc-snapshots.qt.io/qt5-dev/",
+}
+
+
+def qt_version_to_doc_prefix(version):
+ if version in qt_documentation_website_prefixes:
+ return qt_documentation_website_prefixes[version]
+ else:
+ raise RuntimeError("The specified qt version is not supported")
+
+
+def create_doc_url(module_doc_page_url, version):
+ return qt_version_to_doc_prefix(version) + module_doc_page_url
+
+
+def get_parser():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "module",
+ default="all",
+ choices=list(modules_to_test.keys()).append("all"),
+ nargs="?",
+ type=str,
+ help="the Qt module for which to get the missing types",
+ )
+ parser.add_argument(
+ "--qt-version",
+ "-v",
+ default="6.0",
+ choices=["6.0", "dev"],
+ type=str,
+ dest="version",
+ help="the Qt version to use to check for types",
+ )
+ parser.add_argument(
+ "--which-missing",
+ "-w",
+ default="all",
+ choices=["all", "in-pyqt", "not-in-pyqt"],
+ type=str,
+ dest="which_missing",
+ help="Which missing types to show (all, or just those " "that are not present in PyQt)",
+ )
+ return parser
+
+
+def wikilog(*pargs, **kw):
+ print(*pargs)
+
+ computed_str = ""
+ for arg in pargs:
+ computed_str += str(arg)
+
+ style = "text"
+ if "style" in kw:
+ style = kw["style"]
+
+ if style == "heading1":
+ computed_str = "= " + computed_str + " ="
+ elif style == "heading5":
+ computed_str = "===== " + computed_str + " ====="
+ elif style == "with_newline":
+ computed_str += "\n"
+ elif style == "bold_colon":
+ computed_str = computed_str.replace(":", ":'''")
+ computed_str += "'''"
+ computed_str += "\n"
+ elif style == "error":
+ computed_str = "''" + computed_str.strip("\n") + "''\n"
+ elif style == "text_with_link":
+ computed_str = computed_str
+ elif style == "code":
+ computed_str = " " + computed_str
+ elif style == "end":
+ return
+
+ print(computed_str, file=wiki_file)
+
+
+if __name__ == "__main__":
+ parser = get_parser()
+ args = parser.parse_args()
+
+ if hasattr(args, "module") and args.module != "all":
+ saved_value = modules_to_test[args.module]
+ modules_to_test.clear()
+ modules_to_test[args.module] = saved_value
+
+ pyside_package_name = "PySide6"
+ pyqt_package_name = "PyQt6"
+
+ total_missing_types_count = 0
+ total_missing_types_count_compared_to_pyqt = 0
+ total_missing_modules_count = 0
+
+ wiki_file = open("missing_bindings_for_wiki_qt_io.txt", "w")
+ wiki_file.truncate()
+
+ wikilog(f"PySide6 bindings for Qt {args.version}", style="heading1")
+
+ wikilog(
+ f"Using Qt version {args.version} documentation to find public "
+ "API Qt types and test if the types are present in the PySide6 "
+ "package."
+ )
+
+ wikilog(
+ dedent(
+ """\
+ Results are usually stored at
+ https://wiki.qt.io/PySide6_Missing_Bindings
+ so consider taking the contents of the generated
+ missing_bindings_for_wiki_qt_io.txt
+ file and updating the linked wiki page."""
+ ),
+ style="end",
+ )
+
+ wikilog(
+ "Similar report:\n" "https://gist.github.com/ethanhs/6c626ca4e291f3682589699296377d3a",
+ style="text_with_link",
+ )
+
+ python_executable = os.path.basename(sys.executable or "")
+ command_line_arguments = " ".join(sys.argv)
+ report_date = strftime("%Y-%m-%d %H:%M:%S %Z", gmtime())
+
+ wikilog(
+ dedent(
+ f"""
+ This report was generated by running the following command:
+ {python_executable} {command_line_arguments}
+ on the following date:
+ {report_date}
+ """
+ )
+ )
+
+ for module_name in modules_to_test.keys():
+ wikilog(module_name, style="heading5")
+
+ url = create_doc_url(modules_to_test[module_name], args.version)
+ wikilog(f"Documentation link: {url}\n", style="text_with_link")
+
+ # Import the tested module
+ try:
+ pyside_tested_module = getattr(
+ __import__(pyside_package_name, fromlist=[module_name]), module_name
+ )
+ except Exception as e:
+ e_str = str(e).replace('"', "")
+ wikilog(
+ f"\nCould not load {pyside_package_name}.{module_name}. "
+ f"Received error: {e_str}. Skipping.\n",
+ style="error",
+ )
+ total_missing_modules_count += 1
+ continue
+
+ try:
+ pyqt_module_name = module_name
+ if module_name == "QtCharts":
+ pyqt_module_name = module_name[:-1]
+
+ pyqt_tested_module = getattr(
+ __import__(pyqt_package_name, fromlist=[pyqt_module_name]), pyqt_module_name
+ )
+ except Exception as e:
+ e_str = str(e).replace("'", "")
+ wikilog(
+ f"\nCould not load {pyqt_package_name}.{module_name} for comparison. "
+ f"Received error: {e_str}.\n",
+ style="error",
+ )
+
+ # Get C++ class list from documentation page.
+ page = request.urlopen(url)
+ soup = BeautifulSoup(page, "html.parser")
+
+ # Extract the Qt type names from the documentation classes table
+ links = soup.body.select(".annotated a")
+ types_on_html_page = []
+
+ for link in links:
+ link_text = link.text
+ link_text = link_text.replace("::", ".")
+ if link_text not in types_to_ignore:
+ types_on_html_page.append(link_text)
+
+ wikilog(f"Number of types in {module_name}: {len(types_on_html_page)}", style="bold_colon")
+
+ missing_types_count = 0
+ missing_types_compared_to_pyqt = 0
+ missing_types = []
+ for qt_type in types_on_html_page:
+ try:
+ pyside_qualified_type = "pyside_tested_module."
+
+ if "QtCharts" == module_name:
+ pyside_qualified_type += "QtCharts."
+ elif "DataVisualization" in module_name:
+ pyside_qualified_type += "QtDataVisualization."
+
+ pyside_qualified_type += qt_type
+ eval(pyside_qualified_type)
+ except:
+ missing_type = qt_type
+ missing_types_count += 1
+ total_missing_types_count += 1
+
+ is_present_in_pyqt = False
+ try:
+ pyqt_qualified_type = "pyqt_tested_module."
+
+ if "Charts" in module_name:
+ pyqt_qualified_type += "QtCharts."
+ elif "DataVisualization" in module_name:
+ pyqt_qualified_type += "QtDataVisualization."
+
+ pyqt_qualified_type += qt_type
+ eval(pyqt_qualified_type)
+ missing_type += " (is present in PyQt6)"
+ missing_types_compared_to_pyqt += 1
+ total_missing_types_count_compared_to_pyqt += 1
+ is_present_in_pyqt = True
+ except:
+ pass
+
+ if args.which_missing == "all":
+ missing_types.append(missing_type)
+ elif args.which_missing == "in-pyqt" and is_present_in_pyqt:
+ missing_types.append(missing_type)
+ elif args.which_missing == "not-in-pyqt" and not is_present_in_pyqt:
+ missing_types.append(missing_type)
+
+ if len(missing_types) > 0:
+ wikilog(f"Missing types in {module_name}:", style="with_newline")
+ missing_types.sort()
+ for missing_type in missing_types:
+ wikilog(missing_type, style="code")
+ wikilog("")
+
+ wikilog(f"Number of missing types: {missing_types_count}", style="bold_colon")
+ if len(missing_types) > 0:
+ wikilog(
+ "Number of missing types that are present in PyQt6: "
+ f"{missing_types_compared_to_pyqt}",
+ style="bold_colon",
+ )
+ wikilog(f"End of missing types for {module_name}\n", style="end")
+ else:
+ wikilog("", style="end")
+
+ wikilog("Summary", style="heading5")
+ wikilog(f"Total number of missing types: {total_missing_types_count}", style="bold_colon")
+ wikilog(
+ "Total number of missing types that are present in PyQt6: "
+ f"{total_missing_types_count_compared_to_pyqt}",
+ style="bold_colon",
+ )
+ wikilog(f"Total number of missing modules: {total_missing_modules_count}", style="bold_colon")
+ wiki_file.close()
diff --git a/tools/missing_bindings/requirements.txt b/tools/missing_bindings/requirements.txt
new file mode 100644
index 000000000..732522d26
--- /dev/null
+++ b/tools/missing_bindings/requirements.txt
@@ -0,0 +1,8 @@
+beautifulsoup4
+
+# PySide
+PySide6
+
+# PyQt
+PyQt6
+PyQt6-3D
diff --git a/tools/snippets_translate/README.md b/tools/snippets_translate/README.md
new file mode 100644
index 000000000..8b24b6b7f
--- /dev/null
+++ b/tools/snippets_translate/README.md
@@ -0,0 +1,151 @@
+# Snippets Translate
+
+To install dependencies on an activated virtual environment run
+`pip install -r requirements.txt`.
+
+To run the tests, execute `python -m pytest`. It's important not to
+run `pytest` alone to include the PYTHONPATH so the imports work.
+
+Here's an explanation for each file:
+
+* `main.py`, main file that handle the arguments, the general process
+ of copying/writing files into the pyside-setup/ repository.
+* `converter.py`, main function that translate each line depending
+ of the decision making process that use different handlers.
+* `handlers.py`, functions that handle the different translation cases.
+* `parse_utils.py`, some useful function that help the translation process.
+* `tests/test_converter.py`, tests cases for the converter function.
+
+## Usage
+
+```
+% python main.py -h
+usage: sync_snippets [-h] --qt QT_DIR --pyside PYSIDE_DIR [-w] [-v]
+
+optional arguments:
+ -h, --help show this help message and exit
+ --qt QT_DIR Path to the Qt directory (QT_SRC_DIR)
+ --pyside PYSIDE_DIR Path to the pyside-setup directory
+ -w, --write Actually copy over the files to the pyside-setup directory
+ -v, --verbose Generate more output
+```
+
+For example:
+
+```
+python main.py --qt /home/cmaureir/dev/qt6/ --pyside /home/cmaureir/dev/pyside-setup -w
+```
+
+which will create all the snippet files in the pyside repository. The `-w`
+option is in charge of actually writing the files.
+
+
+## Pending cases
+
+As described at the end of the `converter.py` and `tests/test_converter.py`
+files there are a couple of corner cases that are not covered like:
+
+* handler `std::` types and functions
+* handler for `operator...`
+* handler for `tr("... %1").arg(a)`
+* support for lambda expressions
+* there are also strange cases that cannot be properly handle with
+ a line-by-line approach, for example, `for ( ; it != end; ++it) {`
+* interpretation of `typedef ...` (including function pointers)
+* interpretation of `extern "C" ...`
+
+Additionally,
+one could add more test cases for each handler, because at the moment
+only the general converter function (which uses handlers) is being
+tested as a whole.
+
+## Patterns for directories
+
+### Snippets
+
+Everything that has .../snippets/*, for example:
+
+```
+ qtbase/src/corelib/doc/snippets/
+ ./qtdoc/doc/src/snippets/
+
+```
+
+goes to:
+
+```
+ pyside-setup/sources/pyside6/doc/codesnippets/doc/src/snippets/*
+```
+
+### Examples
+
+Everything that has .../examples/*/*, for example:
+
+```
+ ./qtbase/examples/widgets/dialogs/licensewizard
+ ./qtbase/examples/widgets/itemviews/pixelator
+```
+
+goes to
+
+```
+ pyside-setup/sources/pyside6/doc/codesnippets/examples/
+ dialogs/licensewizard
+ itemviews/pixelator
+
+```
+
+## Patterns for files
+
+Files to skip:
+
+```
+ *.pro
+ *.pri
+ *.cmake
+ *.qdoc
+ CMakeLists.txt
+```
+
+which means we will be copying:
+
+```
+ *.png
+ *.cpp
+ *.h
+ *.ui
+ *.qrc
+ *.xml
+ *.qml
+ *.svg
+ *.js
+ *.ts
+ *.xq
+ *.txt
+ etc
+```
+## Files examples
+
+```
+[repo] qt5
+
+ ./qtbase/src/corelib/doc/snippets/code/src_corelib_thread_qmutexpool.cpp
+ ./qtbase/src/widgets/doc/snippets/code/src_gui_styles_qstyle.cpp
+ ./qtbase/src/network/doc/snippets/code/src_network_kernel_qhostinfo.cpp
+ ./qtbase/examples/sql/relationaltablemodel/relationaltablemodel.cpp
+ ./qtbase/src/printsupport/doc/snippets/code/src_gui_dialogs_qabstractprintdialog.cpp
+ ./qtdoc/doc/src/snippets/qlistview-using
+ ./qtbase/src/widgets/doc/snippets/layouts/layouts.cpp
+```
+
+```
+[repo] pyside-setup
+
+ ./sources/pyside6/doc/codesnippets/doc/src/snippets/code/src_corelib_thread_qmutexpool.cpp
+ ./sources/pyside6/doc/codesnippets/doc/src/snippets/code/src_gui_styles_qstyle.cpp
+ ./sources/pyside6/doc/codesnippets/doc/src/snippets/code/src_network_kernel_qhostinfo.cpp
+ ./sources/pyside6/doc/codesnippets/examples/relationaltablemodel/relationaltablemodel.cpp
+ ./sources/pyside6/doc/codesnippets/doc/src/snippets/code/src_gui_dialogs_qabstractprintdialog.cpp
+ ./sources/pyside6/doc/codesnippets/doc/src/snippets/qlistview-using
+ ./sources/pyside6/doc/codesnippets/doc/src/snippets/layouts
+```
diff --git a/tools/snippets_translate/converter.py b/tools/snippets_translate/converter.py
new file mode 100644
index 000000000..e5193f598
--- /dev/null
+++ b/tools/snippets_translate/converter.py
@@ -0,0 +1,334 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+import re
+
+
+from handlers import (handle_casts, handle_class, handle_condition,
+ handle_conditions, handle_constructor_default_values,
+ handle_constructors, handle_cout_endl, handle_emit,
+ handle_for, handle_foreach, handle_inc_dec,
+ handle_include, handle_keywords, handle_negate,
+ handle_type_var_declaration, handle_void_functions,
+ handle_methods_return_type, handle_functions,
+ handle_array_declarations, handle_useless_qt_classes,)
+
+from parse_utils import get_indent, dstrip, remove_ref
+
+
+def snippet_translate(x):
+
+ ## Cases which are not C++
+ ## TODO: Maybe expand this with lines that doesn't need to be translated
+ if x.strip().startswith("content-type: text/html"):
+ return x
+
+ ## General Rules
+
+ # Remove ';' at the end of the lines
+ if x.endswith(";"):
+ x = x[:-1]
+
+ # Remove lines with only '{' or '}'
+ if x.strip() == "{" or x.strip() == "}":
+ return ""
+
+ # Skip lines with the snippet related identifier '//!'
+ if x.strip().startswith("//!"):
+ return x
+
+ # handle lines with only comments using '//'
+ if x.lstrip().startswith("//"):
+ x = x.replace("//", "#", 1)
+ return x
+
+ # Handle "->"
+ if "->" in x:
+ x = x.replace("->", ".")
+
+ # handle '&&' and '||'
+ if "&&" in x:
+ x = x.replace("&&", "and")
+ if "||" in x:
+ x = x.replace("||", "or")
+
+ # Handle lines that have comments after the ';'
+ if ";" in x and "//" in x:
+ if x.index(";") < x.index("//"):
+ left, right = x.split("//", 1)
+ left = left.replace(";", "", 1)
+ x = f"{left}#{right}"
+
+ # Handle 'new '
+ # This contains an extra whitespace because of some variables
+ # that include the string 'new'
+ if "new " in x:
+ x = x.replace("new ", "")
+
+ # Handle 'const'
+ # Some variables/functions have the word 'const' so we explicitly
+ # consider the cases with a whitespace before and after.
+ if " const" in x:
+ x = x.replace(" const", "")
+ if "const " in x:
+ x = x.replace("const ", "")
+
+ # Handle 'static'
+ if "static " in x:
+ x = x.replace("static ", "")
+
+ # Handle 'inline'
+ if "inline " in x:
+ x = x.replace("inline ", "")
+
+ # Handle 'double'
+ if "double " in x:
+ x = x.replace("double ", "float ")
+
+ # Handle increment/decrement operators
+ if "++" in x:
+ x = handle_inc_dec(x, "++")
+ if "--" in x:
+ x = handle_inc_dec(x, "--")
+
+ # handle negate '!'
+ if "!" in x:
+ x = handle_negate(x)
+
+ # Handle "this", "true", "false" but before "#" symbols
+ if "this" in x:
+ x = handle_keywords(x, "this", "self")
+ if "true" in x:
+ x = handle_keywords(x, "true", "True")
+ if "false" in x:
+ x = handle_keywords(x, "false", "False")
+ if "throw" in x:
+ x = handle_keywords(x, "throw", "raise")
+
+ # handle 'void Class::method(...)' and 'void method(...)'
+ if re.search(r"^ *void *[\w\_]+(::)?[\w\d\_]+\(", x):
+ x = handle_void_functions(x)
+
+ # 'Q*::' -> 'Q*.'
+ # FIXME: This will break iterators, but it's a small price.
+ if re.search(r"Q[\w]+::", x):
+ x = x.replace("::", ".")
+
+ # handle 'nullptr'
+ if "nullptr" in x:
+ x = x.replace("nullptr", "None")
+
+ ## Special Cases Rules
+
+ # Special case for 'main'
+ if x.strip().startswith("int main("):
+ return f'{get_indent(x)}if __name__ == "__main__":'
+
+ if x.strip().startswith("QApplication app(argc, argv)"):
+ return f"{get_indent(x)}app = QApplication([])"
+
+ # Special case for 'return app.exec()'
+ if x.strip().startswith("return app.exec"):
+ return x.replace("return app.exec()", "sys.exit(app.exec_())")
+
+ # Handle includes -> import
+ if x.strip().startswith("#include"):
+ x = handle_include(x)
+ return dstrip(x)
+
+ if x.strip().startswith("emit "):
+ x = handle_emit(x)
+ return dstrip(x)
+
+ # *_cast
+ if "_cast<" in x:
+ x = handle_casts(x)
+
+ # Handle Qt classes that needs to be removed
+ x = handle_useless_qt_classes(x)
+
+ # Handling ternary operator
+ if re.search(r"^.* \? .+ : .+$", x.strip()):
+ x = x.replace(" ? ", " if ")
+ x = x.replace(" : ", " else ")
+
+ # Handle 'while', 'if', and 'else if'
+ # line might end in ')' or ") {"
+ if x.strip().startswith(("while", "if", "else if", "} else if")):
+ x = handle_conditions(x)
+ return dstrip(x)
+ elif re.search("^ *}? *else *{?", x):
+ x = re.sub(r"}? *else *{?", "else:", x)
+ return dstrip(x)
+
+ # 'cout' and 'endl'
+ if re.search("^ *(std::)?cout", x) or ("endl" in x) or x.lstrip().startswith("qDebug()"):
+ x = handle_cout_endl(x)
+ return dstrip(x)
+
+ # 'for' loops
+ if re.search(r"^ *for *\(", x.strip()):
+ return dstrip(handle_for(x))
+
+ # 'foreach' loops
+ if re.search(r"^ *foreach *\(", x.strip()):
+ return dstrip(handle_foreach(x))
+
+ # 'class' and 'structs'
+ if re.search(r"^ *class ", x) or re.search(r"^ *struct ", x):
+ if "struct " in x:
+ x = x.replace("struct ", "class ")
+ return handle_class(x)
+
+ # 'delete'
+ if re.search(r"^ *delete ", x):
+ return x.replace("delete", "del")
+
+ # 'public:'
+ if re.search(r"^public:$", x.strip()):
+ return x.replace("public:", "# public")
+
+ # 'private:'
+ if re.search(r"^private:$", x.strip()):
+ return x.replace("private:", "# private")
+
+ # For expressions like: `Type var`
+ # which does not contain a `= something` on the right side
+ # should match
+ # Some thing
+ # QSome<var> thing
+ # QSome thing(...)
+ # should not match
+ # QSome thing = a
+ # QSome thing = a(...)
+ # def something(a, b, c)
+ # At the end we skip methods with the form:
+ # QStringView Message::body()
+ # to threat them as methods.
+ if (re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*\&]+(\(.*?\))? ?(?!.*=|:).*$", x.strip())
+ and x.strip().split()[0] not in ("def", "return", "and", "or")
+ and not re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w]+::[\w\*\&]+\(.*\)$", x.strip())
+ and ("{" not in x and "}" not in x)):
+
+ # FIXME: this 'if' is a hack for a function declaration with this form:
+ # QString myDecoderFunc(QByteArray &localFileName)
+ # No idea how to check for different for variables like
+ # QString notAFunction(Something something)
+ # Maybe checking the structure of the arguments?
+ if "Func" not in x:
+ return dstrip(handle_type_var_declaration(x))
+
+ # For expressions like: `Type var = value`,
+ # considering complex right-side expressions.
+ # QSome thing = b
+ # QSome thing = b(...)
+ # float v = 0.1
+ # QSome *thing = ...
+ if (re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*]+ *= *[\w\.\"\']*(\(.*?\))?", x.strip()) and
+ ("{" not in x and "}" not in x)):
+ left, right = x.split("=", 1)
+ var_name = " ".join(left.strip().split()[1:])
+ x = f"{get_indent(x)}{remove_ref(var_name)} = {right.strip()}"
+ # Special case: When having this:
+ # QVBoxLayout *layout = new QVBoxLayout;
+ # we end up like this:
+ # layout = QVBoxLayout
+ # so we need to add '()' at the end if it's just a word
+ # with only alpha numeric content
+ if re.search(r"\w+ = [A-Z]{1}\w+", x.strip()) and not x.strip().endswith(")"):
+ x = f"{x.rstrip()}()"
+ return dstrip(x)
+
+ # For constructors, that we now the shape is:
+ # ClassName::ClassName(...)
+ if re.search(r"^ *\w+::\w+\(.*?\)", x.strip()):
+ x = handle_constructors(x)
+ return dstrip(x)
+
+ # For base object constructor:
+ # : QWidget(parent)
+ if (
+ x.strip().startswith(": ")
+ and ("<<" not in x)
+ and ("::" not in x)
+ and not x.strip().endswith(";")
+ ):
+
+ return handle_constructor_default_values(x)
+
+ # Arrays declarations with the form:
+ # type var_name[] = {...
+ # type var_name {...
+ #if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*]+\[\] * = *\{", x.strip()):
+ if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*]+\[?\]? * =? *\{", x.strip()):
+ x = handle_array_declarations(x)
+
+ # Methods with return type
+ # int Class::method(...)
+ # QStringView Message::body()
+ if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w]+::[\w\*\&]+\(.*\)$", x.strip()):
+ # We just need to capture the 'method name' and 'arguments'
+ x = handle_methods_return_type(x)
+
+ # Handling functions
+ # By this section of the function, we cover all the other cases
+ # So we can safely assume it's not a variable declaration
+ if re.search(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*\&]+\(.*\)$", x.strip()):
+ x = handle_functions(x)
+
+ # General return for no special cases
+ return dstrip(x)
+
+ # TODO:
+ # * Lambda expressions
+
+ # * operator overload
+ # void operator()(int newState) { state = newState; }
+ # const QDBusArgument &operator>>(const QDBusArgument &argument, MyDictionary &myDict)
+ # inline bool operator==(const Employee &e1, const Employee &e2)
+ # void *operator new[](size_t size)
+
+ # * extern "C" ...
+ # extern "C" MY_EXPORT int avg(int a, int b)
+
+ # * typedef ...
+ # typedef int (*AvgFunction)(int, int);
+
+ # * function pointers
+ # typedef void (*MyPrototype)();
diff --git a/tools/snippets_translate/handlers.py b/tools/snippets_translate/handlers.py
new file mode 100644
index 000000000..b8ee9f219
--- /dev/null
+++ b/tools/snippets_translate/handlers.py
@@ -0,0 +1,519 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+import re
+
+from parse_utils import get_indent, dstrip, remove_ref, parse_arguments, replace_main_commas, get_qt_module_class
+
+def handle_condition(x, name):
+ # Make sure it's not a multi line condition
+ x = x.replace("}", "")
+ if x.count("(") == x.count(")"):
+ comment = ""
+ # This handles the lines that have no ';' at the end but
+ # have a comment after the end of the line, like:
+ # while (true) // something
+ # { ... }
+ if "//" in x:
+ comment_content = x.split("//", 1)
+ comment = f" #{comment_content[-1]}"
+ x = x.replace(f"//{comment_content[-1]}", "")
+
+ re_par = re.compile(r"\((.+)\)")
+ condition = re_par.search(x).group(1)
+ return f"{get_indent(x)}{name} {condition.strip()}:{comment}"
+ return x
+
+
+def handle_keywords(x, word, pyword):
+ if word in x:
+ if "#" in x:
+ if x.index(word) < x.index("#"):
+ x = x.replace(word, pyword)
+ else:
+ x = x.replace(word, pyword)
+ return x
+
+
+def handle_inc_dec(x, operator):
+ # Alone on a line
+ clean_x = x.strip()
+ if clean_x.startswith(operator) or clean_x.endswith(operator):
+ x = x.replace(operator, "")
+ x = f"{x} = {clean_x.replace(operator, '')} {operator[0]} 1"
+ return x
+
+
+def handle_casts(x):
+ cast = None
+ re_type = re.compile(r"<(.*)>")
+ re_data = re.compile(r"_cast<.*>\((.*)\)")
+ type_name = re_type.search(x)
+ data_name = re_data.search(x)
+
+ if type_name and data_name:
+ type_name = type_name.group(1).replace("*", "")
+ data_name = data_name.group(1)
+ new_value = f"{type_name}({data_name})"
+
+ if "static_cast" in x:
+ x = re.sub(r"static_cast<.*>\(.*\)", new_value, x)
+ elif "dynamic_cast" in x:
+ x = re.sub(r"dynamic_cast<.*>\(.*\)", new_value, x)
+ elif "const_cast" in x:
+ x = re.sub(r"const_cast<.*>\(.*\)", new_value, x)
+ elif "reinterpret_cast" in x:
+ x = re.sub(r"reinterpret_cast<.*>\(.*\)", new_value, x)
+ elif "qobject_cast" in x:
+ x = re.sub(r"qobject_cast<.*>\(.*\)", new_value, x)
+
+ return x
+
+
+def handle_include(x):
+ if '"' in x:
+ re_par = re.compile(r'"(.*)"')
+ header = re_par.search(x)
+ if header:
+ header_name = header.group(1).replace(".h", "")
+ module_name = header_name.replace('/', '.')
+ x = f"from {module_name} import *"
+ else:
+ # We discard completely if there is something else
+ # besides '"something.h"'
+ x = ""
+ elif "<" in x and ">" in x:
+ re_par = re.compile(r"<(.*)>")
+ name = re_par.search(x).group(1)
+ t = get_qt_module_class(name)
+ # if it's not a Qt module or class, we discard it.
+ if t is None:
+ x = ""
+ else:
+ # is a module
+ if t[0]:
+ x = f"from PySide6 import {t[1]}"
+ # is a class
+ else:
+ x = f"from PySide6.{t[1]} import {name}"
+ return x
+
+
+def handle_conditions(x):
+ x_strip = x.strip()
+ if x_strip.startswith("while") and "(" in x:
+ x = handle_condition(x, "while")
+ elif x_strip.startswith("if") and "(" in x:
+ x = handle_condition(x, "if")
+ elif x_strip.startswith(("else if", "} else if")):
+ x = handle_condition(x, "else if")
+ x = x.replace("else if", "elif")
+ x = x.replace("::", ".")
+ return x
+
+
+def handle_for(x):
+ re_content = re.compile(r"\((.*)\)")
+ content = re_content.search(x)
+
+ new_x = x
+ if content:
+ # parenthesis content
+ content = content.group(1)
+
+ # for (int i = 1; i < argc; ++i)
+ if x.count(";") == 2:
+
+ # for (start; middle; end)
+ start, middle, end = content.split(";")
+
+ # iterators
+ if "begin(" in x.lower() and "end(" in x.lower():
+ name = re.search(r"= *(.*)egin\(", start)
+ iterable = None
+ iterator = None
+ if name:
+ name = name.group(1)
+ # remove initial '=', and split the '.'
+ # because '->' was already transformed,
+ # and we keep the first word.
+ iterable = name.replace("=", "", 1).split(".")[0]
+
+ iterator = remove_ref(start.split("=")[0].split()[-1])
+ if iterator and iterable:
+ return f"{get_indent(x)}for {iterator} in {iterable}:"
+
+ if ("++" in end or "--" in end) or ("+=" in end or "-=" in end):
+ if "," in start:
+ raw_var, value = start.split(",")[0].split("=")
+ else:
+ # Malformed for-loop:
+ # for (; pixel1 > start; pixel1 -= stride)
+ # We return the same line
+ if not start.strip():
+ return f"{get_indent(x)}{dstrip(x)}"
+ raw_var, value = start.split("=")
+ raw_var = raw_var.strip()
+ value = value.strip()
+ var = raw_var.split()[-1]
+
+ end_value = None
+ if "+=" in end:
+ end_value = end.split("+=")[-1]
+ elif "-=" in end:
+ end_value = end.split("-=")[-1]
+ if end_value:
+ try:
+ end_value = int(end_value)
+ except ValueError:
+ end_value = None
+
+ if "<" in middle:
+ limit = middle.split("<")[-1]
+
+ if "<=" in middle:
+ limit = middle.split("<=")[-1]
+ try:
+ limit = int(limit)
+ limit += 1
+ except ValueError:
+ limit = f"{limit} + 1"
+
+ if end_value:
+ new_x = f"for {var} in range({value}, {limit}, {end_value}):"
+ else:
+ new_x = f"for {var} in range({value}, {limit}):"
+ elif ">" in middle:
+ limit = middle.split(">")[-1]
+
+ if ">=" in middle:
+ limit = middle.split(">=")[-1]
+ try:
+ limit = int(limit)
+ limit -= 1
+ except ValueError:
+ limit = f"{limit} - 1"
+ if end_value:
+ new_x = f"for {var} in range({limit}, {value}, -{end_value}):"
+ else:
+ new_x = f"for {var} in range({limit}, {value}, -1):"
+ else:
+ # TODO: No support if '<' or '>' is not used.
+ pass
+
+ # for (const QByteArray &ext : qAsConst(extensionList))
+ elif x.count(":") > 0:
+ iterator, iterable = content.split(":", 1)
+ var = iterator.split()[-1].replace("&", "").strip()
+ new_x = f"for {remove_ref(var)} in {iterable.strip()}:"
+ return f"{get_indent(x)}{dstrip(new_x)}"
+
+
+def handle_foreach(x):
+ re_content = re.compile(r"\((.*)\)")
+ content = re_content.search(x)
+ if content:
+ parenthesis = content.group(1)
+ iterator, iterable = parenthesis.split(",", 1)
+ # remove iterator type
+ it = dstrip(iterator.split()[-1])
+ # remove <...> from iterable
+ value = re.sub("<.*>", "", iterable)
+ return f"{get_indent(x)}for {it} in {value}:"
+
+
+def handle_type_var_declaration(x):
+ # remove content between <...>
+ if "<" in x and ">" in x:
+ x = " ".join(re.sub("<.*>", "", i) for i in x.split())
+ content = re.search(r"\((.*)\)", x)
+ if content:
+ # this means we have something like:
+ # QSome thing(...)
+ type_name, var_name = x.split()[:2]
+ var_name = var_name.split("(")[0]
+ x = f"{get_indent(x)}{var_name} = {type_name}({content.group(1)})"
+ else:
+ # this means we have something like:
+ # QSome thing
+ type_name, var_name = x.split()[:2]
+ x = f"{get_indent(x)}{var_name} = {type_name}()"
+ return x
+
+
+def handle_constructors(x):
+ re_content = re.compile(r"\((.*)\)")
+ arguments = re_content.search(x).group(1)
+ class_method = x.split("(")[0].split("::")
+ if len(class_method) == 2:
+ # Equal 'class name' and 'method name'
+ if len(set(class_method)) == 1:
+ arguments = ", ".join(remove_ref(i.split()[-1]) for i in arguments.split(",") if i)
+ if arguments:
+ return f"{get_indent(x)}def __init__(self, {arguments}):"
+ else:
+ return f"{get_indent(x)}def __init__(self):"
+ return dstrip(x)
+
+
+def handle_constructor_default_values(x):
+ # if somehow we have a ' { } ' by the end of the line,
+ # we discard that section completely, since even with a single
+ # value, we don't need to take care of it, for example:
+ # ' : a(1) { } -> self.a = 1
+ if re.search(".*{ *}.*", x):
+ x = re.sub("{ *}", "", x)
+
+ values = "".join(x.split(":", 1))
+ # Check the commas that are not inside round parenthesis
+ # For example:
+ # : QWidget(parent), Something(else, and, other), value(1)
+ # so we can find only the one after '(parent),' and 'other),'
+ # and replace them by '@'
+ # : QWidget(parent)@ Something(else, and, other)@ value(1)
+ # to be able to split the line.
+ values = replace_main_commas(values)
+ # if we have more than one expression
+ if "@" in values:
+ return_values = ""
+ for arg in values.split("@"):
+ arg = re.sub("^ *: *", "", arg).strip()
+ if arg.startswith("Q"):
+ class_name = arg.split("(")[0]
+ content = arg.replace(class_name, "")[1:-1]
+ return_values += f" {class_name}.__init__(self, {content})\n"
+ elif arg:
+ var_name = arg.split("(")[0]
+ re_par = re.compile(r"\((.+)\)")
+ content = re_par.search(arg).group(1)
+ return_values += f" self.{var_name} = {content}\n"
+ else:
+ arg = re.sub("^ *: *", "", values).strip()
+ if arg.startswith("Q"):
+ class_name = arg.split("(")[0]
+ content = arg.replace(class_name, "")[1:-1]
+ return f" {class_name}.__init__(self, {content})"
+ elif arg:
+ var_name = arg.split("(")[0]
+ re_par = re.compile(r"\((.+)\)")
+ content = re_par.search(arg).group(1)
+ return f" self.{var_name} = {content}"
+
+ return return_values.rstrip()
+
+
+def handle_cout_endl(x):
+ # if comment at the end
+ comment = ""
+ if re.search(r" *# *[\w\ ]+$", x):
+ comment = f' # {re.search(" *# *(.*)$", x).group(1)}'
+ x = x.split("#")[0]
+
+ if "qDebug()" in x:
+ x = x.replace("qDebug()", "cout")
+
+ if "cout" in x and "endl" in x:
+ re_cout_endl = re.compile(r"cout *<<(.*)<< *.*endl")
+ data = re_cout_endl.search(x)
+ if data:
+ data = data.group(1)
+ data = re.sub(" *<< *", ", ", data)
+ x = f"{get_indent(x)}print({data}){comment}"
+ elif "cout" in x:
+ data = re.sub(".*cout *<<", "", x)
+ data = re.sub(" *<< *", ", ", data)
+ x = f"{get_indent(x)}print({data}){comment}"
+ elif "endl" in x:
+ data = re.sub("<< +endl", "", x)
+ data = re.sub(" *<< *", ", ", data)
+ x = f"{get_indent(x)}print({data}){comment}"
+
+ x = x.replace("( ", "(").replace(" )", ")").replace(" ,", ",").replace("(, ", "(")
+ x = x.replace("Qt.endl", "").replace(", )", ")")
+ return x
+
+
+def handle_negate(x):
+ # Skip if it's part of a comment:
+ if "#" in x:
+ if x.index("#") < x.index("!"):
+ return x
+ elif "/*" in x:
+ if x.index("/*") < x.index("!"):
+ return x
+ re_negate = re.compile(r"!(.)")
+ next_char = re_negate.search(x).group(1)
+ if next_char not in ("=", '"'):
+ x = x.replace("!", "not ")
+ return x
+
+
+def handle_emit(x):
+ function_call = x.replace("emit ", "").strip()
+ re_content = re.compile(r"\((.*)\)")
+ arguments = re_content.search(function_call).group(1)
+ method_name = function_call.split("(")[0].strip()
+ return f"{get_indent(x)}{method_name}.emit({arguments})"
+
+
+def handle_void_functions(x):
+ class_method = x.replace("void ", "").split("(")[0]
+ first_param = ""
+ if "::" in class_method:
+ first_param = "self, "
+ method_name = class_method.split("::")[1]
+ else:
+ method_name = class_method.strip()
+
+ # if the arguments are in the same line:
+ if ")" in x:
+ re_content = re.compile(r"\((.*)\)")
+ parenthesis = re_content.search(x).group(1)
+ arguments = dstrip(parse_arguments(parenthesis))
+ elif "," in x:
+ arguments = dstrip(parse_arguments(x.split("(")[-1]))
+
+ # check if includes a '{ ... }' after the method signature
+ after_signature = x.split(")")[-1]
+ re_decl = re.compile(r"\{(.*)\}").search(after_signature)
+ extra = ""
+ if re_decl:
+ extra = re_decl.group(1)
+ if not extra:
+ extra = " pass"
+
+ if arguments:
+ x = f"{get_indent(x)}def {method_name}({first_param}{dstrip(arguments)}):{extra}"
+ else:
+ x = f"{get_indent(x)}def {method_name}({first_param.replace(', ', '')}):{extra}"
+ return x
+
+
+def handle_class(x):
+ # Check if there is a comment at the end of the line
+ comment = ""
+ if "//" in x:
+ parts = x.split("//")
+ x = "".join(parts[:-1])
+ comment = parts[-1]
+
+ # If the line ends with '{'
+ if x.rstrip().endswith("{"):
+ x = x[:-1]
+
+ # Get inheritance
+ decl_parts = x.split(":")
+ class_name = decl_parts[0].rstrip()
+ if len(decl_parts) > 1:
+ bases = decl_parts[1]
+ bases_name = ", ".join(i.split()[-1] for i in bases.split(",") if i)
+ else:
+ bases_name = ""
+
+ # Check if the class_name is templated, then remove it
+ if re.search(r".*<.*>", class_name):
+ class_name = class_name.split("<")[0]
+
+ # Special case: invalid notation for an example:
+ # class B() {...} -> clas B(): pass
+ if re.search(r".*{.*}", class_name):
+ class_name = re.sub(r"{.*}", "", class_name).rstrip()
+ return f"{class_name}(): pass"
+
+ # Special case: check if the line ends in ','
+ if x.endswith(","):
+ x = f"{class_name}({bases_name},"
+ else:
+ x = f"{class_name}({bases_name}):"
+
+ if comment:
+ return f"{x} #{comment}"
+ else:
+ return x
+
+def handle_array_declarations(x):
+ re_varname = re.compile(r"^[a-zA-Z0-9\<\>]+ ([\w\*]+) *\[?\]?")
+ content = re_varname.search(x.strip())
+ if content:
+ var_name = content.group(1)
+ rest_line = "".join(x.split("{")[1:])
+ x = f"{get_indent(x)}{var_name} = {{{rest_line}"
+ return x
+
+def handle_methods_return_type(x):
+ re_capture = re.compile(r"^ *[a-zA-Z0-9]+ [\w]+::([\w\*\&]+\(.*\)$)")
+ capture = re_capture.search(x)
+ if capture:
+ content = capture.group(1)
+ method_name = content.split("(")[0]
+ re_par = re.compile(r"\((.+)\)")
+ par_capture = re_par.search(x)
+ arguments = "(self)"
+ if par_capture:
+ arguments = f"(self, {par_capture.group(1)})"
+ x = f"{get_indent(x)}def {method_name}{arguments}:"
+ return x
+
+
+def handle_functions(x):
+ re_capture = re.compile(r"^ *[a-zA-Z0-9]+ ([\w\*\&]+\(.*\)$)")
+ capture = re_capture.search(x)
+ if capture:
+ content = capture.group(1)
+ function_name = content.split("(")[0]
+ re_par = re.compile(r"\((.+)\)")
+ par_capture = re_par.search(x)
+ arguments = ""
+ if par_capture:
+ for arg in par_capture.group(1).split(","):
+ arguments += f"{arg.split()[-1]},"
+ # remove last comma
+ if arguments.endswith(","):
+ arguments = arguments[:-1]
+ x = f"{get_indent(x)}def {function_name}({dstrip(arguments)}):"
+ return x
+
+def handle_useless_qt_classes(x):
+ _classes = ("QLatin1String", "QLatin1Char")
+ for i in _classes:
+ re_content = re.compile(fr"{i}\((.*)\)")
+ content = re_content.search(x)
+ if content:
+ x = x.replace(content.group(0), content.group(1))
+ return x
diff --git a/tools/snippets_translate/main.py b/tools/snippets_translate/main.py
new file mode 100644
index 000000000..c1267d22c
--- /dev/null
+++ b/tools/snippets_translate/main.py
@@ -0,0 +1,466 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+import argparse
+import logging
+import os
+import re
+import shutil
+import sys
+from enum import Enum
+from pathlib import Path
+from textwrap import dedent
+
+from converter import snippet_translate
+
+# Logger configuration
+try:
+ from rich.logging import RichHandler
+
+ logging.basicConfig(
+ level="NOTSET", format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
+ )
+ have_rich = True
+ extra = {"markup": True}
+
+ from rich.console import Console
+ from rich.table import Table
+
+except ModuleNotFoundError:
+ print("-- 'rich' not found, falling back to default logger")
+ logging.basicConfig(level=logging.INFO)
+ have_rich = False
+ extra = {}
+
+log = logging.getLogger("snippets_translate")
+opt_quiet = False
+
+# Filter and paths configuration
+SKIP_END = (".pro", ".pri", ".cmake", ".qdoc", ".yaml", ".frag", ".qsb", ".vert", "CMakeLists.txt")
+SKIP_BEGIN = ("changes-", ".")
+OUT_MAIN = Path("sources/pyside6/doc/codesnippets/")
+OUT_SNIPPETS = OUT_MAIN / "doc/src/snippets/"
+OUT_EXAMPLES = OUT_MAIN / "doc/codesnippets/examples/"
+
+
+class FileStatus(Enum):
+ Exists = 0
+ New = 1
+
+
+def get_parser():
+ parser = argparse.ArgumentParser(prog="snippets_translate")
+ # List pyproject files
+ parser.add_argument(
+ "--qt",
+ action="store",
+ dest="qt_dir",
+ required=True,
+ help="Path to the Qt directory (QT_SRC_DIR)",
+ )
+
+ parser.add_argument(
+ "--pyside",
+ action="store",
+ dest="pyside_dir",
+ required=True,
+ help="Path to the pyside-setup directory",
+ )
+
+ parser.add_argument(
+ "-w",
+ "--write",
+ action="store_true",
+ dest="write_files",
+ help="Actually copy over the files to the pyside-setup directory",
+ )
+
+ parser.add_argument(
+ "-q",
+ "--quiet",
+ action="store_true",
+ help="Quiet"
+ )
+
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ dest="verbose",
+ help="Generate more output",
+ )
+
+ parser.add_argument(
+ "-s",
+ "--single",
+ action="store",
+ dest="single_snippet",
+ help="Path to a single file to be translated",
+ )
+
+ parser.add_argument(
+ "--filter",
+ action="store",
+ dest="filter_snippet",
+ help="String to filter the snippets to be translated",
+ )
+ return parser
+
+
+def is_directory(directory):
+ if not os.path.isdir(directory):
+ log.error(f"Path '{directory}' is not a directory")
+ return False
+ return True
+
+
+def check_arguments(options):
+
+ # Notify 'write' option
+ if options.write_files:
+ if not opt_quiet:
+ log.warning(
+ f"Files will be copied from '{options.qt_dir}':\n" f"\tto '{options.pyside_dir}'"
+ )
+ else:
+ msg = "This is a listing only, files are not being copied"
+ if have_rich:
+ msg = f"[green]{msg}[/green]"
+ if not opt_quiet:
+ log.info(msg, extra=extra)
+
+ # Check 'qt_dir' and 'pyside_dir'
+ if is_directory(options.qt_dir) and is_directory(options.pyside_dir):
+ return True
+
+ return False
+
+
+def is_valid_file(x):
+ file_name = x.name
+ # Check END
+ for ext in SKIP_END:
+ if file_name.endswith(ext):
+ return False
+
+ # Check BEGIN
+ for ext in SKIP_BEGIN:
+ if file_name.startswith(ext):
+ return False
+
+ # Contains 'snippets' or 'examples' as subdirectory
+ if not ("snippets" in x.parts or "examples" in x.parts):
+ return False
+
+ return True
+
+
+def get_snippets(data):
+ snippet_lines = ""
+ is_snippet = False
+ snippets = []
+ for line in data:
+ if not is_snippet and line.startswith("//! ["):
+ snippet_lines = line
+ is_snippet = True
+ elif is_snippet:
+ snippet_lines = f"{snippet_lines}\n{line}"
+ if line.startswith("//! ["):
+ is_snippet = False
+ snippets.append(snippet_lines)
+ # Special case when a snippet line is:
+ # //! [1] //! [2]
+ if line.count("//!") > 1:
+ snippet_lines = ""
+ is_snippet = True
+ return snippets
+
+
+def get_license_from_file(filename):
+ lines = []
+ with open(filename, "r") as f:
+ line = True
+ while line:
+ line = f.readline().rstrip()
+
+ if line.startswith("/*") or line.startswith("**"):
+ lines.append(line)
+ # End of the comment
+ if line.endswith("*/"):
+ break
+ if lines:
+ # We know we have the whole block, so we can
+ # perform replacements to translate the comment
+ lines[0] = lines[0].replace("/*", "**").replace("*", "#")
+ lines[-1] = lines[-1].replace("*/", "**").replace("*", "#")
+
+ for i in range(1, len(lines) - 1):
+ lines[i] = re.sub(r"^\*\*", "##", lines[i])
+
+ return "\n".join(lines)
+ else:
+ return ""
+
+def translate_file(file_path, final_path, verbose, write):
+ with open(str(file_path)) as f:
+ snippets = get_snippets(f.read().splitlines())
+ if snippets:
+ # TODO: Get license header first
+ license_header = get_license_from_file(str(file_path))
+ if verbose:
+ if have_rich:
+ console = Console()
+ table = Table(show_header=True, header_style="bold magenta")
+ table.add_column("C++")
+ table.add_column("Python")
+
+ file_snippets = []
+ for snippet in snippets:
+ lines = snippet.split("\n")
+ translated_lines = []
+ for line in lines:
+ if not line:
+ continue
+ translated_line = snippet_translate(line)
+ translated_lines.append(translated_line)
+
+ # logging
+ if verbose:
+ if have_rich:
+ table.add_row(line, translated_line)
+ else:
+ if not opt_quiet:
+ print(line, translated_line)
+
+ if verbose and have_rich:
+ if not opt_quiet:
+ console.print(table)
+
+ file_snippets.append("\n".join(translated_lines))
+
+ if write:
+ # Open the final file
+ with open(str(final_path), "w") as out_f:
+ out_f.write(license_header)
+ out_f.write("\n")
+
+ for s in file_snippets:
+ out_f.write(s)
+ out_f.write("\n\n")
+
+ # Rename to .py
+ written_file = shutil.move(str(final_path), str(final_path.with_suffix(".py")))
+ if not opt_quiet:
+ log.info(f"Written: {written_file}")
+ else:
+ if not opt_quiet:
+ log.warning("No snippets were found")
+
+
+
+def copy_file(file_path, py_path, category, category_path, write=False, verbose=False):
+
+ if not category:
+ translate_file(file_path, Path("_translated.py"), verbose, write)
+ return
+ # Get path after the directory "snippets" or "examples"
+ # and we add +1 to avoid the same directory
+ idx = file_path.parts.index(category) + 1
+ rel_path = Path().joinpath(*file_path.parts[idx:])
+
+ final_path = py_path / category_path / rel_path
+
+ # Check if file exists.
+ if final_path.exists():
+ status_msg = " [yellow][Exists][/yellow]" if have_rich else "[Exists]"
+ status = FileStatus.Exists
+ elif final_path.with_suffix(".py").exists():
+ status_msg = "[cyan][ExistsPy][/cyan]" if have_rich else "[Exists]"
+ status = FileStatus.Exists
+ else:
+ status_msg = " [green][New][/green]" if have_rich else "[New]"
+ status = FileStatus.New
+
+ if verbose:
+ if not opt_quiet:
+ log.info(f"From {file_path} to")
+ log.info(f"==> {final_path}")
+
+ if not opt_quiet:
+ if have_rich:
+ log.info(f"{status_msg} {final_path}", extra={"markup": True})
+ else:
+ log.info(f"{status_msg:10s} {final_path}")
+
+ # Directory where the file will be placed, if it does not exists
+ # we create it. The option 'parents=True' will create the parents
+ # directories if they don't exist, and if some of them exists,
+ # the option 'exist_ok=True' will ignore them.
+ if write and not final_path.parent.is_dir():
+ if not opt_quiet:
+ log.info(f"Creating directories for {final_path.parent}")
+ final_path.parent.mkdir(parents=True, exist_ok=True)
+
+ # Change .cpp to .py
+ # TODO:
+ # - What do we do with .h in case both .cpp and .h exists with
+ # the same name?
+
+ # Translate C++ code into Python code
+ if final_path.name.endswith(".cpp"):
+ translate_file(file_path, final_path, verbose, write)
+
+ return status
+
+
+def process(options):
+ qt_path = Path(options.qt_dir)
+ py_path = Path(options.pyside_dir)
+
+ # (new, exists)
+ valid_new, valid_exists = 0, 0
+
+ # Creating directories in case they don't exist
+ if not OUT_SNIPPETS.is_dir():
+ OUT_SNIPPETS.mkdir(parents=True)
+
+ if not OUT_EXAMPLES.is_dir():
+ OUT_EXAMPLES.mkdir(parents=True)
+
+ if options.single_snippet:
+ f = Path(options.single_snippet)
+ if is_valid_file(f):
+ if "snippets" in f.parts:
+ status = copy_file(
+ f,
+ py_path,
+ "snippets",
+ OUT_SNIPPETS,
+ write=options.write_files,
+ verbose=options.verbose,
+ )
+ elif "examples" in f.parts:
+ status = copy_file(
+ f,
+ py_path,
+ "examples",
+ OUT_EXAMPLES,
+ write=options.write_files,
+ verbose=options.verbose,
+ )
+ else:
+ log.warning("Path did not contain 'snippets' nor 'examples'."
+ "File will not be copied over, just generated locally.")
+ status = copy_file(
+ f,
+ py_path,
+ None,
+ None,
+ write=options.write_files,
+ verbose=options.verbose,
+ )
+
+ else:
+ for i in qt_path.iterdir():
+ module_name = i.name
+ # FIXME: remove this, since it's just for testing.
+ if i.name != "qtbase":
+ continue
+
+ # Filter only Qt modules
+ if not module_name.startswith("qt"):
+ continue
+ if not opt_quiet:
+ log.info(f"Module {module_name}")
+
+ # Iterating everything
+ for f in i.glob("**/*.*"):
+ if is_valid_file(f):
+ if options.filter_snippet:
+ # Proceed only if the full path contain the filter string
+ if options.filter_snippet not in str(f.absolute()):
+ continue
+ if "snippets" in f.parts:
+ status = copy_file(
+ f,
+ py_path,
+ "snippets",
+ OUT_SNIPPETS,
+ write=options.write_files,
+ verbose=options.verbose,
+ )
+ elif "examples" in f.parts:
+ status = copy_file(
+ f,
+ py_path,
+ "examples",
+ OUT_EXAMPLES,
+ write=options.write_files,
+ verbose=options.verbose,
+ )
+
+ # Stats
+ if status == FileStatus.New:
+ valid_new += 1
+ elif status == FileStatus.Exists:
+ valid_exists += 1
+
+ if not opt_quiet:
+ log.info(
+ dedent(
+ f"""\
+ Summary:
+ Total valid files: {valid_new + valid_exists}
+ New files: {valid_new}
+ Existing files: {valid_exists}
+ """
+ )
+ )
+
+
+if __name__ == "__main__":
+ parser = get_parser()
+ options = parser.parse_args()
+ opt_quiet = options.quiet
+
+ if not check_arguments(options):
+ parser.print_help()
+ sys.exit(0)
+
+ process(options)
diff --git a/tools/snippets_translate/parse_utils.py b/tools/snippets_translate/parse_utils.py
new file mode 100644
index 000000000..c4ba91409
--- /dev/null
+++ b/tools/snippets_translate/parse_utils.py
@@ -0,0 +1,145 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+import re
+
+# Bring all the PySide modules to find classes for the imports
+import PySide6
+from PySide6 import *
+
+
+def get_qt_module_class(x):
+ """
+ Receives the name of an include:
+ 'QSomething' from '#include <QSomething>'
+
+ Returns a tuple '(bool, str)' where the 'bool' is True if the name is
+ a module by itself, like QtCore or QtWidgets, and False if it's a class
+ from one of those modules. The 'str' returns the name of the module
+ where the class belongs, or the same module.
+
+ In case it doesn't find the class or the module, it will return None.
+ """
+ for imodule in (m for m in dir(PySide6) if m.startswith("Qt")):
+ if imodule == x:
+ return True, x
+ # we use eval() to transform 'QtModule' into QtModule
+ for iclass in (c for c in dir(eval(f"PySide6.{imodule}")) if c.startswith("Q")):
+ if iclass == x:
+ return False, imodule
+ return None
+
+
+def get_indent(x):
+ return " " * (len(x) - len(x.lstrip()))
+
+# Remove more than one whitespace from the code, but not considering
+# the indentation. Also removes '&', '*', and ';' from arguments.
+def dstrip(x):
+ right = x
+ if re.search(r"\s+", x):
+ right = re.sub(" +", " ", x).strip()
+ if "&" in right:
+ right = right.replace("&", "")
+
+ if "*" in right:
+ re_pointer = re.compile(r"\*(.)")
+ next_char = re_pointer.search(x)
+ if next_char:
+ if next_char.group(1).isalpha():
+ right = right.replace("*", "")
+
+ if right.endswith(";"):
+ right = right.replace(";", "")
+ x = f"{get_indent(x)}{right}"
+
+ return x
+
+
+def remove_ref(var_name):
+ var = var_name.strip()
+ while var.startswith("*") or var.startswith("&"):
+ var = var[1:]
+ return var.lstrip()
+
+
+def parse_arguments(p):
+ unnamed_var = 0
+ if "," in p:
+ v = ""
+ for i, arg in enumerate(p.split(",")):
+ if i != 0:
+ v += ", "
+ if arg:
+ new_value = arg.split()[-1]
+ # handle no variable name
+ if new_value.strip() == "*":
+ v += f"arg__{unnamed_var}"
+ unnamed_var += 1
+ else:
+ v += arg.split()[-1]
+ elif p.strip():
+ new_value = p.split()[-1]
+ if new_value.strip() == "*":
+ v = f"arg__{unnamed_var}"
+ else:
+ v = new_value
+ else:
+ v = p
+
+ return v
+
+
+def replace_main_commas(v):
+ # : QWidget(parent), Something(else, and, other), value(1)
+ new_v = ""
+ parenthesis = 0
+ for c in v:
+ if c == "(":
+ parenthesis += 1
+ elif c == ")":
+ parenthesis -= 1
+
+ if c == "," and parenthesis == 0:
+ c = "@"
+
+ new_v += c
+
+ return new_v
+
diff --git a/tools/snippets_translate/requirements.txt b/tools/snippets_translate/requirements.txt
new file mode 100644
index 000000000..1fb678867
--- /dev/null
+++ b/tools/snippets_translate/requirements.txt
@@ -0,0 +1,2 @@
+rich
+pytest
diff --git a/tools/snippets_translate/snippets_translate.pyproject b/tools/snippets_translate/snippets_translate.pyproject
new file mode 100644
index 000000000..8016eb637
--- /dev/null
+++ b/tools/snippets_translate/snippets_translate.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "converter.py", "handlers.py", "tests/test_converter.py"]
+}
diff --git a/tools/snippets_translate/tests/test_converter.py b/tools/snippets_translate/tests/test_converter.py
new file mode 100644
index 000000000..5656ff5e8
--- /dev/null
+++ b/tools/snippets_translate/tests/test_converter.py
@@ -0,0 +1,439 @@
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+
+from converter import snippet_translate as st
+
+
+def test_comments():
+ assert st("// This is a comment") == "# This is a comment"
+ assert st("// double slash // inside") == "# double slash // inside"
+
+
+def test_comments_eol():
+ assert st("a = 1; // comment") == "a = 1 # comment"
+ assert st("while ( 1 != 1 ) { // comment") == "while 1 != 1: # comment"
+
+
+def test_qdoc_snippets():
+ assert st("//! [0]") == "//! [0]"
+
+
+def test_arrow():
+ assert st("label->setText('something')") == "label.setText('something')"
+
+
+def test_curly_braces():
+ assert st(" {") == ""
+ assert st("}") == ""
+ assert st("while (true){") == "while True:"
+ assert st("while (true) { ") == "while True:"
+
+
+def test_inc_dec():
+ assert st("++i;") == "i = i + 1"
+ assert st("i--;") == "i = i - 1"
+
+
+def test_and_or():
+ assert st("while (a && b)") == "while a and b:"
+ assert st("else if (a || b && c)") == "elif a or b and c:"
+
+
+def test_while_if_elseif():
+ assert st("while(a)") == "while a:"
+ assert st("if (condition){") == "if condition:"
+ assert st("} else if (a) {") == " elif a:"
+ assert (
+ st("if (!m_vbo.isCreated()) // init() failed,")
+ == "if not m_vbo.isCreated(): # init() failed,"
+ )
+ # Special case, second line from a condition
+ assert (
+ st("&& event->answerRect().intersects(dropFrame->geometry()))")
+ == "and event.answerRect().intersects(dropFrame.geometry()))"
+ )
+
+
+def test_else():
+ assert st("else") == "else:"
+ assert st("} else {") == "else:"
+ assert st("}else") == "else:"
+ assert st("else {") == "else:"
+
+
+def test_new():
+ assert st("a = new Something(...);") == "a = Something(...)"
+ assert st("a = new Something") == "a = Something"
+
+
+def test_semicolon():
+ assert st("a = 1;") == "a = 1"
+ assert st("};") == ""
+
+
+def test_include():
+ assert st('#include "something.h"') == "from something import *"
+ assert st("#include <QtCore>") == "from PySide6 import QtCore"
+ assert st("#include <QLabel>") == "from PySide6.QtWidgets import QLabel"
+ assert st("#include <NotQt>") == ""
+ assert st('#include strange"') == ""
+
+
+def test_main():
+ assert st("int main(int argc, char *argv[])") == 'if __name__ == "__main__":'
+
+
+def test_cast():
+ assert st("a = reinterpret_cast<type>(data);") == "a = type(data)"
+ assert st("a = reinterpret_cast<type*>(data) * 9;") == "a = type(data) * 9"
+ assert (
+ st("elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;")
+ == "elapsed = (elapsed + QTimer(sender()).interval()) % 1000"
+ )
+
+
+def test_double_colon():
+ assert st("Qt::Align") == "Qt.Align"
+ assert st('QSound::play("mysounds/bells.wav");') == 'QSound.play("mysounds/bells.wav")'
+ # FIXME
+ assert st("Widget::method") == "Widget::method"
+
+
+def test_cout_endl():
+ assert st("cout << 'hello' << 'world' << endl") == "print('hello', 'world')"
+ assert st(" cout << 'hallo' << 'welt' << endl") == " print('hallo', 'welt')"
+ assert st("cout << 'hi'") == "print('hi')"
+ assert st("'world' << endl") == "print('world')"
+
+ assert st("cout << circ.at(i) << endl;") == "print(circ.at(i))"
+ assert (
+ st('cout << "Element name: " << qPrintable(e.tagName()) << "\n";')
+ == 'print("Element name: ", qPrintable(e.tagName()), "\n")'
+ )
+ assert (
+ st('cout << "First occurrence of Harumi is at position " << i << Qt::endl;')
+ == 'print("First occurrence of Harumi is at position ", i)'
+ )
+ assert st('cout << "Found Jeanette" << endl;') == 'print("Found Jeanette")'
+ assert st('cout << "The key: " << it.key() << Qt::endl') == 'print("The key: ", it.key())'
+ assert (
+ st("cout << (*constIterator).toLocal8Bit().constData() << Qt::endl;")
+ == "print((constIterator).toLocal8Bit().constData())"
+ )
+ assert st("cout << ba[0]; // prints H") == "print(ba[0]) # prints H"
+ assert (
+ st('cout << "Also the value: " << (*it) << Qt::endl;') == 'print("Also the value: ", (it))'
+ )
+ assert st('cout << "[" << *data << "]" << Qt::endl;') == 'print("[", data, "]")'
+
+ assert st('out << "Qt rocks!" << Qt::endl;') == 'print(out, "Qt rocks!")'
+ assert st(' std::cout << "MyObject::MyObject()\n";') == ' print("MyObject::MyObject()\n")'
+ assert st('qDebug() << "Retrieved:" << retrieved;') == 'print("Retrieved:", retrieved)'
+
+
+def test_variable_declaration():
+ assert st("QLabel label;") == "label = QLabel()"
+ assert st('QLabel label("Hello")') == 'label = QLabel("Hello")'
+ assert st("Widget w;") == "w = Widget()"
+ assert st('QLabel *label = new QLabel("Hello");') == 'label = QLabel("Hello")'
+ assert st('QLabel label = a_function("Hello");') == 'label = a_function("Hello")'
+ assert st('QString a = "something";') == 'a = "something"'
+ assert st("int var;") == "var = int()"
+ assert st("float v = 0.1;") == "v = 0.1"
+ assert st("QSome<thing> var") == "var = QSome()"
+ assert st("QQueue<int> queue;") == "queue = QQueue()"
+ assert st("QVBoxLayout *layout = new QVBoxLayout;") == "layout = QVBoxLayout()"
+ assert st("QPointer<QLabel> label = new QLabel;") == "label = QLabel()"
+ assert st("QMatrix4x4 matrix;") == "matrix = QMatrix4x4()"
+ assert st("QList<QImage> collage =") == "collage ="
+
+
+def test_for():
+ assert st("for (int i = 0; i < 10; i++)") == "for i in range(0, 10):"
+ assert st(" for (int i = 0; i < 10; i+=2)") == " for i in range(0, 10, 2):"
+ assert st("for (int i = 10; i >= 0; i-=2)") == "for i in range(-1, 10, -2):"
+ assert st("for (int i = 0; i < 10; ++i)") == "for i in range(0, 10):"
+ assert (
+ st("for (int c = 0;" "c < model.columnCount();" "++c) {")
+ == "for c in range(0, model.columnCount()):"
+ )
+ assert (
+ st("for (int c = 0;" "c < table->columns();" "++c) {")
+ == "for c in range(0, table.columns()):"
+ )
+ assert st("for (int i = 0; i <= 10; i++)") == "for i in range(0, 11):"
+ assert st("for (int i = 10; i >= 0; i--)") == "for i in range(-1, 10, -1):"
+
+ ## if contains "begin()" and "end()", do a 'for it in var'
+ assert (
+ st(
+ "for (QHash<int, QString>::const_iterator it = hash.cbegin(),"
+ "end = hash.cend(); it != end; ++it)"
+ )
+ == "for it in hash:"
+ )
+ assert (
+ st("for (QTextBlock it = doc->begin();" "it != doc->end(); it = it.next())")
+ == "for it in doc:"
+ )
+ assert st("for (auto it = map.begin(); it != map.end(); ++it) {") == "for it in map:"
+ assert st("for (i = future.constBegin(); i != future.constEnd(); ++i)") == "for i in future:"
+ assert st("for (it = block.begin(); !(it.atEnd()); ++it) {") == "for it in block:"
+ assert (
+ st(" for (it = snippetPaths.constBegin();" "it != snippetPaths.constEnd(); ++it)")
+ == " for it in snippetPaths:"
+ )
+
+ assert st("for (QChar ch : s)") == "for ch in s:"
+ assert (
+ st("for (const QByteArray &ext : " "qAsConst(extensionList))")
+ == "for ext in qAsConst(extensionList):"
+ )
+ assert st("for (QTreeWidgetItem *item : found) {") == "for item in found:"
+
+ # TODO: Strange cases
+ # for ( ; it != end; ++it) {
+ # for (; !elt.isNull(); elt = elt.nextSiblingElement("entry")) {
+ # for (int i = 0; ids[i]; ++i)
+ # for (int i = 0; i < (1>>20); ++i)
+ # for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling())
+
+
+def test_emit():
+ assert st("emit sliderPressed();") == "sliderPressed.emit()"
+ assert st("emit actionTriggered(action);") == "actionTriggered.emit(action)"
+ assert st("emit activeChanged(d->m_active);") == "activeChanged.emit(d.m_active)"
+ assert st("emit dataChanged(index, index);") == "dataChanged.emit(index, index)"
+ assert st("emit dataChanged(index, index, {role});") == "dataChanged.emit(index, index, {role})"
+ assert (
+ st('emit dragResult(tr("The data was copied here."));')
+ == 'dragResult.emit(tr("The data was copied here."))'
+ )
+ assert (
+ st("emit mimeTypes(event->mimeData()->formats());")
+ == "mimeTypes.emit(event.mimeData().formats())"
+ )
+ assert (
+ st("emit q_ptr->averageFrequencyChanged(m_averageFrequency);")
+ == "q_ptr.averageFrequencyChanged.emit(m_averageFrequency)"
+ )
+ assert st("emit q_ptr->frequencyChanged();") == "q_ptr.frequencyChanged.emit()"
+ assert (
+ st("emit rangeChanged(d->minimum, d->maximum);")
+ == "rangeChanged.emit(d.minimum, d.maximum)"
+ )
+ assert (
+ st("emit sliderMoved((d->position = value));") == "sliderMoved.emit((d.position = value))"
+ )
+ assert (
+ st("emit stateChanged(QContactAction::FinishedState);")
+ == "stateChanged.emit(QContactAction.FinishedState)"
+ )
+ assert st("emit textCompleted(lineEdit->text());") == "textCompleted.emit(lineEdit.text())"
+ assert (
+ st("emit updateProgress(newstat, m_watcher->progressMaximum());")
+ == "updateProgress.emit(newstat, m_watcher.progressMaximum())"
+ )
+
+
+def test_void_functions():
+ assert st("void Something::Method(int a, char *b) {") == "def Method(self, a, b):"
+ assert (
+ st("void MainWindow::updateMenus(QListWidgetItem *current)")
+ == "def updateMenus(self, current):"
+ )
+ assert (
+ st("void MyScrollArea::scrollContentsBy(int dx, int dy)")
+ == "def scrollContentsBy(self, dx, dy):"
+ )
+ assert st("void Wrapper::wrapper6() {") == "def wrapper6(self):"
+ assert st("void MyClass::setPriority(Priority) {}") == "def setPriority(self, Priority): pass"
+ assert st("void MyException::raise() const { throw *this; }") == "def raise(self): raise self"
+ assert st("void tst_Skip::test_data()") == "def test_data(self):"
+ assert st("void util_function_does_nothing()") == "def util_function_does_nothing():"
+ assert st("static inline void cleanup(MyCustomClass *pointer)") == "def cleanup(pointer):"
+ # TODO: Which name?
+ assert st("void RenderWindow::exposeEvent(QExposeEvent *)") == "def exposeEvent(self, arg__0):"
+
+
+def test_classes():
+ assert st("class MyWidget //: public QWidget") == "class MyWidget(): #: public QWidget"
+ assert st("class MyMfcView : public CView") == "class MyMfcView(CView):"
+ assert st("class MyGame : public QObject {") == "class MyGame(QObject):"
+ assert st("class tst_Skip") == "class tst_Skip():"
+ assert st("class A : public B, protected C") == "class A(B, C):"
+ assert st("class A : public B, public C") == "class A(B, C):"
+ assert st("class SomeTemplate<int> : public QFrame") == "class SomeTemplate(QFrame):"
+ # This is a tricky situation because it has a multi line dependency:
+ # class MyMemberSheetExtension : public QObject,
+ # public QDesignerMemberSheetExtension
+ # {
+ # we will use the leading comma to trust it's the previously situation.
+ assert st("class A : public QObject,") == "class A(QObject,"
+ assert st("class B {...};") == "class B(): pass"
+
+
+def test_constuctors():
+ assert st("MyWidget::MyWidget(QWidget *parent)") == "def __init__(self, parent):"
+ assert st("Window::Window()") == "def __init__(self):"
+
+
+def test_inheritance_init():
+ assert (
+ st(": QClass(fun(re, 1, 2), parent), a(1)")
+ == " QClass.__init__(self, fun(re, 1, 2), parent)\n self.a = 1"
+ )
+ assert (
+ st(": QQmlNdefRecord(copyFooRecord(record), parent)")
+ == " QQmlNdefRecord.__init__(self, copyFooRecord(record), parent)"
+ )
+ assert (
+ st(" : QWidget(parent), helper(helper)")
+ == " QWidget.__init__(self, parent)\n self.helper = helper"
+ )
+ assert st(" : QWidget(parent)") == " QWidget.__init__(self, parent)"
+ assert (
+ st(": a(0), bB(99), cC2(1), p_S(10),")
+ == " self.a = 0\n self.bB = 99\n self.cC2 = 1\n self.p_S = 10"
+ )
+ assert (
+ st(": QAbstractFileEngineIterator(nameFilters, filters), index(0) ")
+ == " QAbstractFileEngineIterator.__init__(self, nameFilters, filters)\n self.index = 0"
+ )
+ assert (
+ st(": m_document(doc), m_text(text)") == " self.m_document = doc\n self.m_text = text"
+ )
+ assert st(": m_size(size) { }") == " self.m_size = size"
+ assert (
+ st(": option->palette.color(QPalette::Mid);")
+ == " self.option.palette.color = QPalette.Mid"
+ )
+ assert st(": QSqlResult(driver) {}") == " QSqlResult.__init__(self, driver)"
+
+
+def test_arrays():
+ assert st("static const GLfloat vertices[] = {") == "vertices = {"
+ assert st("static const char *greeting_strings[] = {") == "greeting_strings = {"
+ assert st("uchar arrow_bits[] = {0x3f, 0x1f, 0x0f}") == "arrow_bits = {0x3f, 0x1f, 0x0f}"
+ assert st("QList<int> vector { 1, 2, 3, 4 };") == "vector = { 1, 2, 3, 4 }"
+
+
+def test_functions():
+ assert st("int Class::method(a, b, c)") == "def method(self, a, b, c):"
+ assert st("QStringView Message::body() const") == "def body(self):"
+ assert st("void Ren::exEvent(QExp *)") == "def exEvent(self, arg__0):"
+ assert (
+ st("QString myDecoderFunc(const QByteArray &localFileName);")
+ == "def myDecoderFunc(localFileName):"
+ )
+
+
+def test_foreach():
+ assert st("foreach (item, selected) {") == "for item in selected:"
+ assert st("foreach (const QVariant &v, iterable) {") == "for v in iterable:"
+ assert st("foreach (QObject *obj, list)") == "for obj in list:"
+ assert (
+ st("foreach (const QContactTag& tag, contact.details<QContactTag>()) {")
+ == "for tag in contact.details():"
+ )
+
+
+def test_structs():
+ assert st("struct ScopedPointerCustomDeleter") == "class ScopedPointerCustomDeleter():"
+ assert st("struct Wrapper : public QWidget {") == "class Wrapper(QWidget):"
+ assert st("struct Window {") == "class Window():"
+
+
+def test_ternary_operator():
+ assert st("int a = 1 ? b > 0 : 3") == "a = 1 if b > 0 else 3"
+ assert (
+ st("if (!game.saveGame(json ? Game::Json : Game::Binary))")
+ == "if not game.saveGame(json if Game.Json else Game.Binary):"
+ )
+
+def test_useless_qt_classes():
+ assert st('result += QLatin1String("; ");') == 'result += "; "'
+ assert st("<< QLatin1Char('\0') << endl;") == "print('\0')"
+
+def test_special_cases():
+ assert (
+ st('http->setProxy("proxy.example.com", 3128);')
+ == 'http.setProxy("proxy.example.com", 3128)'
+ )
+ assert st("delete something;") == "del something"
+ assert (
+ st("m_program->setUniformValue(m_matrixUniform, matrix);")
+ == "m_program.setUniformValue(m_matrixUniform, matrix)"
+ )
+ assert (
+ st("QObject::connect(&window1, &Window::messageSent,")
+ == "QObject.connect(window1, Window.messageSent,"
+ )
+ assert st("double num;") == "num = float()"
+
+ # Leave a comment to remember it comes from C++
+ assert st("public:") == "# public"
+ assert st("private:") == "# private"
+
+
+ # TODO: Handle the existing ones with Python equivalents
+ # assert st("std::...")
+
+ # FIXME: Maybe a better interpretation?
+ # assert st("QDebug operator<<(QDebug dbg, const Message &message)") == "def __str__(self):"
+
+ # TODO: Maybe play with the format?
+ # assert st('m_o.append(tr("version: %1.%2").arg(a).arg(b))') == 'm_o.append(tr("version: {1}.{2}".format(a, b)'
+
+
+def test_lambdas():
+ # QtConcurrent::blockingMap(vector, [](int &x) { x *= 2; });
+
+ # QList<QImage> collage = QtConcurrent::mappedReduced(images,
+ # [&size](const QImage &image) {
+ # return image.scaled(size, size);
+ # },
+ # addToCollage
+ # ).results();
+ pass
+
+
+def test_std_function():
+ # std::function<QImage(const QImage &)> scale = [](const QImage &img) {
+ pass