Vladimir Sementsov-Ogievskiy | 25ad2cf | 2023-10-06 18:41:24 +0300 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Intended usage: |
| 4 | # |
| 5 | # git grep -l '\.qmp(' | xargs ./scripts/python_qmp_updater.py |
| 6 | # |
| 7 | |
| 8 | import re |
| 9 | import sys |
| 10 | from typing import Optional |
| 11 | |
| 12 | start_reg = re.compile(r'^(?P<padding> *)(?P<res>\w+) = (?P<vm>.*).qmp\(', |
| 13 | flags=re.MULTILINE) |
| 14 | |
| 15 | success_reg_templ = re.sub('\n *', '', r""" |
| 16 | (\n*{padding}(?P<comment>\#.*$))? |
| 17 | \n*{padding} |
| 18 | ( |
| 19 | self.assert_qmp\({res},\ 'return',\ {{}}\) |
| 20 | | |
| 21 | assert\ {res}\['return'\]\ ==\ {{}} |
| 22 | | |
| 23 | assert\ {res}\ ==\ {{'return':\ {{}}}} |
| 24 | | |
| 25 | self.assertEqual\({res}\['return'\],\ {{}}\) |
| 26 | )""") |
| 27 | |
| 28 | some_check_templ = re.sub('\n *', '', r""" |
| 29 | (\n*{padding}(?P<comment>\#.*$))? |
| 30 | \s*self.assert_qmp\({res},""") |
| 31 | |
| 32 | |
| 33 | def tmatch(template: str, text: str, |
| 34 | padding: str, res: str) -> Optional[re.Match[str]]: |
| 35 | return re.match(template.format(padding=padding, res=res), text, |
| 36 | flags=re.MULTILINE) |
| 37 | |
| 38 | |
| 39 | def find_closing_brace(text: str, start: int) -> int: |
| 40 | """ |
| 41 | Having '(' at text[start] search for pairing ')' and return its index. |
| 42 | """ |
| 43 | assert text[start] == '(' |
| 44 | |
| 45 | height = 1 |
| 46 | |
| 47 | for i in range(start + 1, len(text)): |
| 48 | if text[i] == '(': |
| 49 | height += 1 |
| 50 | elif text[i] == ')': |
| 51 | height -= 1 |
| 52 | if height == 0: |
| 53 | return i |
| 54 | |
| 55 | raise ValueError |
| 56 | |
| 57 | |
| 58 | def update(text: str) -> str: |
| 59 | result = '' |
| 60 | |
| 61 | while True: |
| 62 | m = start_reg.search(text) |
| 63 | if m is None: |
| 64 | result += text |
| 65 | break |
| 66 | |
| 67 | result += text[:m.start()] |
| 68 | |
| 69 | args_ind = m.end() |
| 70 | args_end = find_closing_brace(text, args_ind - 1) |
| 71 | |
| 72 | all_args = text[args_ind:args_end].split(',', 1) |
| 73 | |
| 74 | name = all_args[0] |
| 75 | args = None if len(all_args) == 1 else all_args[1] |
| 76 | |
| 77 | unchanged_call = text[m.start():args_end+1] |
| 78 | text = text[args_end+1:] |
| 79 | |
| 80 | padding, res, vm = m.group('padding', 'res', 'vm') |
| 81 | |
| 82 | m = tmatch(success_reg_templ, text, padding, res) |
| 83 | |
| 84 | if m is None: |
| 85 | result += unchanged_call |
| 86 | |
| 87 | if ('query-' not in name and |
| 88 | 'x-debug-block-dirty-bitmap-sha256' not in name and |
| 89 | not tmatch(some_check_templ, text, padding, res)): |
| 90 | print(unchanged_call + text[:200] + '...\n\n') |
| 91 | |
| 92 | continue |
| 93 | |
| 94 | if m.group('comment'): |
| 95 | result += f'{padding}{m.group("comment")}\n' |
| 96 | |
| 97 | result += f'{padding}{vm}.cmd({name}' |
| 98 | |
| 99 | if args: |
| 100 | result += ',' |
| 101 | |
| 102 | if '\n' in args: |
| 103 | m_args = re.search('(?P<pad> *).*$', args) |
| 104 | assert m_args is not None |
| 105 | |
| 106 | cur_padding = len(m_args.group('pad')) |
| 107 | expected = len(f'{padding}{res} = {vm}.qmp(') |
| 108 | drop = len(f'{res} = ') |
| 109 | if cur_padding == expected - 1: |
| 110 | # tolerate this bad style |
| 111 | drop -= 1 |
| 112 | elif cur_padding < expected - 1: |
| 113 | # assume nothing to do |
| 114 | drop = 0 |
| 115 | |
| 116 | if drop: |
| 117 | args = re.sub('\n' + ' ' * drop, '\n', args) |
| 118 | |
| 119 | result += args |
| 120 | |
| 121 | result += ')' |
| 122 | |
| 123 | text = text[m.end():] |
| 124 | |
| 125 | return result |
| 126 | |
| 127 | |
| 128 | for fname in sys.argv[1:]: |
| 129 | print(fname) |
| 130 | with open(fname) as f: |
| 131 | t = f.read() |
| 132 | |
| 133 | t = update(t) |
| 134 | |
| 135 | with open(fname, 'w') as f: |
| 136 | f.write(t) |