diff options
Diffstat (limited to 'tools/snippets_translate')
-rw-r--r-- | tools/snippets_translate/converter.py | 41 | ||||
-rw-r--r-- | tools/snippets_translate/handlers.py | 35 | ||||
-rw-r--r-- | tools/snippets_translate/main.py | 16 | ||||
-rw-r--r-- | tools/snippets_translate/module_classes.py | 1 | ||||
-rw-r--r-- | tools/snippets_translate/tests/test_converter.py | 64 | ||||
-rw-r--r-- | tools/snippets_translate/tests/test_snippets.py | 34 |
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(): |