aboutsummaryrefslogtreecommitdiffstats
path: root/tools/snippets_translate
diff options
context:
space:
mode:
Diffstat (limited to 'tools/snippets_translate')
-rw-r--r--tools/snippets_translate/converter.py41
-rw-r--r--tools/snippets_translate/handlers.py35
-rw-r--r--tools/snippets_translate/main.py16
-rw-r--r--tools/snippets_translate/module_classes.py1
-rw-r--r--tools/snippets_translate/tests/test_converter.py64
-rw-r--r--tools/snippets_translate/tests/test_snippets.py34
6 files changed, 161 insertions, 30 deletions
diff --git a/tools/snippets_translate/converter.py b/tools/snippets_translate/converter.py
index 93aab199f..d45bf277f 100644
--- a/tools/snippets_translate/converter.py
+++ b/tools/snippets_translate/converter.py
@@ -36,13 +36,24 @@ RETURN_TYPE_PATTERN = re.compile(r"^[a-zA-Z0-9]+(<.*?>)? [\w]+::[\w\*\&]+\(.*\)$
FUNCTION_PATTERN = re.compile(r"^[a-zA-Z0-9]+(<.*?>)? [\w\*\&]+\(.*\)$")
ITERATOR_PATTERN = re.compile(r"(std::)?[\w]+<[\w]+>::(const_)?iterator")
SCOPE_PATTERN = re.compile(r"[\w]+::")
+SWITCH_PATTERN = re.compile(r"^\s*switch\s*\(([a-zA-Z0-9_\.]+)\)\s*{.*$")
+CASE_PATTERN = re.compile(r"^(\s*)case\s+([a-zA-Z0-9_:\.]+):.*$")
+DEFAULT_PATTERN = re.compile(r"^(\s*)default:.*$")
QUALIFIERS = {"public:", "protected:", "private:", "public slots:",
"protected slots:", "private slots:", "signals:"}
+FUNCTION_QUALIFIERS = ["virtual ", " override", "inline ", " noexcept"]
+
+
+switch_var = None
+switch_branch = 0
+
+
def snippet_translate(x):
+ global switch_var, switch_branch
## Cases which are not C++
## TODO: Maybe expand this with lines that doesn't need to be translated
@@ -52,7 +63,8 @@ def snippet_translate(x):
## General Rules
# Remove ';' at the end of the lines
- if x.endswith(";"):
+ has_semicolon = x.endswith(";")
+ if has_semicolon:
x = x[:-1]
# Remove lines with only '{' or '}'
@@ -136,6 +148,25 @@ def snippet_translate(x):
if "throw" in x:
x = handle_keywords(x, "throw", "raise")
+ switch_match = SWITCH_PATTERN.match(x)
+ if switch_match:
+ switch_var = switch_match.group(1)
+ switch_branch = 0
+ return ""
+
+ switch_match = CASE_PATTERN.match(x)
+ if switch_match:
+ indent = switch_match.group(1)
+ value = switch_match.group(2).replace("::", ".")
+ cond = "if" if switch_branch == 0 else "elif"
+ switch_branch += 1
+ return f"{indent}{cond} {switch_var} == {value}:"
+
+ switch_match = DEFAULT_PATTERN.match(x)
+ if switch_match:
+ indent = switch_match.group(1)
+ return f"{indent}else:"
+
# handle 'void Class::method(...)' and 'void method(...)'
if VOID_METHOD_PATTERN.search(x):
x = handle_void_functions(x)
@@ -233,7 +264,8 @@ def snippet_translate(x):
# At the end we skip methods with the form:
# QStringView Message::body()
# to threat them as methods.
- if (VAR1_PATTERN.search(xs)
+ if (has_semicolon and VAR1_PATTERN.search(xs)
+ and not ([f for f in FUNCTION_QUALIFIERS if f in x])
and xs.split()[0] not in ("def", "return", "and", "or")
and not VAR2_PATTERN.search(xs)
and ("{" not in x and "}" not in x)):
@@ -264,7 +296,10 @@ def snippet_translate(x):
# so we need to add '()' at the end if it's just a word
# with only alpha numeric content
if VAR4_PATTERN.search(xs) and not xs.endswith(")"):
- x = f"{x.rstrip()}()"
+ v = x.rstrip()
+ if (not v.endswith(" True") and not v.endswith(" False")
+ and not v.endswith(" None")):
+ x = f"{v}()"
return dstrip(x)
# For constructors, that we now the shape is:
diff --git a/tools/snippets_translate/handlers.py b/tools/snippets_translate/handlers.py
index 1af97ff64..34e969a62 100644
--- a/tools/snippets_translate/handlers.py
+++ b/tools/snippets_translate/handlers.py
@@ -30,8 +30,13 @@ ARRAY_DECLARATION_PATTERN = re.compile(r"^[a-zA-Z0-9\<\>]+ ([\w\*]+) *\[?\]?")
RETURN_TYPE_PATTERN = re.compile(r"^ *[a-zA-Z0-9]+ [\w]+::([\w\*\&]+\(.*\)$)")
CAPTURE_PATTERN = re.compile(r"^ *([a-zA-Z0-9]+) ([\w\*\&]+\(.*\)$)")
USELESS_QT_CLASSES_PATTERNS = [
- re.compile(r"QLatin1String\((.*)\)"),
- re.compile(r"QLatin1Char\((.*)\)")
+ re.compile(r'QLatin1StringView\(("[^"]*")\)'),
+ re.compile(r'QLatin1String\(("[^"]*")\)'),
+ re.compile(r'QString\.fromLatin1\(("[^"]*")\)'),
+ re.compile(r"QLatin1Char\(('[^']*')\)"),
+ re.compile(r'QStringLiteral\(("[^"]*")\)'),
+ re.compile(r'QString\.fromUtf8\(("[^"]*")\)'),
+ re.compile(r'u("[^"]*")_s')
]
COMMENT1_PATTERN = re.compile(r" *# *[\w\ ]+$")
COMMENT2_PATTERN = re.compile(r" *# *(.*)$")
@@ -510,10 +515,13 @@ def handle_functions(x):
def handle_useless_qt_classes(x):
for c in USELESS_QT_CLASSES_PATTERNS:
- content = c.search(x)
- if content:
- x = x.replace(content.group(0), content.group(1))
- return x
+ while True:
+ match = c.search(x)
+ if match:
+ x = x[0:match.start()] + match.group(1) + x[match.end():]
+ else:
+ break
+ return x.replace('"_L1', '"').replace("u'", "'")
def handle_new(x):
@@ -547,13 +555,19 @@ def handle_new(x):
INSTANCE_PMF_RE = re.compile(r"&?(\w+),\s*&\w+::(\w+)")
-CONNECT_RE = re.compile(r"^(\s*)(QObject::)?connect\((\w+\.\w+),\s*")
+CONNECT_RE = re.compile(r"^(\s*)(QObject::)?connect\(([A-Za-z0-9_\.]+),\s*")
-def handle_qt_connects(line):
- if not INSTANCE_PMF_RE.search(line):
+def handle_qt_connects(line_in):
+ if not INSTANCE_PMF_RE.search(line_in):
return None
# 1st pass, "fontButton, &QAbstractButton::clicked" -> "fontButton.clicked"
+
+ is_connect = "connect(" in line_in
+ line = line_in
+ # Remove any smart pointer access, etc in connect statements
+ if is_connect:
+ line = line.replace(".get()", "").replace(".data()", "").replace("->", ".")
last_pos = 0
result = ""
for match in INSTANCE_PMF_RE.finditer(line):
@@ -567,6 +581,9 @@ def handle_qt_connects(line):
result += f"{instance}.{member_fun}"
result += line[last_pos:]
+ if not is_connect:
+ return result
+
# 2nd pass, reorder connect.
connect_match = CONNECT_RE.match(result)
if not connect_match:
diff --git a/tools/snippets_translate/main.py b/tools/snippets_translate/main.py
index 12b6e54f1..01ea06c5e 100644
--- a/tools/snippets_translate/main.py
+++ b/tools/snippets_translate/main.py
@@ -213,7 +213,9 @@ def get_snippet_override(start_id: str, rel_path: str) -> List[str]:
return overriden_snippet_lines(lines, start_id)
-def _get_snippets(lines: List[str], pattern: re.Pattern) -> Dict[str, List[str]]:
+def _get_snippets(lines: List[str],
+ comment: str,
+ pattern: re.Pattern) -> Dict[str, List[str]]:
"""Helper to extract (potentially overlapping) snippets from a C++ file
indicated by pattern ("//! [1]") and return them as a dict by <id>."""
snippets: Dict[str, List[str]] = {}
@@ -231,8 +233,12 @@ def _get_snippets(lines: List[str], pattern: re.Pattern) -> Dict[str, List[str]]
start_id = start_ids.pop(0)
if start_id in done_snippets:
continue
+
+ # Reconstruct a single ID line to avoid repetitive ID lines
+ # by consecutive snippets with multi-ID lines like "//! [1] [2]"
+ id_line = f"{comment}! [{start_id}]"
done_snippets.append(start_id)
- snippet = [line] # The snippet starts with this id
+ snippet = [id_line] # The snippet starts with this id
# Find the end of the snippet
j = i
@@ -246,6 +252,7 @@ def _get_snippets(lines: List[str], pattern: re.Pattern) -> Dict[str, List[str]]
# Check if the snippet is complete
if start_id in get_snippet_ids(l, pattern):
# End of snippet
+ snippet[len(snippet) - 1] = id_line
snippets[start_id] = snippet
break
@@ -260,7 +267,7 @@ def get_python_example_snippet_override(start_id: str, rel_path: str) -> List[st
return []
path, id = value
file_lines = path.read_text().splitlines()
- snippet_dict = _get_snippets(file_lines, PYTHON_SNIPPET_PATTERN)
+ snippet_dict = _get_snippets(file_lines, '#', PYTHON_SNIPPET_PATTERN)
lines = snippet_dict.get(id)
if not lines:
raise RuntimeError(f'Snippet "{id}" not found in "{os.fspath(path)}"')
@@ -271,7 +278,7 @@ def get_python_example_snippet_override(start_id: str, rel_path: str) -> List[st
def get_snippets(lines: List[str], rel_path: str) -> List[List[str]]:
"""Extract (potentially overlapping) snippets from a C++ file indicated
by '//! [1]'."""
- result = _get_snippets(lines, CPP_SNIPPET_PATTERN)
+ result = _get_snippets(lines, '//', CPP_SNIPPET_PATTERN)
id_list = result.keys()
for snippet_id in id_list:
# Check file overrides and example overrides
@@ -371,6 +378,7 @@ def translate_file(file_path, final_path, qt_path, debug, write):
target_file.parent.mkdir(parents=True, exist_ok=True)
with target_file.open("w", encoding="utf-8") as out_f:
+ out_f.write("//! [AUTO]\n\n")
out_f.write(license_header)
out_f.write("\n\n")
diff --git a/tools/snippets_translate/module_classes.py b/tools/snippets_translate/module_classes.py
index 4992e170b..df4c7557c 100644
--- a/tools/snippets_translate/module_classes.py
+++ b/tools/snippets_translate/module_classes.py
@@ -532,6 +532,7 @@ module_classes = {
"QAccessibleEvent",
"QAccessibleInterface",
"QAccessibleObject",
+ "QAccessibleSelectionInterface",
"QAccessibleStateChangeEvent",
"QAccessibleTableCellInterface",
"QAccessibleTableModelChangeEvent",
diff --git a/tools/snippets_translate/tests/test_converter.py b/tools/snippets_translate/tests/test_converter.py
index 4cf614d1e..084cc8a6d 100644
--- a/tools/snippets_translate/tests/test_converter.py
+++ b/tools/snippets_translate/tests/test_converter.py
@@ -4,6 +4,11 @@
from converter import snippet_translate as st
+def multi_st(lines):
+ result = [st(l) for l in lines.split("\n")]
+ return "\n".join(result)
+
+
def test_comments():
assert st("// This is a comment") == "# This is a comment"
assert st("// double slash // inside") == "# double slash // inside"
@@ -112,6 +117,21 @@ def test_double_colon():
assert st("this, &MyClass::slotError);") == "self.slotError)"
+def test_connects():
+ assert (
+ st("connect(button, &QPushButton::clicked, this, &MyClass::slotClicked);")
+ == "button.clicked.connect(self.slotClicked)"
+ )
+ assert (
+ st("connect(m_ui->button, &QPushButton::clicked, this, &MyClass::slotClicked);")
+ == "m_ui.button.clicked.connect(self.slotClicked)"
+ )
+ assert (
+ st("connect(button.get(), &QPushButton::clicked, this, &MyClass::slotClicked);")
+ == "button.clicked.connect(self.slotClicked)"
+ )
+
+
def test_cout_endl():
assert st("cout << 'hello' << 'world' << endl") == "print('hello', 'world')"
assert st(" cout << 'hallo' << 'welt' << endl") == " print('hallo', 'welt')"
@@ -146,19 +166,25 @@ def test_cout_endl():
def test_variable_declaration():
assert st("QLabel label;") == "label = QLabel()"
- assert st('QLabel label("Hello")') == 'label = QLabel("Hello")'
+ 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("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 ="
+ assert st("bool b = true;") == "b = True"
+ assert st("Q3DBars *m_graph = nullptr;") == "m_graph = None"
+ # Do not fall for member function definitions
+ assert st("Q3DBars *Graph::bars() const") == "Q3DBars Graph.bars()"
+ # Do not fall for member function declarations
+ assert st("virtual Q3DBars *bars();") == "virtual Q3DBars bars()"
def test_for():
@@ -368,7 +394,12 @@ def test_ternary_operator():
def test_useless_qt_classes():
assert st('result += QLatin1String("; ");') == 'result += "; "'
+ assert st('result += QString::fromLatin1("; ");') == 'result += "; "'
+ assert (
+ st('result = QStringLiteral("A") + QStringLiteral("B");')
+ == 'result = "A" + "B"')
assert st("<< QLatin1Char('\0') << endl;") == "print('\0')"
+ assert st('result = u"A"_s;') == 'result = "A"'
def test_special_cases():
@@ -416,6 +447,35 @@ def test_lambdas():
pass
+def test_switch_case():
+ source = """switch (v) {
+case 1:
+ f1();
+ break;
+case ClassName::EnumValue:
+ f2();
+ break;
+default:
+ f3();
+ break;
+}
+"""
+ expected = """
+if v == 1:
+ f1()
+ break
+elif v == ClassName.EnumValue:
+ f2()
+ break
+else:
+ f3()
+ break
+
+"""
+
+ assert multi_st(source) == expected
+
+
def test_std_function():
# std::function<QImage(const QImage &)> scale = [](const QImage &img) {
pass
diff --git a/tools/snippets_translate/tests/test_snippets.py b/tools/snippets_translate/tests/test_snippets.py
index 3c29fcbf5..84897d815 100644
--- a/tools/snippets_translate/tests/test_snippets.py
+++ b/tools/snippets_translate/tests/test_snippets.py
@@ -4,6 +4,9 @@
from main import _get_snippets, get_snippet_ids, CPP_SNIPPET_PATTERN
+C_COMMENT = "//"
+
+
def test_stacking():
lines = [
"//! [A] //! [B] ",
@@ -12,7 +15,7 @@ def test_stacking():
"//! [C] //! [A] ",
"//! [B] //! [D] //! [E]",
]
- snippets = _get_snippets(lines, CPP_SNIPPET_PATTERN)
+ snippets = _get_snippets(lines, C_COMMENT, CPP_SNIPPET_PATTERN)
assert len(snippets) == 5
snippet_a = snippets["A"]
@@ -41,7 +44,7 @@ def test_nesting():
"//! [C]",
"//! [B]",
]
- snippets = _get_snippets(lines, CPP_SNIPPET_PATTERN)
+ snippets = _get_snippets(lines, C_COMMENT, CPP_SNIPPET_PATTERN)
assert len(snippets) == 3
snippet_a = snippets["A"]
@@ -58,24 +61,27 @@ def test_nesting():
def test_overlapping():
+ a_id = "//! [A]"
+ b_id = "//! [B]"
lines = [
"pretext",
- "//! [A]",
+ a_id,
"l1",
"//! [C]",
"//! [A] //! [B]",
"l2",
"l3 // Comment",
- "//! [B]",
+ b_id,
"posttext",
"//! [C]",
]
- snippets = _get_snippets(lines, CPP_SNIPPET_PATTERN)
+ snippets = _get_snippets(lines, C_COMMENT, CPP_SNIPPET_PATTERN)
assert len(snippets) == 3
+ # Simple snippet ID lines are generated
snippet_a = snippets["A"]
assert len(snippet_a) == 4
- assert snippet_a == lines[1:5]
+ assert snippet_a == lines[1:4] + [a_id]
snippet_c = snippets["C"]
assert len(snippet_c) == 7
@@ -83,31 +89,35 @@ def test_overlapping():
snippet_b = snippets["B"]
assert len(snippet_b) == 4
- assert snippet_b == lines[4:8]
+ assert snippet_b == [b_id] + lines[5:8]
def test_snippets():
+ a_id = "//! [A]"
+ b_id = "//! [B]"
+
lines = [
"pretext",
- "//! [A]",
+ a_id,
"l1",
"//! [A] //! [B]",
"l2",
"l3 // Comment",
- "//! [B]",
+ b_id,
"posttext"
]
- snippets = _get_snippets(lines, CPP_SNIPPET_PATTERN)
+ snippets = _get_snippets(lines, C_COMMENT, CPP_SNIPPET_PATTERN)
assert len(snippets) == 2
snippet_a = snippets["A"]
+
assert len(snippet_a) == 3
- assert snippet_a == lines[1:4]
+ assert snippet_a == lines[1:3] + [a_id]
snippet_b = snippets["B"]
assert len(snippet_b) == 4
- assert snippet_b == lines[3:7]
+ assert snippet_b == [b_id] + lines[4:7]
def test_snippet_ids():